commit 6366859bb3bd5d2784c2a3dcdb9d5c3f9eee7839 Author: whm <973418690@qq.com> Date: Fri Jul 31 10:19:22 2026 +0800 chore: initial commit of ai site platform Co-authored-by: Cursor diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..098e51e --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# copy to .env and fill keys +# 供应商列表 / 默认模型见 ai-service/etc/llm.yaml(勿在代码里改) +LLM_PROVIDER=deepseek +DEEPSEEK_API_KEY= +MINIMAX_API_KEY= + +# 视觉(可选;默认读 llm.yaml 的 vision 段,也可用环境变量覆盖) +VISION_PROVIDER=dashscope +DASHSCOPE_API_KEY= +DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +VISION_MODEL=qwen3.6-plus +# AI_CONFIG_PATH=/app/etc/llm.yaml + +# 有截图时:视觉摘录 → 代码出蓝图 → 发布并截真页面 → 对比打分 → 再改,直到 ≥ FIDELITY_TARGET +FIDELITY_LOOP=1 +FIDELITY_TARGET=95 +FIDELITY_MAX_ROUNDS=4 +FIDELITY_REAL_SCREEN=1 +PLATFORM_BASE=http://127.0.0.1:8180 +WEB_BASE=http://127.0.0.1:5173 +FIDELITY_USER=demo +FIDELITY_PASSWORD=demo123 +# FIDELITY_SHOT_DIR=E:/project/ai建站/test/_out + +# ---- 同机 Docker 端口(默认绑 127.0.0.1,避开 yh_web 的 8088/9080/9081 与宿主机 80/443)---- +# AIJZ_WEB_PUBLISH=127.0.0.1:5173 +# AIJZ_GATEWAY_PUBLISH=127.0.0.1:8180 +# AIJZ_PLATFORM_PUBLISH=127.0.0.1:8888 +# AIJZ_AI_PUBLISH=127.0.0.1:8001 +# AIJZ_PG_PUBLISH=127.0.0.1:5432 +# 宿主机已有 Postgres 时改为:AIJZ_PG_PUBLISH=127.0.0.1:15432 +# 需要局域网直连时改为:AIJZ_WEB_PUBLISH=0.0.0.0:5173 + +# ---- 对外域名(可选;与同机 yh_web 并存时用不同域名)---- +# AIJZ_ENABLE_HOST_NGINX=1 +# AIJZ_DOMAIN=aijz.example.com +# AIJZ_PUBLIC_BASE_URL=https://aijz.example.com +# 证书放 nginx/<域名>.pem + nginx/<域名>.key,或 nginx/fullchain.pem + privkey.pem +# 改配置后:./reload-config.sh host-nginx 或 ./reload-config.sh platform diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cb8b15c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +start.sh text eol=lf +stop.sh text eol=lf +restart.sh text eol=lf +pull-and-restart.sh text eol=lf +deploy-menu.sh text eol=lf +reload-config.sh text eol=lf +nginx/*.conf text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ced432d --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# 运行时物理目录(Linux 一键脚本) +.runtime/ +platform/data/uploads/ + +# 编译产物 +platform/platform +platform/platform.exe +gateway/gateway +gateway/gateway.exe + +# 依赖 / 缓存 +web/node_modules/ +**/__pycache__/ +*.pyc +.venv/ +.env +ai-service/.env +*.log + +*.exe~ + diff --git a/ENV.md b/ENV.md new file mode 100644 index 0000000..08609bf --- /dev/null +++ b/ENV.md @@ -0,0 +1,86 @@ +# Windows 本地环境说明 + +更新时间:2026-07-17 + +## 已就绪 + +| 组件 | 版本/状态 | 说明 | +|------|-----------|------| +| Go | 1.21.13 | 中台必需,已有 | +| Python | 3.13+ | AI / 蓝图校验,已有;已装 `jsonschema` | +| Node.js | v22 | 前端预留,已有 | +| Git | 2.52 | 已有 | +| PostgreSQL | 16.14 | **本次安装**,服务 `postgresql-x64-16` 自动启动 | +| Docker Desktop | 4.82 / engine 29.x | **本次安装**(可选;当前项目用本机 Postgres 即可) | + +**不需要 Java。** + +## 数据库连接 + +```text +Host: 127.0.0.1 +Port: 5432 +User: platform +Password: platform +Database: platform +``` + +连接串(已写入 `platform/etc/platform.yaml`): + +```text +postgres://platform:platform@127.0.0.1:5432/platform?sslmode=disable +``` + +超级用户(安装时设置):`postgres` / `platform` + +`psql` 路径已加入用户 PATH:`C:\Program Files\PostgreSQL\16\bin` +(新开终端后生效) + +## 启动中台 + +```powershell +cd E:\project\ai建站\platform +go run . -f etc/platform.yaml +# 另开终端 +go run .\scripts\smoke.go +``` + +正常日志应出现:`postgres engine enabled` 且 `memoryMode=false`。 + +## Docker 注意 + +Docker Desktop 已安装,首次使用可能需要: + +1. 从开始菜单打开 **Docker Desktop** 完成初始化 +2. 若提示启用 WSL2,按向导操作(本机已有 WSL Ubuntu-22.04) +3. 重启后再执行 `docker version` + +### 国内镜像(已接入) + +| 用途 | 源 | +|------|-----| +| Docker Hub 拉取 | DaoCloud / 1ms / 轩辕(`scripts\Apply-DockerMirrors.ps1` 写入 `%USERPROFILE%\.docker\daemon.json`) | +| 基础镜像前缀 | `docker.m.daocloud.io/library`(compose / Dockerfile,可用环境变量 `DOCKER_BASE_REGISTRY` 覆盖) | +| Go modules | `goproxy.cn` | +| pip | 清华 `pypi.tuna.tsinghua.edu.cn` | +| npm | `registry.npmmirror.com`(`web/.npmrc`) | + +```powershell +# 仅写入 Docker Desktop 镜像配置(改完后建议重启一次 Docker Desktop) +.\scripts\Apply-DockerMirrors.ps1 + +# 正常启动会自动 Apply + 优先走国内前缀拉镜像 +.\start.ps1 +``` + +本项目数据库也可用本机 PostgreSQL;Docker 拉不动时会自动回退本机模式。 + +## 常用检查 + +```powershell +go version +python --version +node --version +psql -U platform -h 127.0.0.1 -d platform -c "SELECT 1" +Get-Service postgresql-x64-16 +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdb4e0f --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# 宇信达智建 + +## 要不要上 K8s? + +**现阶段不需要。** Docker Compose 已覆盖单机/小团队全栈;等出现多机部署、弹性扩缩容、灰度发布、服务网格等需求再上 Kubernetes。 + +## 推荐拓扑(默认 Docker) + +```text +浏览器 + → web :5173 (nginx) + → gateway :8180 + → platform :8888 + → ai :8001 + → postgres :5432 +``` + +数据挂载到仓库物理目录 `.runtime/`(不是仅存在容器层)。 + +## 一键启动 + +先启动 **Docker Desktop**(Windows)或确保 `docker compose` 可用。 + +### Windows +```powershell +.\start.ps1 # 默认 Compose 全栈 +.\start.ps1 -Rebuild +.\stop.ps1 +.\restart.ps1 # 先停再启 +``` +也可双击 `start.bat` / `restart.bat`。 + +### Linux +```bash +chmod +x start.sh stop.sh restart.sh scripts/linux/*.sh +./start.sh # 默认 Compose +./start.sh --rebuild +./stop.sh +./restart.sh # 先停再启 +``` + +无 Docker 时脚本会**自动回退**宿主机模式(`--host` 可强制)。 + +| 物理路径 | 内容 | +|----------|------| +| `.runtime/pgdata` | Postgres 数据 | +| `.runtime/uploads` | 对象存储 | +| `.runtime/logs` | (宿主机模式日志) | + +打开 http://127.0.0.1:5173 +演示账号:`demo` / `demo123` + +等价手动命令: + +```bash +docker compose up -d --build +docker compose down # 保留 .runtime +``` + +## 能力清单 + +| 能力 | 说明 | +|------|------| +| RBAC | JWT 含 `role`(owner/editor/viewer) | +| 对象存储 | 上传落 `.runtime/uploads` | +| 发布审计 | `GET /api/v1/audit/logs` | +| database_per_app | 独立库;compose 内 platform 用户已具备建库权限 | +| 网关鉴权 | JWT 预检 + 中台二次校验 | +| 多模态 | 截图理解(可设 `OPENAI_API_KEY`) | + +## 自动化测试 + +```powershell +python scripts/e2e_flow.py +``` + +## 网关路由 + +| 路径 | 上游 | +|------|------| +| `/api/v1/*` | platform | +| `/ai/*` | AI | +| `/gateway/health` | 健康检查 | + +可选 APISIX 仍见 `gateway/README.md`(`:9080`),日常用 Compose 里的 Go 网关即可。 diff --git a/agent_sdk/client.py b/agent_sdk/client.py new file mode 100644 index 0000000..393880f --- /dev/null +++ b/agent_sdk/client.py @@ -0,0 +1,98 @@ +"""智能体侧:解密胶囊并按契约请求平台(前端不持有明文契约)。""" + +from __future__ import annotations + +import base64 +import json +from typing import Any + +import httpx +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + + +PREFIX = "AJZ1" + + +def decrypt_capsule(agent_key_b64: str, capsule: str) -> dict[str, Any]: + parts = capsule.split(".") + if len(parts) != 3 or parts[0] != PREFIX: + raise ValueError("invalid capsule") + key = base64.urlsafe_b64decode(pad(agent_key_b64)) + nonce = base64.urlsafe_b64decode(pad(parts[1])) + data = base64.urlsafe_b64decode(pad(parts[2])) + # Go RawURLEncoding has no padding; Python needs pad + aes = AESGCM(key) + plain = aes.decrypt(nonce, data, None) + return json.loads(plain) + + +def pad(s: str) -> str: + return s + "=" * (-len(s) % 4) + + +class AgentClient: + def __init__(self, access_token: str, agent_key: str, capsule: str): + self.access_token = access_token + self.desc = decrypt_capsule(agent_key, capsule) + self.http = httpx.Client( + base_url=self.desc["base_url"], + headers={"Authorization": f"Bearer {access_token}"}, + timeout=30.0, + ) + + def resources(self) -> list[dict[str, Any]]: + return self.desc.get("resources", []) + + def list(self, resource_name: str, **filters: str) -> dict[str, Any]: + res = self._find(resource_name) + params = {f"filter.{k}": v for k, v in filters.items()} + r = self.http.get(res["path"], params=params) + r.raise_for_status() + return r.json() + + def create(self, resource_name: str, body: dict[str, Any]) -> dict[str, Any]: + res = self._find(resource_name) + r = self.http.post(res["path"], json=body) + r.raise_for_status() + return r.json() + + def get(self, resource_name: str, row_id: str) -> dict[str, Any]: + res = self._find(resource_name) + r = self.http.get(f"{res['path'].rstrip('/')}/{row_id}") + r.raise_for_status() + return r.json() + + def update(self, resource_name: str, row_id: str, body: dict[str, Any]) -> dict[str, Any]: + res = self._find(resource_name) + r = self.http.put(f"{res['path'].rstrip('/')}/{row_id}", json=body) + r.raise_for_status() + return r.json() + + def delete(self, resource_name: str, row_id: str) -> None: + res = self._find(resource_name) + r = self.http.delete(f"{res['path'].rstrip('/')}/{row_id}") + if r.status_code not in (200, 204): + r.raise_for_status() + + def _find(self, name: str) -> dict[str, Any]: + for r in self.resources(): + if r["name"] == name: + return r + raise KeyError(f"resource not in capsule: {name}") + + +if __name__ == "__main__": + import os + import sys + + token = os.environ.get("AJZ_TOKEN", "") + key = os.environ.get("AJZ_AGENT_KEY", "") + capsule = os.environ.get("AJZ_CAPSULE", "") + if not (token and key and capsule): + print("Set AJZ_TOKEN / AJZ_AGENT_KEY / AJZ_CAPSULE", file=sys.stderr) + sys.exit(1) + client = AgentClient(token, key, capsule) + print(json.dumps({"app": client.desc["app_slug"], "resources": [r["name"] for r in client.resources()]}, ensure_ascii=False)) + if client.resources(): + name = client.resources()[0]["name"] + print(json.dumps(client.list(name), ensure_ascii=False, indent=2)) diff --git a/agent_sdk/requirements.txt b/agent_sdk/requirements.txt new file mode 100644 index 0000000..86b4af6 --- /dev/null +++ b/agent_sdk/requirements.txt @@ -0,0 +1,2 @@ +httpx==0.28.1 +cryptography==44.0.0 diff --git a/ai-service/.dockerignore b/ai-service/.dockerignore new file mode 100644 index 0000000..ae24b00 --- /dev/null +++ b/ai-service/.dockerignore @@ -0,0 +1,6 @@ +.git +__pycache__ +*.pyc +.venv +sample_inventory.xlsx +*.md diff --git a/ai-service/Dockerfile b/ai-service/Dockerfile new file mode 100644 index 0000000..5a6d599 --- /dev/null +++ b/ai-service/Dockerfile @@ -0,0 +1,11 @@ +ARG BASE_REGISTRY=docker.m.daocloud.io/library +FROM ${BASE_REGISTRY}/python:3.12-slim-bookworm +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir \ + -i https://pypi.tuna.tsinghua.edu.cn/simple \ + --trusted-host pypi.tuna.tsinghua.edu.cn \ + -r requirements.txt +COPY . . +EXPOSE 8001 +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8001"] diff --git a/ai-service/app.py b/ai-service/app.py new file mode 100644 index 0000000..71358bc --- /dev/null +++ b/ai-service/app.py @@ -0,0 +1,1807 @@ +""" +AI 蓝图生成服务:描述 + Excel(+可选图片) → AppBlueprint draft +无 LLM Key 时走启发式规则,保证可离线测试。 +领域无关:不写死库存/沉降等业务字段,由表头规范化 + 类型推断 + 蓝图 widgets 驱动。 +""" + +from __future__ import annotations + +import asyncio +import hashlib +import io +import json +import re +import unicodedata +from pathlib import Path +from typing import Any + +from fastapi import FastAPI, File, Form, UploadFile +from fastapi.middleware.cors import CORSMiddleware +from openpyxl import load_workbook + +from generation_rules import ( + DEFAULT_ACTION_LABELS, + compose_effective_prompt, + parse_action_bar, + wants_screenshot_layout, +) +from html_layout import layout_summary_text, merge_layout_into_ui_hints, parse_layout_file +from llm import enhance_blueprint_with_llm, list_providers, load_ai_config, understand_images +from fidelity_loop import run_fidelity_loop +from generate_log import GenerateTrace, ensure_logging +from demo_fixtures import file_response, fixtures_manifest, read_prompt_text +from preview_store import create_preview, get_preview +import os + +ensure_logging() + +# 优先加载仓库根 .env,其次 ai-service/.env(容错编码,避免整服务起不来) +_root = Path(__file__).resolve().parents[1] + + +def _safe_load_dotenv(path: Path, *, override: bool = False) -> None: + if not path.is_file(): + return + raw = path.read_bytes() + text: str | None = None + for enc in ("utf-8-sig", "utf-8", "gbk", "cp936", "latin-1"): + try: + text = raw.decode(enc) + break + except UnicodeDecodeError: + continue + if text is None: + print(f"WARN: cannot decode env file {path}, skip") + return + for line in text.splitlines(): + s = line.strip() + if not s or s.startswith("#") or "=" not in s: + continue + key, _, val = s.partition("=") + key = key.strip() + val = val.strip().strip('"').strip("'") + if not key: + continue + if override or key not in os.environ: + os.environ[key] = val + # 若曾用非 utf-8 读入,尝试写回 utf-8,避免下次再炸 + try: + if raw[:3] != b"\xef\xbb\xbf": + # 仅当原文件不是合法 utf-8 时重写 + try: + raw.decode("utf-8") + except UnicodeDecodeError: + path.write_text(text if text.endswith("\n") else text + "\n", encoding="utf-8") + print(f"WARN: rewrote {path} as utf-8") + except OSError: + pass + + +_safe_load_dotenv(_root / ".env") +_safe_load_dotenv(Path(__file__).resolve().parent / ".env", override=True) + +app = FastAPI(title="aijianzhan-ai", version="0.1.0") +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +_SYSTEM_FIELDS = {"id", "tenant_id", "created_by", "created_at", "updated_at"} + + +def slugify(text: str, fallback: str = "app") -> str: + """通用标识符:驼峰/点号 → snake_case;纯中文用稳定短哈希,无业务词表。""" + original = text or "" + text = unicodedata.normalize("NFKC", original) + text = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", text) + text = text.lower() + text = text.replace("-", "_").replace(".", "_") + text = re.sub(r"[^a-z0-9_]+", "_", text) + text = re.sub(r"_+", "_", text).strip("_") + if not text or not re.match(r"^[a-z]", text): + # 列名用 col_N;应用 slug 用短哈希,避免依赖中文词表 + if re.match(r"^col_\d+$", fallback): + text = fallback + else: + h = hashlib.sha1(original.encode("utf-8")).hexdigest()[:8] + text = f"{fallback}_{h}" + return text[:48] + + +def to_field_name(header: str, idx: int) -> str: + raw = slugify(header, fallback=f"col_{idx}") + return raw if re.match(r"^[a-z]", raw) else f"col_{idx}" + + +def infer_app_name(prompt: str) -> str: + """从需求描述提取应用显示名。""" + p = prompt or "" + for pat in ( + r"应用名称[推荐为是::\s]*[「『\"]?([^」』\"\n,。;]{2,32})", + r"[「『](.+?)[」』]", + r"生成[一个]?(?:业务)?应用[::\s]*([^\n,。;]{2,32})", + ): + m = re.search(pat, p) + if m: + name = m.group(1).strip() + if name and name not in {"Excel", "xlsx", "CSV"}: + return name[:32] + for line in p.splitlines(): + line = line.strip("# ").strip() + if 4 <= len(line) <= 32 and not line.startswith("http"): + return line[:32] + return "智能应用" + + +def infer_item_label(prompt: str, sheet_label: str) -> str: + """业务对象称呼:提示词显式指定 > Excel 表名 >「记录」。""" + p = prompt or "" + m = re.search(r"(?:对象|实体|记录)名[称]?[推荐为是::\s]*([^\n,。;]{1,16})", p) + if m: + return m.group(1).strip()[:16] + if sheet_label and re.search(r"[\u4e00-\u9fff]", sheet_label): + lab = re.sub(r"(表|列表|台账|数据|信息)$", "", sheet_label).strip() + if lab and lab.lower() not in {"sheet1", "sheet"}: + return lab[:16] + if sheet_label and sheet_label.lower() not in {"sheet1", "sheet", "数据"}: + return sheet_label[:16] + return "记录" + + +def parse_ui_hints(prompt: str, image_count: int, html_count: int = 0) -> dict[str, Any]: + """从用户需求提取展示元信息;截图/HTML 忠实策略由 generation_rules 决定。""" + p = prompt or "" + hints: dict[str, Any] = {"ui": {}} + if wants_screenshot_layout(p, image_count, html_count): + hints["ui_preset"] = "screenshot_faithful" + + m = re.search(r"平台抬头[::]\s*(.+)", p) + if m: + hints["ui"]["platform_title"] = m.group(1).strip().splitlines()[0][:64] + m = re.search(r"系统名称[::]\s*(.+)", p) + if m: + hints["app_name"] = m.group(1).strip().splitlines()[0][:64] + m = re.search(r"应用名称推荐[::]\s*(.+)", p) + if m and not hints.get("app_name"): + hints["app_name"] = m.group(1).strip().splitlines()[0][:64] + m = re.search(r"推荐 slug[::]\s*`?([a-z][a-z0-9_]{1,47})`?", p) + if m: + hints["slug"] = m.group(1) + m = re.search(r"工程上下文(?:示例)?[::]\s*(.+)", p) + if m: + ctx = m.group(1).strip().splitlines()[0] + ctx = re.split(r"[((]", ctx, 1)[0].strip() + hints["project_context"] = ctx[:120] + # 常见「XX平台」抬头 + if "platform_title" not in hints["ui"]: + m = re.search(r"([^\n]{2,20}管理平台)", p) + if m: + hints["ui"]["platform_title"] = m.group(1).strip() + + # 仅当提示词明文写出时才采纳(不写死行业) + if "点击" in p and "查看" in p: + m = re.search(r"(点击[^。\n]{2,40})", p) + if m: + hints["ui"]["filter_hint"] = m.group(1).strip() + + # 单选选项:全部 / A / B / C 或 **A / B / C** + m = re.search(r"(?:全部\s*[//]\s*)([^。\n]{2,40})", p) + if m and ("单选" in p or "筛选" in p or "类型" in p): + opts = [x.strip().strip("*` ") for x in re.split(r"[//、,,]", m.group(1)) if x.strip()] + opts = [o for o in opts if o and o != "全部" and len(o) <= 12][:8] + if len(opts) >= 2: + hints["ui"]["section_options"] = opts + hints["filter_style"] = "section_radios" + + # 页面标题:仅匹配「标题:xxx」或明确业务页名行 + for key, pat in ( + ("list_title", r"(?:列表标题|列表页)[::]\s*(.+)"), + ("create_title", r"(?:新增页|表单标题|创建页)[::]\s*(.+)"), + ("dash_title", r"(?:看板标题|看板页)[::]\s*(.+)"), + ): + m = re.search(pat, p) + if m: + hints[key] = m.group(1).strip().splitlines()[0][:64] + + action_bar = parse_action_bar(p) + if action_bar: + hints["actions"] = action_bar["actions"] + hints["action_labels"] = action_bar["action_labels"] + + return hints + + +def wants_dashboard(prompt: str, image_count: int, html_count: int = 0) -> bool: + p = prompt or "" + keys = ("图表", "看板", "统计", "概览", "dashboard", "折线", "曲线", "纵断", "KPI", "可视化") + if any(k.lower() in p.lower() if k.isascii() else k in p for k in keys): + return True + return image_count > 0 or html_count > 0 + + +def wants_line_chart(prompt: str) -> bool: + p = prompt or "" + return any(k in p for k in ("折线", "曲线", "纵断", "趋势", "line chart", "时序")) + + +def build_dashboard_widgets( + fields: list[dict[str, Any]], + filters: list[str], + list_cols: list[str], + entity_name: str, + item_label: str, + prompt: str, +) -> list[dict[str, Any]]: + """按字段类型与提示词组装 widgets;标题优先来自提示词/HTML,否则用通用名。""" + usable = [f for f in fields if f["name"] not in _SYSTEM_FIELDS] + names = {f["name"] for f in usable} + numeric = [f for f in usable if f["type"] in {"int", "bigint", "decimal"}] + categorical = [ + f["name"] + for f in usable + if f.get("enum_values") or f["type"] == "enum" or f["name"] in filters + ] + x_candidates = [f["name"] for f in usable if f["type"] in {"string", "enum", "date", "datetime"}] + p = prompt or "" + + def pick_title(*cands: str, default: str) -> str: + for c in cands: + if c and c in p: + # 取含该关键词的短句 + m = re.search(rf"([^\n::]{{0,10}}{re.escape(c)}[^\n::]{{0,16}})", p) + if m: + t = m.group(1).strip(" -—||") + if 2 <= len(t) <= 32: + return t + return c + return default + + # 多数值 + 类目/编码轴 → 堆叠看板(图 + 可选状态条 + 表),不绑定行业 + stacked = ( + len(numeric) >= 2 + and (x_candidates or filters) + and ( + wants_line_chart(p) + or "看板" in p + or "示意图" in p + or "监督" in p + or "status_strip" in p + or bool({"chainage", "dkilo"} & names) + ) + ) + + if stacked: + x_field = next((n for n in ("chainage", "dkilo", "code", "name") if n in names), None) + if not x_field: + x_field = x_candidates[0] if x_candidates else (list_cols[0] if list_cols else "id") + metrics = [f["name"] for f in numeric[:4]] + # 优先常见度量名(若存在) + preferred = [n for n in ( + "value", "cjl_value", "amount", "qty", "score", "count", + "design_value", "design_settlement_mm", "cum_value", "cum_settlement_mm", + "pred_value", "pred_settlement_mm", + ) if n in names] + if preferred: + # 主度量优先观测值,设计/累积作辅系列 + primary = next( + (n for n in ("cjl_value", "value", "cum_value", "cum_settlement_mm") if n in preferred), + preferred[0], + ) + rest = [n for n in preferred if n != primary][:3] + metrics = [primary] + rest + widgets: list[dict[str, Any]] = [ + { + "type": "line_chart", + "title": pick_title("示意图", "趋势图", "曲线", default="趋势图"), + "x_field": x_field, + "group_by": x_field, + "metrics": metrics, + "metric": metrics[0] if metrics else "count", + "y_unit": "mm" if any("mm" in n or n.endswith("_value") for n in metrics) else "", + "entity": entity_name, + }, + ] + if "before_day" in names and "next_day" in names: + widgets.append( + { + "type": "status_strip", + "title": pick_title("监督", "状态条", "进度", default="状态"), + "label_field": x_field, + "value_field": "before_day", + "secondary_field": "next_day", + "warn_field": next((n for n in ("overdue_days", "exceed_days", "warn_flag") if n in names), ""), + "variant": "stacked_days", + "cycle_days": 30, + "entity": entity_name, + } + ) + elif "supervise_days" in names or "status_days" in names or "days" in names: + value_f = next((n for n in ("supervise_days", "status_days", "days") if n in names), "supervise_days") + widgets.append( + { + "type": "status_strip", + "title": pick_title("状态条", "进度", "监督", default="状态"), + "label_field": x_field, + "value_field": value_f, + "warn_field": next((n for n in ("overdue_days", "exceed_days", "warn_flag") if n in names), ""), + "entity": entity_name, + } + ) + table_cols = list_cols[:6] or [f["name"] for f in usable[:6]] + widgets.append( + { + "type": "table", + "title": pick_title("明细", "列表", "异常", default=f"{item_label}明细"), + "entity": entity_name, + "columns": table_cols, + "filter_field": next((n for n in ("exceed_mm", "status", "flag") if n in names), ""), + "filter_op": "gt" if "exceed_mm" in names else "eq", + "filter_value": 0 if "exceed_mm" in names else "", + } + ) + return widgets + + widgets = [ + {"type": "kpi", "title": f"{item_label}数量", "metric": "count", "entity": entity_name}, + ] + + if wants_line_chart(prompt) or (len(numeric) >= 2 and x_candidates): + x_field = x_candidates[0] if x_candidates else (list_cols[0] if list_cols else "id") + metrics = [f["name"] for f in numeric[:4]] + widgets.append( + { + "type": "line_chart", + "title": "趋势图", + "x_field": x_field, + "group_by": x_field, + "metrics": metrics, + "metric": metrics[0] if metrics else "count", + "entity": entity_name, + } + ) + + g = categorical[0] if categorical else (filters[0] if filters else None) + if g: + chart_type = "bar_chart" if wants_line_chart(prompt) else "pie_chart" + widgets.append( + { + "type": chart_type, + "title": "分布", + "metric": "count", + "group_by": g, + "entity": entity_name, + } + ) + elif not any(w["type"] == "line_chart" for w in widgets): + g2 = list_cols[0] if list_cols else "id" + widgets.append( + { + "type": "pie_chart", + "title": "分布", + "metric": "count", + "group_by": g2, + "entity": entity_name, + } + ) + return widgets + + +_ALLOWED_TYPES = { + "string", "text", "int", "bigint", "decimal", "boolean", + "date", "datetime", "enum", "json", "file_ref", +} +_TYPE_ALIAS = { + "float": "decimal", "float32": "decimal", "float64": "decimal", + "double": "decimal", "number": "decimal", "numeric": "decimal", + "integer": "int", "int32": "int", "int64": "bigint", "long": "bigint", + "bool": "boolean", "varchar": "string", "str": "string", + "timestamp": "datetime", "timestamptz": "datetime", +} + + +def looks_like_faithful_dashboard(draft: dict[str, Any]) -> bool: + """看板是否应按截图壳渲染:已标 preset,或为「图+条/表」分区结构(不限行业)。""" + for p in draft.get("pages") or []: + if not isinstance(p, dict) or p.get("type") != "dashboard": + continue + lay = p.get("layout") or {} + if not isinstance(lay, dict): + continue + if lay.get("preset") in {"screenshot_faithful", "ops_monitor"}: + return True + widgets = lay.get("widgets") or [] + types = {str(w.get("type") or "") for w in widgets if isinstance(w, dict)} + # 截图类看板常见:主图 +(状态条或明细表),而非仅 KPI + if "line_chart" in types and ("status_strip" in types or "table" in types): + return True + if "bar_chart" in types and "table" in types and "kpi" in types and len(types) >= 3: + return True + return False + + +def apply_screenshot_faithful(draft: dict[str, Any], *, force: bool = False) -> dict[str, Any]: + """通用:写入 ui_preset / layout.preset,并补齐主图/监督条默认,避免主图区空白。""" + if not force and not looks_like_faithful_dashboard(draft): + return draft + meta = draft.setdefault("meta", {}) + meta["ui_preset"] = "screenshot_faithful" + ui = meta.get("ui") if isinstance(meta.get("ui"), dict) else {} + meta["ui"] = ui + + # 从主实体字段推断主图取值/颜色/设计曲线(领域无关:按常见后缀与颜色字段) + fields: list[dict[str, Any]] = [] + for ent in draft.get("entities") or []: + if isinstance(ent, dict) and ent.get("fields"): + fields = list(ent["fields"]) + break + names = {str(f.get("name") or "") for f in fields if isinstance(f, dict)} + + def first(*cands: str) -> str: + for c in cands: + if c in names: + return c + return "" + + if not ui.get("value_field"): + vf = first("cjl_value", "value", "cum_value", "cum_settlement_mm", "amount", "qty", "score") + if vf: + ui["value_field"] = vf + if not ui.get("color_field"): + cf = first("cjl_color", "color", "mark_color") + if cf: + ui["color_field"] = cf + if not ui.get("design_field"): + df = first("design_value", "design_settlement_mm", "target", "design") + if df: + ui["design_field"] = df + if not ui.get("category_field"): + cat = first("section_type", "category", "type", "kind") + if cat: + ui["category_field"] = cat + # 截图还原默认着色测点图(有取值字段时);显式 line/multi_series 除外 + style = str(ui.get("chart_style") or "").lower() + if style not in {"line", "multi_series", "line_chart", "section_marks", "colored_marks"}: + if ui.get("value_field") or first("cjl_value", "value"): + ui["chart_style"] = "section_marks" + if not ui.get("y_unit") and ( + ui.get("value_field", "").endswith("_mm") + or "settlement" in ui.get("value_field", "") + or ui.get("value_field") in {"cjl_value", "cum_value", "design_value"} + ): + ui["y_unit"] = "mm" + if not ui.get("y_axis_label") and ui.get("y_unit") == "mm": + ui["y_axis_label"] = "沉降量" + if "invert_y" not in ui and ui.get("chart_style") in {"section_marks", "colored_marks"}: + ui["invert_y"] = True + if not ui.get("legend_items"): + # 用单选选项生成占位图例色,避免图例全空 + radios = ui.get("filter_radios") or ui.get("section_options") or [] + palette = ["#8000FF", "#214080", "#868e96", "#0f766e", "#e8590c"] + items = [] + for i, lab in enumerate(radios): + if not lab or lab in {"全部", "所有", "all", "All"}: + continue + items.append({"label": f"{lab}累计", "color": palette[i % len(palette)]}) + if ui.get("design_field"): + items.append({"label": "设计曲线", "color": ui.get("design_series_color") or "#e8590c"}) + if items: + ui["legend_items"] = items + + for p in draft.get("pages") or []: + if not isinstance(p, dict): + continue + if p.get("type") == "dashboard": + lay = p.setdefault("layout", {}) + if isinstance(lay, dict): + lay["preset"] = "screenshot_faithful" + # 截图主区通常无顶部 KPI 卡片条;有图/条/表分区时去掉 kpi 以免破坏还原 + widgets = [w for w in (lay.get("widgets") or []) if isinstance(w, dict)] + types = {w.get("type") for w in widgets} + if ("line_chart" in types or "bar_chart" in types) and ( + "status_strip" in types or "table" in types + ): + widgets = [w for w in widgets if w.get("type") != "kpi"] + # 补齐主图/监督条字段,避免渲染侧整段跳过 + x_fallback = first("chainage", "dkilo", "code", "name") or ( + next( + ( + str(f.get("name")) + for f in fields + if isinstance(f, dict) + and f.get("type") in {"string", "enum", "date"} + and f.get("name") not in _SYSTEM_FIELDS + ), + "id", + ) + ) + for w in widgets: + if w.get("type") in {"line_chart", "bar_chart"}: + w.setdefault("x_field", x_fallback) + w.setdefault("group_by", w.get("x_field") or x_fallback) + if not w.get("metrics") and not w.get("metric"): + m0 = ui.get("value_field") or first( + "cjl_value", "value", "cum_value", "cum_settlement_mm" + ) + if m0: + w["metric"] = m0 + w["metrics"] = [m0] + if ui.get("design_field"): + w["metrics"] = [m0, ui["design_field"]] + if ui.get("y_unit"): + w.setdefault("y_unit", ui["y_unit"]) + if w.get("type") == "status_strip": + w.setdefault("label_field", x_fallback) + if "before_day" in names and "next_day" in names: + w.setdefault("value_field", "before_day") + w.setdefault("secondary_field", "next_day") + w.setdefault("variant", "stacked_days") + w.setdefault("cycle_days", 30) + elif not w.get("value_field"): + w["value_field"] = first( + "supervise_days", "status_days", "before_day", "days" + ) or "supervise_days" + lay["widgets"] = widgets + title = str(p.get("title") or "") + # 去掉套话「概览」,保留业务名;不改写成固定行业标题 + if title.endswith("概览") and len(title) > 2: + p["title"] = title[: -len("概览")].rstrip() or title + if not str(p["title"]).endswith("看板"): + p["title"] = f"{p['title']}看板" if p["title"] else "看板" + if p.get("type") == "list": + lay = p.setdefault("layout", {}) + if isinstance(lay, dict) and not lay.get("filter_style"): + if ui.get("filter_style") or ui.get("section_options") or ui.get("filter_radios"): + lay["filter_style"] = ui.get("filter_style") or "section_radios" + return draft + + +def harden_draft(draft: dict[str, Any], fallback: dict[str, Any]) -> dict[str, Any]: + """发布前收口:非法 type/空结构回退,避免 LLM 润色后无法 publish。""" + if not isinstance(draft, dict) or not draft.get("entities"): + return fallback + draft.setdefault("version", "1.0") + draft.setdefault("storage", fallback.get("storage") or {"mode": "schema_per_app", "engine": "postgres"}) + draft.setdefault("security", fallback.get("security") or {"visibility": "private", "roles": [], "row_policies": []}) + draft.setdefault("apis", fallback.get("apis")) + draft.setdefault("pages", fallback.get("pages") or []) + meta = draft.setdefault("meta", {}) + if not meta.get("slug"): + meta["slug"] = (fallback.get("meta") or {}).get("slug") or "app" + meta["slug"] = slugify(str(meta["slug"]), fallback="app") + if not meta.get("name"): + meta["name"] = (fallback.get("meta") or {}).get("name") or meta["slug"] + entity_map: dict[str, str] = {} + for ent in draft["entities"]: + old = str(ent.get("name") or "") + if not ent.get("name"): + ent["name"] = "record" + ent["name"] = slugify(str(ent["name"]), fallback="record") + ent["table"] = slugify(str(ent.get("table") or ent["name"]), fallback=ent["name"]) + if old: + entity_map[old] = ent["name"] + entity_map[slugify(old, fallback=ent["name"])] = ent["name"] + ent.setdefault("primary_key", "id") + fields = ent.get("fields") or [] + if not fields: + return fallback + names = set() + for i, f in enumerate(fields): + fname = slugify(str(f.get("name") or f"col_{i}"), fallback=f"col_{i}") + if fname in names: + fname = f"{fname}_{i}" + names.add(fname) + f["name"] = fname + t = str(f.get("type") or "string").lower().strip() + t = _TYPE_ALIAS.get(t, t) + if t not in _ALLOWED_TYPES: + t = "string" + f["type"] = t + if not any(f.get("name") == ent["primary_key"] for f in fields): + fields.insert(0, { + "name": ent["primary_key"], + "label": "ID", + "type": "bigint", + "nullable": False, + "ui": {"widget": "hidden", "listable": False}, + }) + ent["fields"] = fields + + def resolve_entity(ref: Any) -> str: + key = str(ref or "") + if key in entity_map: + return entity_map[key] + sn = slugify(key, fallback="") + if sn in entity_map: + return entity_map[sn] + return sn or key + + apis = draft.get("apis") or {} + for r in apis.get("resources") or []: + if isinstance(r, dict): + r["entity"] = resolve_entity(r.get("entity")) + path = str(r.get("path") or "").lstrip("/") + r["path"] = "/" + (slugify(path, fallback="items") if path else "items") + for p in draft.get("pages") or []: + if isinstance(p, dict) and p.get("entity"): + p["entity"] = resolve_entity(p.get("entity")) + for rp in (draft.get("security") or {}).get("row_policies") or []: + if isinstance(rp, dict) and rp.get("entity"): + rp["entity"] = resolve_entity(rp.get("entity")) + + # 截图忠实:LLM 不得抹掉;有图或分区结构时强制恢复(不限行业) + fb_meta = fallback.get("meta") or {} + fb_src = fb_meta.get("source") or {} + force_faithful = ( + fb_meta.get("ui_preset") == "screenshot_faithful" + or fb_src.get("screenshot_faithful") is True + or bool(fb_src.get("image_refs")) + or looks_like_faithful_dashboard(fallback) + or looks_like_faithful_dashboard(draft) + ) + if force_faithful: + if fb_meta.get("platform_title"): + meta["platform_title"] = fb_meta["platform_title"] + if fb_meta.get("ui"): + meta["ui"] = {**(fb_meta.get("ui") or {}), **(meta.get("ui") or {})} + if fb_meta.get("project_context") and not meta.get("project_context"): + meta["project_context"] = fb_meta["project_context"] + # 保留用户/启发式名称,禁止被润色成套话 + fb_name = str(fb_meta.get("name") or "") + cur_name = str(meta.get("name") or "") + if fb_name and (not cur_name or "数据管理" in cur_name or cur_name.endswith("概览")): + meta["name"] = fb_name + apply_screenshot_faithful(draft, force=True) + + # 功能按键文案:LLM 不得抹掉用户/截图还原的 labels + for p, fp in zip(draft.get("pages") or [], fallback.get("pages") or []): + if not isinstance(p, dict) or not isinstance(fp, dict): + continue + flay = fp.get("layout") or {} + if not isinstance(flay, dict): + continue + lay = p.setdefault("layout", {}) + if not isinstance(lay, dict): + continue + if flay.get("action_labels"): + merged = dict(flay.get("action_labels") or {}) + merged.update(lay.get("action_labels") or {}) + for k, v in (flay.get("action_labels") or {}).items(): + merged[k] = v + lay["action_labels"] = merged + if flay.get("actions") and ( + not lay.get("actions") or len(lay.get("actions") or []) < 2 + ): + lay["actions"] = list(flay["actions"]) + if flay.get("preset") == "screenshot_faithful": + lay["preset"] = "screenshot_faithful" + if flay.get("filter_style") and not lay.get("filter_style"): + lay["filter_style"] = flay["filter_style"] + return draft + + +def infer_type(values: list[Any]) -> tuple[str, list[str] | None, float]: + samples = [v for v in values if v is not None and str(v).strip() != ""] + if not samples: + return "string", None, 0.4 + strs = [str(v).strip() for v in samples] + uniq = sorted(set(strs)) + # 高基数字段不当 enum,避免导入新值失败 + if len(uniq) <= 8 and len(uniq) <= max(2, int(len(strs) * 0.25)) and len(strs) >= 4: + if all(len(u) <= 32 for u in uniq): + return "enum", uniq[:50], 0.85 + int_ok = True + dec_ok = True + for s in strs: + try: + if "." in s or "e" in s.lower(): + float(s) + int_ok = False + else: + int(s) + except ValueError: + int_ok = False + try: + float(s) + except ValueError: + dec_ok = False + break + if int_ok: + return "int", None, 0.9 + if dec_ok: + return "decimal", None, 0.88 + if all(re.match(r"^\d{4}-\d{2}-\d{2}", s) for s in strs): + return "datetime", None, 0.8 + avg_len = sum(len(s) for s in strs) / len(strs) + if avg_len > 80: + return "text", None, 0.7 + return "string", None, 0.75 + + +def parse_excel(content: bytes) -> dict[str, Any]: + wb = load_workbook(io.BytesIO(content), read_only=True, data_only=True) + # 默认首表(不按行业表名挑选) + ws = wb[wb.sheetnames[0]] if wb.sheetnames else wb.active + rows = list(ws.iter_rows(values_only=True)) + if not rows: + return {"sheets": []} + headers = [str(h).strip() if h is not None else f"col_{i}" for i, h in enumerate(rows[0])] + data_rows = rows[1:2001] # 最多采 2000 行做类型推断 / 蓝图 + cols: dict[str, list[Any]] = {h: [] for h in headers} + sample_rows: list[list[Any]] = [] + for r in data_rows: + if r is None or all(c is None or str(c).strip() == "" for c in r): + continue + vals = [] + for i, h in enumerate(headers): + v = r[i] if i < len(r) else None + cols[h].append(v) + vals.append(v) + sample_rows.append(vals) + inferred = {} + for h in headers: + t, enum_vals, _ = infer_type(cols[h]) + inferred[h] = {"type": t, "enum_values": enum_vals} + return { + "sheets": [ + { + "name": ws.title, + "headers": headers, + "sample_rows": sample_rows[:5], + "row_dicts": [ + {headers[i]: (r[i] if i < len(r) else None) for i in range(len(headers))} + for r in sample_rows[:500] + ], + "inferred": inferred, + "columns": cols, + "row_count": len(sample_rows), + } + ] + } + + +def _chart_rows_from_json(data: dict[str, Any]) -> list[dict[str, Any]]: + """展开并行数组图表 JSON(mcljChartData / cljdChartData)→ 行字典。不写死行业文案。""" + mclj = data.get("mcljChartData") or {} + cljd = data.get("cljdChartData") or {} + dkilos = list(mclj.get("dkilo") or []) + cjls = list(mclj.get("cjl") or []) + sjcjls = list(mclj.get("sjcjl") or []) + workinfos = list(cljd.get("workinfo_kilo") or []) + befores = list(cljd.get("beforeDay") or cljd.get("before_day") or []) + nexts = list(cljd.get("nextDay") or cljd.get("next_day") or []) + freqs = list(cljd.get("frequency") or []) + # 颜色 → 分组N(按出现顺序);不臆造路基/桥梁等业务词 + color_to_group: dict[str, str] = {} + rows: list[dict[str, Any]] = [] + for i, dkilo in enumerate(dkilos): + parts = str(dkilo).split("_") + point_code = parts[0] if parts else str(dkilo) + mileage_m = int(parts[1]) if len(parts) >= 2 and parts[1].isdigit() else None + chainage = "" + if mileage_m is not None: + km, rem = divmod(mileage_m, 1000) + chainage = f"DK{km}+{rem:03d}" + cjl = cjls[i] if i < len(cjls) else {} + if not isinstance(cjl, dict): + cjl = {"value": cjl, "color": ""} + try: + cjl_value = float(str(cjl.get("value") or 0).replace(",", "")) + except ValueError: + cjl_value = 0.0 + color = str(cjl.get("color") or "").strip().lower() + try: + curve = abs(float(sjcjls[i])) if i < len(sjcjls) else 0.0 + except (TypeError, ValueError): + curve = 0.0 + design = 3.0 if curve >= 10 else (curve or 3.0) + cum_abs = abs(cjl_value) + exceed = round(max(0.0, cum_abs - design), 2) + if color and color not in color_to_group: + color_to_group[color] = f"分组{len(color_to_group) + 1}" + section = color_to_group.get(color) or f"分组{(i % 4) + 1}" + before = float(befores[i]) if i < len(befores) else 0.0 + nxt = float(nexts[i]) if i < len(nexts) else 0.0 + freq = float(freqs[i]) if i < len(freqs) else 0.0 + rows.append( + { + "dkilo": str(dkilo), + "chainage": chainage or str(dkilo), + "point_code": point_code, + "section_type": section, + "worksite": f"分组{i // 40 + 1}", + "value": cjl_value, + "color": color, + "cjl_value": cjl_value, + "cjl_color": color, + "design_value": design, + "cum_value": cjl_value, + "pred_value": round(cjl_value * 0.9, 2), + "exceed_mm": exceed, + "exceed_days": int(round(exceed)) if exceed > 0 else 0, + "supervise_days": int(round(before)) if before > 1 else 28, + "overdue_days": max(0, int(round(nxt / 30)) if nxt > 50 else 0), + "before_day": before, + "next_day": nxt, + "frequency": freq, + "workinfo_kilo": workinfos[i] if i < len(workinfos) else "", + "status": "异常" if exceed > 0 else "正常", + } + ) + return rows + + +def _flatten_json_object(obj: dict[str, Any], prefix: str = "") -> dict[str, Any]: + out: dict[str, Any] = {} + for k, v in obj.items(): + key = slugify(f"{prefix}_{k}" if prefix else str(k), fallback="field") + if isinstance(v, dict) and "value" in v: + out[key] = v.get("value") + if "color" in v: + out[f"{key}_color"] = v.get("color") + elif isinstance(v, dict): + out.update(_flatten_json_object(v, key)) + elif isinstance(v, list): + out[key] = json.dumps(v, ensure_ascii=False) + else: + out[key] = v + return out + + +def _rows_to_sheet(name: str, row_dicts: list[dict[str, Any]], *, link_field: str | None = None, parent: str | None = None) -> dict[str, Any]: + if not row_dicts: + return {"name": name, "headers": [], "sample_rows": [], "inferred": {}, "columns": {}, "row_count": 0} + headers = list(row_dicts[0].keys()) + for r in row_dicts[1:]: + for k in r: + if k not in headers: + headers.append(k) + # 用全量做类型推断(上限 2000) + use = row_dicts[:2000] + cols: dict[str, list[Any]] = {h: [r.get(h) for r in use] for h in headers} + inferred = {} + for h in headers: + t, enum_vals, _ = infer_type(cols[h]) + inferred[h] = {"type": t, "enum_values": enum_vals} + sheet: dict[str, Any] = { + "name": name, + "headers": headers, + "sample_rows": [[r.get(h) for h in headers] for r in use[:5]], + "row_dicts": use[:500], + "inferred": inferred, + "columns": cols, + "source": "json", + "row_count": len(row_dicts), + } + if link_field: + sheet["link_field"] = link_field + if parent: + sheet["parent_entity"] = parent + return sheet + + +def parse_json_table(content: bytes) -> dict[str, Any]: + """JSON → 一张或多张关联表(sheets),按结构拆实体。""" + text = content.decode("utf-8-sig") + data = json.loads(text) + sheets: list[dict[str, Any]] = [] + + if isinstance(data, dict) and ("mcljChartData" in data or "cljdChartData" in data): + points = _chart_rows_from_json(data) + primary = "chart_points" + sheets.append(_rows_to_sheet(primary, points)) + # 并行状态数组拆成关联表(按 dkilo 关联) + cljd = data.get("cljdChartData") or {} + mclj = data.get("mcljChartData") or {} + dkilos = list(mclj.get("dkilo") or []) + workinfos = list(cljd.get("workinfo_kilo") or []) + befores = list(cljd.get("beforeDay") or cljd.get("before_day") or []) + nexts = list(cljd.get("nextDay") or cljd.get("next_day") or []) + freqs = list(cljd.get("frequency") or []) + superv = [] + for i, dk in enumerate(dkilos): + superv.append( + { + "dkilo": str(dk), + "workinfo_kilo": workinfos[i] if i < len(workinfos) else "", + "before_day": float(befores[i]) if i < len(befores) else 0, + "next_day": float(nexts[i]) if i < len(nexts) else 0, + "frequency": float(freqs[i]) if i < len(freqs) else 0, + "supervise_days": int(round(float(befores[i]))) if i < len(befores) and float(befores[i]) > 1 else 28, + } + ) + if superv: + sheets.append( + _rows_to_sheet( + "status_series", + superv, + link_field="dkilo", + parent=primary, + ) + ) + exceed = [ + { + "dkilo": r["dkilo"], + "worksite": r.get("worksite"), + "point_code": r.get("point_code"), + "design_value": r.get("design_value"), + "cum_value": r.get("cum_value"), + "exceed_mm": r.get("exceed_mm"), + "exceed_days": r.get("exceed_days"), + "status": r.get("status"), + } + for r in points + if float(r.get("exceed_mm") or 0) > 0 + ] + if exceed: + sheets.append( + _rows_to_sheet( + "alert_points", + exceed, + link_field="dkilo", + parent=primary, + ) + ) + return {"sheets": sheets, "relations": [ + {"from": "status_series", "to": primary, "on": "dkilo"}, + {"from": "alert_points", "to": primary, "on": "dkilo"}, + ]} + + if isinstance(data, list) and data and all(isinstance(x, dict) for x in data): + return {"sheets": [_rows_to_sheet("records", [_flatten_json_object(x) for x in data])]} + + if isinstance(data, dict): + # 多个 list[object] → 多实体;尝试 *_id 关联 + list_keys = [ + k for k, v in data.items() + if isinstance(v, list) and v and isinstance(v[0], dict) + ] + if len(list_keys) >= 1: + for k in list_keys: + rows = [_flatten_json_object(x) for x in data[k]] + sheets.append(_rows_to_sheet(slugify(k, fallback="records"), rows)) + relations = [] + names = [s["name"] for s in sheets] + for s in sheets: + for h in s.get("headers") or []: + if h.endswith("_id") or h.endswith("id") and h != "id": + base = h[:-3] if h.endswith("_id") else h + # 匹配其他实体名 + for other in names: + if other != s["name"] and (base in other or other.startswith(base)): + relations.append({"from": s["name"], "to": other, "on": h}) + s["link_field"] = h + s["parent_entity"] = other + # 同名字段关联 + for other in sheets: + if other["name"] == s["name"]: + continue + common = set(s.get("headers") or []) & set(other.get("headers") or []) + common -= {"id", "created_at", "updated_at", "tenant_id"} + if len(common) == 1: + on = next(iter(common)) + relations.append({"from": s["name"], "to": other["name"], "on": on}) + s.setdefault("link_field", on) + s.setdefault("parent_entity", other["name"]) + return {"sheets": sheets, "relations": relations} + + for key in ("items", "rows", "data", "records", "list"): + if isinstance(data.get(key), list) and data[key] and isinstance(data[key][0], dict): + return { + "sheets": [ + _rows_to_sheet( + slugify(key, fallback="records"), + [_flatten_json_object(x) for x in data[key]], + ) + ] + } + return {"sheets": [_rows_to_sheet("records", [_flatten_json_object(data)])]} + + return {"sheets": []} + + +def parse_tabular_upload(filename: str, content: bytes) -> dict[str, Any]: + name = (filename or "").lower() + # 首页抓包:key/value(菜单 HTML + 图表 JSON + 统计 JSON …) + if name.endswith(".config") or name in {"url.config", "url_config", "capture.config"}: + from url_capture import parse_url_config + + cap = parse_url_config(content) + warnings = list(cap.get("warnings") or []) + chart = cap.get("chart_json") + if not chart: + return { + "sheets": [], + "capture": cap, + "warnings": warnings + ["url.config 中未找到图表 JSON(mcljChartData/cljdChartData)"], + } + raw = json.dumps(chart, ensure_ascii=False).encode("utf-8") + meta = parse_json_table(raw) + meta["capture"] = cap + meta["warnings"] = warnings + meta.setdefault("ui_hints", {}) + if cap.get("nav_items"): + meta["ui_hints"]["nav_items"] = cap["nav_items"] + stats = cap.get("stats_json") or {} + if isinstance(stats, dict) and stats: + meta["ui_hints"]["stats_raw"] = stats + if cap.get("exceed_json") is None and any( + p.get("empty") and "gonghou" in (p.get("action") or "").lower() + for p in (cap.get("pairs") or []) + ): + warnings.append( + "hightchartGonghouCjlCX 返回为空:超限表由主图表数据按 exceed 条件推导(非原接口)" + ) + meta["warnings"] = warnings + return meta + if name.endswith(".json"): + return parse_json_table(content) + if name.endswith(".csv"): + # 简易 CSV:首行表头 + text = content.decode("utf-8-sig") + import csv as _csv + + reader = _csv.DictReader(io.StringIO(text)) + rows = list(reader)[:5000] + if not rows: + return {"sheets": []} + headers = list(rows[0].keys()) + use = rows[:2000] + cols: dict[str, list[Any]] = {h: [r.get(h) for r in use] for h in headers} + inferred = {h: {"type": infer_type(cols[h])[0], "enum_values": infer_type(cols[h])[1]} for h in headers} + return { + "sheets": [ + { + "name": "csv_import", + "headers": headers, + "sample_rows": [[r.get(h) for h in headers] for r in use[:5]], + "inferred": inferred, + "columns": cols, + "source": "csv", + "row_count": len(rows), + } + ] + } + return parse_excel(content) + + + +def _fields_from_sheet(sheet: dict[str, Any]) -> tuple[list[dict[str, Any]], list[str], list[str], list[str], list[float]]: + """从一张 sheet 推断字段定义。""" + headers = sheet.get("headers") or [] + inferred = sheet.get("inferred") or {} + cols = sheet.get("columns") or {} + fields: list[dict[str, Any]] = [ + { + "name": "id", + "label": "ID", + "type": "bigint", + "nullable": False, + "ui": {"widget": "hidden", "listable": False}, + } + ] + filters: list[str] = [] + sorts: list[str] = [] + list_cols: list[str] = [] + confidences: list[float] = [] + for i, h in enumerate(headers): + fname = to_field_name(h, i) + if fname == "id": + fname = f"col_{i}" + info = inferred.get(h, {"type": "string", "enum_values": None}) + ftype = info["type"] + _, _, c = infer_type(cols.get(h, [])) + confidences.append(c) + field: dict[str, Any] = { + "name": fname, + "label": str(h)[:64], + "type": ftype, + "nullable": True, + "max_length": 128 if ftype == "string" else None, + "ui": { + "widget": "select" if ftype == "enum" else "input", + "listable": True, + "filterable": ftype in {"enum", "string"}, + "sortable": ftype in {"int", "decimal", "datetime", "date", "string"}, + "width": "md", + }, + "from_excel": { + "column": h, + "sample_values": [str(x) for x in cols.get(h, [])[:3] if x is not None], + }, + } + if ftype == "string": + uniq = sorted( + {str(v).strip() for v in cols.get(h, []) if v is not None and str(v).strip() != ""} + ) + id_like = any(x in fname for x in ("code", "dkilo", "chainage", "kilo", "id", "name", "color")) + if not id_like and 1 < len(uniq) <= 8 and len(uniq) <= max(2, int(len(cols.get(h, [])) * 0.25)): + ftype = "enum" + field["type"] = "enum" + field["enum_values"] = uniq + field["ui"]["widget"] = "select" + field["ui"]["filterable"] = True + if ftype == "enum" and info.get("enum_values"): + field["enum_values"] = info["enum_values"][:24] + field["nullable"] = True + field["ui"]["widget"] = "select" + if ftype == "decimal": + field["precision"] = 12 + field["scale"] = 2 + field["ui"]["widget"] = "number" + if ftype == "int": + field["ui"]["widget"] = "number" + if ftype == "datetime": + field["ui"]["widget"] = "datetime" + if ftype == "date": + field["ui"]["widget"] = "datepicker" + field = {k: v for k, v in field.items() if v is not None} + fields.append(field) + list_cols.append(fname) + if field["ui"].get("filterable"): + filters.append(fname) + if field["ui"].get("sortable"): + sorts.append(fname) + return fields, filters, sorts, list_cols, confidences + + + +def build_blueprint( + prompt: str, + excel_meta: dict[str, Any] | None, + storage_mode: str, + image_count: int, + html_count: int = 0, +) -> tuple[dict[str, Any], list[str], float]: + warnings: list[str] = [] + confidences: list[float] = [] + + app_name = infer_app_name(prompt or "") + slug = slugify(app_name, fallback="app") + + entities: list[dict[str, Any]] = [] + resources: list[dict[str, Any]] = [] + pages: list[dict[str, Any]] = [] + primary_entity = "record" + primary_fields: list[dict[str, Any]] = [] + primary_filters: list[str] = [] + primary_list_cols: list[str] = [] + label = "数据" + + sheets = (excel_meta or {}).get("sheets") or [] + relations = (excel_meta or {}).get("relations") or [] + + if sheets: + for si, sheet in enumerate(sheets): + ename = slugify(sheet.get("name") or f"record_{si}", fallback=f"record_{si}") + if ename in {"sheet1", "sheet", "sheet_1"}: + ename = "record" if si == 0 else f"record_{si}" + fields, filters, sorts, list_cols, confs = _fields_from_sheet(sheet) + confidences.extend(confs) + ent: dict[str, Any] = { + "name": ename, + "table": ename, + "label": str(sheet.get("name") or ename)[:32], + "primary_key": "id", + "fields": fields, + "indexes": [], + } + if sheet.get("parent_entity") and sheet.get("link_field"): + ent["relation"] = { + "parent": slugify(str(sheet["parent_entity"]), fallback="record"), + "on": to_field_name(str(sheet["link_field"]), 0), + } + entities.append(ent) + path = f"/{ename}" if ename.endswith("s") else f"/{ename}s" + resources.append( + { + "entity": ename, + "path": path, + "operations": ["list", "get", "create", "update", "delete", "import", "export"], + "list": { + "default_page_size": 50, + "max_page_size": 2000, + "allowed_filters": filters[:12], + "allowed_sorts": sorts[:12], + }, + } + ) + if si == 0: + primary_entity = ename + primary_fields = fields + primary_filters = filters + primary_list_cols = list_cols + label = ent["label"] + rc = sheet.get("row_count") + if rc: + warnings.append(f"主表 {ename} 识别到约 {rc} 行,发布后请导入完整 xlsx/json") + else: + pages.append( + { + "id": f"{ename}_list", + "title": f"{ent['label']}", + "route": f"/{ename}", + "type": "list", + "entity": ename, + "layout": { + "columns": list_cols[:8], + "filters": filters[:5], + "actions": ["create", "edit", "delete", "export", "import", "refresh"], + "action_labels": dict(DEFAULT_ACTION_LABELS), + }, + } + ) + if relations: + warnings.append(f"已按 JSON 字段关联生成 {len(relations)} 条表关系") + else: + warnings.append("未上传数据文件,已生成通用文本字段草案") + confidences.append(0.55) + primary_fields = [ + {"name": "id", "label": "ID", "type": "bigint", "nullable": False, "ui": {"widget": "hidden", "listable": False}}, + {"name": "title", "label": "标题", "type": "string", "nullable": False, "max_length": 128, "ui": {"widget": "input", "listable": True, "sortable": True}}, + {"name": "content", "label": "内容", "type": "text", "nullable": True, "ui": {"widget": "textarea", "listable": True}}, + {"name": "status", "label": "状态", "type": "enum", "nullable": True, "enum_values": ["草稿", "已发布"], "ui": {"widget": "select", "listable": True, "filterable": True}}, + ] + primary_filters = ["status"] + primary_list_cols = ["title", "content", "status"] + entities = [{"name": "record", "table": "record", "label": "数据", "primary_key": "id", "fields": primary_fields, "indexes": []}] + resources = [{ + "entity": "record", + "path": "/records", + "operations": ["list", "get", "create", "update", "delete", "import", "export"], + "list": {"default_page_size": 50, "max_page_size": 2000, "allowed_filters": primary_filters, "allowed_sorts": ["title"]}, + }] + + if image_count > 0 and wants_screenshot_layout(prompt or "", image_count, html_count): + warnings.append(f"已收到 {image_count} 张截图:展示页将按截图分区还原") + confidences.append(0.75) + elif image_count > 0: + warnings.append(f"已收到 {image_count} 张截图(用户要求不按截图布局)") + confidences.append(0.7) + if html_count > 0: + warnings.append(f"已收到 {html_count} 个页面 HTML/MHTML:文案与控件优先按页面源码还原") + confidences.append(0.8) + + ui_hints = parse_ui_hints(prompt or "", image_count, html_count) + has_dashboard = ( + wants_dashboard(prompt or "", image_count, html_count) + or image_count > 0 + or html_count > 0 + or bool(ui_hints.get("ui_preset")) + ) + + confidence = sum(confidences) / len(confidences) if confidences else 0.6 + if prompt and excel_meta: + confidence = min(0.95, confidence + 0.05) + + if ui_hints.get("app_name"): + app_name = ui_hints["app_name"] + if ui_hints.get("slug"): + slug = slugify(str(ui_hints["slug"]), fallback=slug) + + item_label = infer_item_label(prompt or "", label) + list_title = ui_hints.get("list_title") or f"{item_label}列表" + create_title = ui_hints.get("create_title") or f"新增{item_label}" + dash_title = ui_hints.get("dash_title") or f"{item_label}看板" + + filter_style = ui_hints.get("filter_style") or "default" + # 有截图或用户要求还原 → 通用截图忠实(不绑定行业默认文案) + faithful = ( + ui_hints.get("ui_preset") == "screenshot_faithful" + or wants_screenshot_layout(prompt or "", image_count, html_count) + ) + layout_preset = "screenshot_faithful" if faithful else "default" + if faithful: + ui_hints.setdefault("ui", {}) + filter_style = ui_hints.get("filter_style") or filter_style + if ui_hints.get("app_name"): + app_name = ui_hints["app_name"] + if ui_hints.get("list_title"): + list_title = ui_hints["list_title"] + if ui_hints.get("create_title"): + create_title = ui_hints["create_title"] + if ui_hints.get("dash_title"): + dash_title = ui_hints["dash_title"] + elif dash_title.endswith("概览"): + dash_title = dash_title.replace("概览", "看板") + + # 从数据字段补齐筛选(不写死行业词:有 enum 才出单选,有 worksite 类字段才出下拉) + ui_block = ui_hints.setdefault("ui", {}) + field_by_name = {f["name"]: f for f in primary_fields} + if not ui_block.get("section_options") and not ui_block.get("filter_radios"): + for cand in ("section_type", "category", "type", "kind", "status"): + f = field_by_name.get(cand) + ev = (f or {}).get("enum_values") or [] + if len(ev) >= 2: + ui_block["section_options"] = list(ev)[:8] + ui_block["filter_radios"] = list(ev)[:8] + ui_block["filter_style"] = "section_radios" + ui_block["radio_field"] = cand + filter_style = "section_radios" + break + if not ui_block.get("select_field") and not ui_block.get("filter_select_field"): + for cand in ("worksite", "site", "store", "warehouse", "dept", "org"): + if cand in field_by_name: + ui_block["select_field"] = cand + break + if ui_hints.get("ui", {}).get("filter_hint"): + pass + elif "点击" in (prompt or "") and "查看" in (prompt or ""): + m = re.search(r"(点击[^。\n]{2,40})", prompt or "") + if m: + ui_block.setdefault("filter_hint", m.group(1).strip()) + + list_actions = ui_hints.get("actions") or ["create", "edit", "delete", "export", "import", "refresh"] + action_labels = dict(DEFAULT_ACTION_LABELS) + if ui_hints.get("action_labels"): + action_labels.update(ui_hints["action_labels"]) + + # 列表列:优先通用 list_cols;若提示词/字段含常用业务列再穿插(不写死行业) + preferred_cols = [c for c in primary_list_cols if c][:8] + preferred_filters = primary_filters[:5] + if ui_hints.get("filter_style") == "section_radios" and "section_type" in primary_filters: + preferred_filters = [f for f in ("section_type", "worksite", "status") if f in primary_filters] or preferred_filters + + main_pages = [ + { + "id": f"{primary_entity}_list", + "title": list_title, + "route": f"/{primary_entity}", + "type": "list", + "entity": primary_entity, + "layout": { + "columns": preferred_cols, + "filters": preferred_filters, + "actions": list_actions, + "action_labels": action_labels, + "filter_style": filter_style, + }, + }, + { + "id": f"{primary_entity}_create", + "title": create_title, + "route": f"/{primary_entity}/create", + "type": "form_create", + "entity": primary_entity, + "layout": { + "form_fields": [f["name"] for f in primary_fields if f["name"] not in _SYSTEM_FIELDS], + "actions": ["create"], + "action_labels": {k: action_labels.get(k, DEFAULT_ACTION_LABELS.get(k, k)) for k in ("create", "save", "cancel")}, + }, + }, + ] + if has_dashboard: + dash_widgets = build_dashboard_widgets( + primary_fields, primary_filters, primary_list_cols, primary_entity, item_label, prompt or "" + ) + if faithful: + # 有截图时去掉顶栏 KPI,避免破坏截图主区结构 + types = {w.get("type") for w in dash_widgets} + if ("line_chart" in types or "bar_chart" in types) and ( + "status_strip" in types or "table" in types + ): + dash_widgets = [w for w in dash_widgets if w.get("type") != "kpi"] + main_pages.append( + { + "id": f"{primary_entity}_dashboard", + "title": dash_title, + "route": f"/{primary_entity}/dashboard", + "type": "dashboard", + "entity": primary_entity, + "layout": { + "preset": layout_preset, + "widgets": dash_widgets, + }, + } + ) + pages = main_pages + pages + + draft = { + "version": "1.0", + "meta": { + "name": app_name, + "slug": slug, + "description": (prompt or "")[:500], + "locale": "zh-CN", + "source": {"prompt": prompt or ""}, + "confidence": round(confidence, 2), + "ui_preset": layout_preset if faithful else (ui_hints.get("ui_preset") or "default"), + "project_context": ui_hints.get("project_context") or "", + "platform_title": (ui_hints.get("ui") or {}).get("platform_title") or "", + "ui": ui_hints.get("ui") or {}, + }, + "storage": {"mode": storage_mode or "schema_per_app", "engine": "postgres"}, + "entities": entities, + "apis": {"base_path": f"/api/v1/apps/{slug}", "resources": resources}, + "pages": pages, + "security": { + "visibility": "private", + "roles": [ + { + "name": "owner", + "permissions": [ + "app.read", "app.write", "app.admin", + "row.create", "row.read", "row.update", "row.delete", "row.export", "row.import", + ], + }, + {"name": "viewer", "permissions": ["app.read", "row.read", "row.export"]}, + ], + "row_policies": [{"entity": e["name"], "rule": "tenant_isolated"} for e in entities], + }, + "seed": {"import_excel": bool(excel_meta), "max_rows": 5000}, + "relations": relations, + } + return draft, warnings, round(confidence, 2) + + +@app.get("/health") +def health(): + return {"ok": True} + + +@app.get("/api/v1/demo/fixtures") +def demo_fixtures(): + """测试素材清单(控制台一键预填用)。""" + return fixtures_manifest() + + +@app.get("/api/v1/demo/prompt") +def demo_prompt(): + try: + text = read_prompt_text() + except Exception as e: # noqa: BLE001 + return {"ok": False, "prompt": "", "error": str(e)} + return {"ok": True, "prompt": text, "slug": fixtures_manifest().get("slug")} + + +@app.get("/api/v1/demo/file/{file_path:path}") +def demo_file(file_path: str): + return file_response(file_path) + + +@app.post("/api/v1/preview") +async def preview_create(payload: dict[str, Any]): + """创建草稿预览(无需登录/发布),返回 preview_id 供 /#/preview/{id} 打开。""" + bp = payload.get("blueprint") + if not isinstance(bp, dict): + return {"ok": False, "error": "blueprint required"} + rows = payload.get("rows") if isinstance(payload.get("rows"), list) else [] + resource = str(payload.get("resource") or "") + pid = create_preview(bp, rows=rows, resource=resource) + return { + "ok": True, + "preview_id": pid, + "path": f"/#/preview/{pid}", + "url_hint": f"WEB_BASE/#/preview/{pid}", + } + + +@app.get("/api/v1/preview/{preview_id}") +def preview_get(preview_id: str): + pack = get_preview(preview_id) + if not pack: + return {"ok": False, "error": "preview not found or expired"} + return { + "ok": True, + "id": pack.get("id"), + "blueprint": pack.get("blueprint"), + "rows": pack.get("rows") or [], + "resource": pack.get("resource") or "records", + } + + +@app.get("/api/v1/llm/providers") +def llm_providers(): + cfg = load_ai_config() + default = ( + (os.getenv("LLM_PROVIDER") or "").strip() + or str(cfg.get("default_provider") or "deepseek") + ) + return {"providers": list_providers(), "default": default} + + +@app.post("/api/v1/apps/generate") +@app.post("/api/v1/apps:generate") # 兼容旧路径,勿再新增第三套 +async def generate( + prompt: str = Form(""), + storage_mode: str = Form("schema_per_app"), + llm_provider: str = Form(""), + llm_model: str = Form(""), + excel: UploadFile | None = File(None), + data_file: UploadFile | None = File(None), # 兼容:json/csv/xlsx + images: list[UploadFile] | None = File(None), + layout_files: list[UploadFile] | None = File(None), # HTML/MHTML(Ctrl+S 另存为) +): + with GenerateTrace() as trace: + excel_meta = None + upload = data_file if (data_file is not None and data_file.filename) else excel + if upload is not None and upload.filename: + content = await upload.read() + if content: + try: + excel_meta = parse_tabular_upload(upload.filename, content) + sheets = (excel_meta or {}).get("sheets") or [] + trace.stage( + "parse_data", + upload.filename, + sheets=len(sheets), + rows=(sheets[0].get("row_count") if sheets else None), + ) + except Exception as e: # noqa: BLE001 + trace.error(f"数据文件解析失败: {e}") + return { + "draft": None, + "warnings": [f"数据文件解析失败: {e}"], + "confidence": 0.0, + "require_confirm": True, + "generate_log": trace.as_payload(), + } + + image_blobs: list[tuple[str, bytes, str]] = [] + for img in images or []: + if not img.filename: + continue + raw = await img.read() + if raw: + ctype = img.content_type or "image/png" + image_blobs.append((img.filename, raw, ctype)) + + layout_parsed_list: list[dict] = [] + layout_texts: list[str] = [] + layout_names: list[str] = [] + for lf in layout_files or []: + if not lf.filename: + continue + raw = await lf.read() + if not raw: + continue + layout_names.append(lf.filename) + parsed = parse_layout_file(lf.filename, raw) + layout_parsed_list.append(parsed) + layout_texts.append(layout_summary_text(parsed)) + + image_count = len(image_blobs) + html_count = len(layout_names) + model_arg = llm_model.strip() or None + user_prompt = prompt or "" + trace.stage( + "inputs", + prompt_chars=len(user_prompt), + images=image_count, + layouts=html_count, + llm_provider=llm_provider, + llm_model=model_arg or "", + image_names=[n for n, _, _ in image_blobs], + layout_names=layout_names, + data_file=(upload.filename if upload and upload.filename else ""), + ) + + # HTML 按钮行并入用户需求,便于 parse_action_bar + html_btn_extra = "" + for parsed in layout_parsed_list: + if parsed.get("ok") and parsed.get("buttons"): + html_btn_extra += "\n操作:" + "、".join(parsed["buttons"][:20]) + prompt_for_hints = user_prompt + html_btn_extra + + trace.stage("vision_extract", "开始理解截图") + vision_text, vision_notes = understand_images(user_prompt, image_blobs, llm_provider, None) + trace.stage( + "vision_extract", + "完成", + chars=len(vision_text or ""), + has_vision=bool(vision_text), + notes=len(vision_notes or []), + ) + html_layout_text = "\n\n".join(layout_texts) + effective_prompt = compose_effective_prompt( + prompt_for_hints, + image_count, + vision_text, + html_layout_text, + html_count, + ) + faithful = wants_screenshot_layout(user_prompt, image_count, html_count) + trace.stage( + "compose_prompt", + chars=len(effective_prompt or ""), + screenshot_faithful=faithful, + ) + + trace.stage("build_blueprint", "启发式草案") + draft, warnings, confidence = build_blueprint( + effective_prompt, excel_meta, storage_mode, image_count, html_count + ) + # url.config 抓包:导航页签 / 空接口告警 + if excel_meta and isinstance(excel_meta, dict): + for w in excel_meta.get("warnings") or []: + warnings.append(str(w)) + cap_ui = excel_meta.get("ui_hints") or {} + if cap_ui.get("nav_items"): + meta = draft.setdefault("meta", {}) + ui = meta.setdefault("ui", {}) + ui.setdefault("nav_items", cap_ui["nav_items"]) + cap = excel_meta.get("capture") or {} + if cap.get("pairs"): + warnings.append( + "已解析 url.config 抓包:" + + "、".join( + f"{p.get('action') or '?'}({'空' if p.get('empty') else p.get('kind')})" + for p in cap["pairs"][:6] + ) + ) + # 用 HTML 再补一轮 ui_hints(名称/页签),写回 meta + for parsed in layout_parsed_list: + if not parsed.get("ok"): + continue + hints = parse_ui_hints(prompt_for_hints, image_count, html_count) + merge_layout_into_ui_hints(hints, parsed) + meta = draft.setdefault("meta", {}) + if hints.get("app_name") and ( + not meta.get("name") or "数据管理" in str(meta.get("name")) + ): + meta["name"] = hints["app_name"] + if hints.get("ui"): + meta["ui"] = {**(meta.get("ui") or {}), **hints["ui"]} + if hints["ui"].get("platform_title"): + meta["platform_title"] = hints["ui"]["platform_title"] + if hints.get("_html_buttons_line"): + ab = parse_action_bar(hints["_html_buttons_line"]) + if ab: + for p in draft.get("pages") or []: + if isinstance(p, dict) and p.get("type") == "list": + lay = p.setdefault("layout", {}) + lay["actions"] = ab["actions"] + labels = dict(lay.get("action_labels") or {}) + labels.update(ab["action_labels"]) + lay["action_labels"] = labels + # 页标题 + widget 标题(来自 HTML label) + for p in draft.get("pages") or []: + if not isinstance(p, dict): + continue + if p.get("type") == "list" and hints.get("list_title"): + p["title"] = hints["list_title"] + if p.get("type") == "form_create" and hints.get("create_title"): + p["title"] = hints["create_title"] + if p.get("type") == "dashboard": + if hints.get("dash_title"): + p["title"] = hints["dash_title"] + elif p.get("title") and "概览" in str(p.get("title")): + p["title"] = str(p["title"]).replace("概览", "看板") + lay = p.setdefault("layout", {}) + for w in lay.get("widgets") or []: + if not isinstance(w, dict): + continue + if w.get("type") in {"line_chart", "bar_chart"} and hints.get("chart_title"): + w["title"] = hints["chart_title"] + if w.get("type") == "status_strip" and hints.get("strip_title"): + w["title"] = hints["strip_title"] + if w.get("type") == "table" and ( + hints.get("table_title") or (hints.get("ui") or {}).get("table_title") + ): + w["title"] = hints.get("table_title") or hints["ui"]["table_title"] + + baseline = json.loads(json.dumps(draft)) # deep copy + warnings.extend(vision_notes) + for parsed in layout_parsed_list: + if parsed.get("ok"): + warnings.append(f"已解析页面布局: {parsed.get('filename')}") + else: + warnings.append(f"页面布局解析失败: {parsed.get('filename')} ({parsed.get('error')})") + trace.notes(warnings) + slug0 = (draft.get("meta") or {}).get("slug") or "" + trace.stage( + "build_blueprint", + "完成", + slug=slug0, + pages=len(draft.get("pages") or []), + confidence=confidence, + ) + + excel_summary = "" + if excel_meta and excel_meta.get("sheets"): + sh = excel_meta["sheets"][0] + excel_summary = f"sheet={sh.get('name')} headers={sh.get('headers')}" + if html_layout_text: + excel_summary = (excel_summary + " | " if excel_summary else "") + "html_layout=yes" + trace.stage("llm_enhance", "代码模型润色蓝图") + draft, llm_notes = enhance_blueprint_with_llm( + draft, effective_prompt, excel_summary, llm_provider, model_arg + ) + warnings.extend(llm_notes) + trace.notes(llm_notes) + draft = harden_draft(draft, baseline) + draft.setdefault("meta", {}).setdefault("source", {}) + draft["meta"]["source"]["prompt"] = user_prompt + draft["meta"]["source"]["llm_provider"] = llm_provider + if model_arg: + draft["meta"]["source"]["llm_model"] = model_arg + if upload is not None and upload.filename: + draft["meta"]["source"]["excel_ref"] = f"upload://{upload.filename}" + if excel_meta and excel_meta.get("sheets"): + draft["meta"]["source"]["data_format"] = excel_meta["sheets"][0].get("source") or "excel" + if excel_meta["sheets"][0].get("row_count"): + draft["meta"]["source"]["row_count"] = excel_meta["sheets"][0]["row_count"] + if image_count: + draft["meta"]["source"]["image_refs"] = [n for n, _, _ in image_blobs] + if vision_text: + draft["meta"]["source"]["vision_summary"] = vision_text[:1000] + if html_count: + draft["meta"]["source"]["layout_refs"] = layout_names + draft["meta"]["source"]["layout_summary"] = html_layout_text[:2000] + if image_count or html_count: + draft["meta"]["source"]["screenshot_faithful"] = faithful + if faithful: + draft["meta"]["ui_preset"] = "screenshot_faithful" + for p in draft.get("pages") or []: + if isinstance(p, dict) and p.get("type") == "dashboard": + p.setdefault("layout", {})["preset"] = "screenshot_faithful" + draft["meta"]["source"]["generate_run_id"] = trace.run_id + trace.stage("llm_enhance", "完成", slug=(draft.get("meta") or {}).get("slug") or "") + + fidelity_report: dict = {"skipped": True, "reason": "not_applicable"} + if image_count and faithful: + # 视觉摘录已写入 effective_prompt;此处:预览/计划 ↔ 原图打分 → 代码模型改蓝图 → 循环至 ≥95% + trace.stage("fidelity_loop", "开始还原度迭代") + preview_rows: list[dict] = [] + if excel_meta and isinstance(excel_meta, dict): + for sh in excel_meta.get("sheets") or []: + if isinstance(sh, dict) and sh.get("row_dicts"): + preview_rows = list(sh["row_dicts"])[:500] + break + draft, fidelity_report, fid_notes = await asyncio.to_thread( + run_fidelity_loop, + draft, + image_blobs, + vision_text, + llm_provider=llm_provider, + llm_model=model_arg, + vision_model=None, # 视觉通道只用 VISION_MODEL,勿混入文本模型名 + harden_fn=harden_draft, + baseline=baseline, + preview_rows=preview_rows, + ) + warnings.extend(fid_notes) + draft = harden_draft(draft, baseline) + draft.setdefault("meta", {}).setdefault("source", {}) + draft["meta"]["source"]["fidelity"] = { + "final_score": fidelity_report.get("final_score"), + "passed": fidelity_report.get("passed"), + "rounds": len(fidelity_report.get("rounds") or []), + "target": fidelity_report.get("target"), + } + if fidelity_report.get("passed"): + draft["meta"]["confidence"] = max( + float(draft["meta"].get("confidence") or confidence or 0), + 0.95, + ) + trace.stage( + "fidelity_loop", + "结束", + final_score=fidelity_report.get("final_score"), + passed=fidelity_report.get("passed"), + rounds=len(fidelity_report.get("rounds") or []), + target=fidelity_report.get("target"), + ) + else: + trace.stage( + "fidelity_loop", + "跳过", + reason=("no_images" if not image_count else "not_screenshot_faithful"), + ) + + conf = float(draft.get("meta", {}).get("confidence") or confidence) + log_payload = trace.as_payload() + draft.setdefault("meta", {}).setdefault("source", {}) + draft["meta"]["source"]["generate_log_file"] = log_payload.get("log_file") or "" + return { + "draft": draft, + "warnings": warnings, + "confidence": conf, + "require_confirm": True, + "llm_provider": llm_provider, + "llm_model": model_arg or "", + "fidelity": fidelity_report, + "generate_log": log_payload, + } diff --git a/ai-service/demo_fixtures.py b/ai-service/demo_fixtures.py new file mode 100644 index 0000000..3f6dcc3 --- /dev/null +++ b/ai-service/demo_fixtures.py @@ -0,0 +1,83 @@ +"""本地演示/测试素材:供控制台一键预填需求与上传文件。 + +目录默认:仓库 test/(可用 DEMO_FIXTURES_DIR 覆盖)。 +""" +from __future__ import annotations + +import mimetypes +import os +from pathlib import Path + +from fastapi import HTTPException +from fastapi.responses import FileResponse + +_ROOT = Path(__file__).resolve().parents[1] + + +def fixtures_root() -> Path: + raw = (os.getenv("DEMO_FIXTURES_DIR") or "").strip() + if raw: + return Path(raw).expanduser().resolve() + return (_ROOT / "test").resolve() + + +# 默认演示包:沉降观测(行业示例,仅测试用) +DEFAULT_PACK = { + "id": "settlement_demo", + "label": "沉降观测演示包(test/)", + "prompt": "prompts/prompt_1.txt", + "data": "data/settlement.xlsx", + "images": ["refs/board_a.png", "refs/board_b.png"], + "layouts": ["refs/dashboard.html", "refs/nav.html"], + "slug": "settlement_observation_system", +} + + +def _safe_file(rel: str) -> Path: + root = fixtures_root() + rel = (rel or "").replace("\\", "/").lstrip("/") + if not rel or ".." in rel.split("/"): + raise HTTPException(400, "非法路径") + path = (root / rel).resolve() + try: + path.relative_to(root) + except ValueError as e: + raise HTTPException(400, "越界路径") from e + if not path.is_file(): + raise HTTPException(404, f"文件不存在: {rel}") + return path + + +def fixtures_manifest() -> dict: + root = fixtures_root() + pack = dict(DEFAULT_PACK) + pack["root"] = str(root) + pack["available"] = root.is_dir() + + def ok(rel: str) -> bool: + try: + return _safe_file(rel).is_file() + except HTTPException: + return False + + pack["files"] = { + "prompt": {"path": pack["prompt"], "ok": ok(pack["prompt"])}, + "data": {"path": pack["data"], "ok": ok(pack["data"])}, + "images": [{"path": p, "ok": ok(p)} for p in pack["images"]], + "layouts": [{"path": p, "ok": ok(p)} for p in pack["layouts"]], + } + return pack + + +def read_prompt_text() -> str: + return _safe_file(DEFAULT_PACK["prompt"]).read_text(encoding="utf-8") + + +def file_response(rel: str) -> FileResponse: + path = _safe_file(rel) + ctype, _ = mimetypes.guess_type(str(path)) + return FileResponse( + path, + media_type=ctype or "application/octet-stream", + filename=path.name, + ) diff --git a/ai-service/etc/README.md b/ai-service/etc/README.md new file mode 100644 index 0000000..ceea776 --- /dev/null +++ b/ai-service/etc/README.md @@ -0,0 +1,45 @@ +# AI / LLM 配置说明 + +供应商、模型列表、默认通道写在 **`ai-service/etc/llm.yaml`**,**不要写在 Python 代码里**。 + +密钥仍放项目根 **`.env`**(`DEEPSEEK_API_KEY` 等),配置文件只引用环境变量名(`api_key_env`)。 + +## 文件 + +| 路径 | 作用 | +|------|------| +| `ai-service/etc/llm.yaml` | 供应商、模型、视觉默认、别名 | +| 项目根 `.env` | API Key、可选 `LLM_PROVIDER` / `VISION_*` 覆盖 | +| `AI_CONFIG_PATH` / `LLM_CONFIG_PATH` | 自定义配置文件绝对路径 | + +Docker:`docker-compose.yml` 将 `llm.yaml` 挂载到 `/app/etc/llm.yaml`。 + +## 改配置后 + +```bash +./reload-config.sh ai +# 或 +docker compose up -d --force-recreate ai +``` + +进程内配置有缓存;重启 `ai` 容器后生效。 + +## 增删供应商 + +在 `providers:` 下增加一段即可,例如: + +```yaml +providers: + openai: + label: OpenAI + base_url: https://api.openai.com/v1 + api_key_env: OPENAI_API_KEY + default_model: gpt-4o-mini + models: + - id: gpt-4o-mini + label: gpt-4o-mini + supports_vision: true + json_mode: true +``` + +并在 `.env` 增加 `OPENAI_API_KEY=...`,控制台「模型」列表会从 `/ai/api/v1/llm/providers` 自动带出。 diff --git a/ai-service/etc/llm.yaml b/ai-service/etc/llm.yaml new file mode 100644 index 0000000..5d5c659 --- /dev/null +++ b/ai-service/etc/llm.yaml @@ -0,0 +1,88 @@ +# AI / LLM 供应商配置(独立文件,勿把密钥写进本文件) +# 密钥放项目根 .env:DEEPSEEK_API_KEY / MINIMAX_API_KEY / DASHSCOPE_API_KEY +# 覆盖路径:环境变量 AI_CONFIG_PATH 或 LLM_CONFIG_PATH +# 改本文件后:./reload-config.sh ai + +default_provider: deepseek + +# 视觉通道(截图理解 / 还原度打分);未配密钥时自动跳过看图 +vision: + provider: dashscope + model: qwen3.6-plus + # 别名 → 正式 provider id + aliases: + qwen: dashscope + aliyun: dashscope + tongyi: dashscope + +# 文本模型别名(请求里写 qwen 时落到 dashscope) +aliases: + qwen: dashscope + aliyun: dashscope + tongyi: dashscope + +# 视觉模型名启发式(用于判断传入的 model 是否像视觉模型) +vision_model_hints: + - vl + - vision + - qwen3 + - qwen-vl + - qwen-plus + - gpt-4o + - gemini + +providers: + heuristic: + label: 本地启发式(不调大模型) + base_url: "" + api_key_env: "" + default_model: "" + models: [] + supports_vision: false + json_mode: false + + deepseek: + label: DeepSeek + base_url: https://api.deepseek.com/v1 + api_key_env: DEEPSEEK_API_KEY + default_model: deepseek-chat + models: + - id: deepseek-chat + label: deepseek-chat + - id: deepseek-reasoner + label: deepseek-reasoner + supports_vision: false + json_mode: true + + minimax: + label: MiniMax + base_url: https://api.minimaxi.com/v1 + api_key_env: MINIMAX_API_KEY + default_model: MiniMax-Text-01 + models: + - id: MiniMax-Text-01 + label: MiniMax-Text-01 + - id: MiniMax-M2.5 + label: MiniMax-M2.5 + - id: abab6.5s-chat + label: abab6.5s-chat + supports_vision: false + json_mode: false + + dashscope: + label: 通义千问 (DashScope) + base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + # 可用环境变量 DASHSCOPE_BASE_URL 覆盖 base_url + api_key_env: DASHSCOPE_API_KEY + default_model: qwen3.6-plus + models: + - id: qwen3.6-plus + label: qwen3.6-plus(视觉) + - id: qwen-vl-plus + label: qwen-vl-plus + - id: qwen-vl-max + label: qwen-vl-max + - id: qwen-plus + label: qwen-plus + supports_vision: true + json_mode: false diff --git a/ai-service/fidelity_loop.py b/ai-service/fidelity_loop.py new file mode 100644 index 0000000..ea3c310 --- /dev/null +++ b/ai-service/fidelity_loop.py @@ -0,0 +1,930 @@ +"""截图还原迭代闭环(通用,不绑定行业)。 + +流程: +1. 视觉模型从参考截图生成结构化提示(上游已完成) +2. 代码模型生成/润色蓝图(上游已完成) +3. 将「当前蓝图将渲染的 UI 计划」与参考截图交给视觉模型打分给差异 +4. 代码模型按差异改蓝图 +5. 重复直至 score>=目标 或达到最大轮次 + +可选:若环境有 playwright,用蓝图 meta 渲染简易预览 HTML 截图再对比(更接近真截图)。 +""" +from __future__ import annotations + +import base64 +import json +import os +import re +import sys +from pathlib import Path +from typing import Any + +import httpx + +from llm import ( + _api_key, + enhance_blueprint_with_llm, + resolve_vision_provider, +) +from generate_log import glog + + +def fidelity_target() -> int: + try: + return max(50, min(100, int(os.getenv("FIDELITY_TARGET", "95")))) + except ValueError: + return 95 + + +def fidelity_max_rounds() -> int: + try: + return max(1, min(8, int(os.getenv("FIDELITY_MAX_ROUNDS", "4")))) + except ValueError: + return 4 + + +def fidelity_enabled() -> bool: + v = (os.getenv("FIDELITY_LOOP") or "1").strip().lower() + return v not in {"0", "false", "off", "no"} + + +def ui_plan_from_draft(draft: dict[str, Any]) -> str: + """把蓝图里会影响展示的字段压成「将渲染的界面计划」,供视觉对比。""" + meta = draft.get("meta") or {} + ui = meta.get("ui") or {} + pages = draft.get("pages") or [] + dash = next((p for p in pages if isinstance(p, dict) and p.get("type") == "dashboard"), None) + widgets = ((dash or {}).get("layout") or {}).get("widgets") or [] + + lines = [ + "【当前蓝图将渲染的界面计划 · 非像素截图,但须与参考图文案/结构一致】", + f"ui_preset: {meta.get('ui_preset') or ''}", + f"平台抬头 platform_title: {meta.get('platform_title') or ui.get('platform_title') or ''}", + f"平台英文 platform_subtitle: {ui.get('platform_subtitle') or ''}", + f"系统名 name: {meta.get('name') or ''}", + f"壳链 shell_links: {'、'.join(ui.get('shell_links') or [])}", + f"浮条 float_actions: {'、'.join(ui.get('float_actions') or [])}", + f"导航 nav_items: {'、'.join(ui.get('nav_items') or [])}", + f"工程/业务上下文 project_context: {meta.get('project_context') or ui.get('project_context') or ''}", + f"统计标签: {ui.get('stats_left_label') or ''} / {ui.get('stats_right_label') or ''}", + f"单选 filter_radios: {'、'.join(ui.get('filter_radios') or ui.get('section_options') or [])}", + f"单选字段 radio_field: {ui.get('radio_field') or ''}", + f"下拉 select_label/select_field: {ui.get('select_label') or ''} / {ui.get('select_field') or ''}", + f"提示 filter_hint: {ui.get('filter_hint') or ''}", + f"侧标 chart/strip/table: {ui.get('chart_side_label') or ''} | {ui.get('strip_side_label') or ''} | {ui.get('table_side_label') or ''}", + f"表标题 table_title: {ui.get('table_title') or ''}", + f"表头 table_headers: {'、'.join(ui.get('table_headers') or [])}", + f"chart_style: {ui.get('chart_style') or ''}", + f"invert_y: {ui.get('invert_y')}", + f"y_unit / y_axis_label: {ui.get('y_unit') or ''} / {ui.get('y_axis_label') or ''}", + ] + legs = ui.get("legend_items") or [] + if legs: + lines.append( + "图例 legend_items: " + + "、".join( + f"{x.get('label')}({x.get('color')})" if isinstance(x, dict) else str(x) for x in legs + ) + ) + lines.append(f"看板页标题: {(dash or {}).get('title') or ''}") + for w in widgets: + if not isinstance(w, dict): + continue + lines.append( + "widget: " + + json.dumps( + { + "type": w.get("type"), + "title": w.get("title"), + "x_field": w.get("x_field"), + "metrics": w.get("metrics"), + "metric": w.get("metric"), + "variant": w.get("variant"), + "value_field": w.get("value_field"), + "secondary_field": w.get("secondary_field"), + "cycle_days": w.get("cycle_days"), + "columns": w.get("columns"), + "filter_field": w.get("filter_field"), + "y_unit": w.get("y_unit"), + }, + ensure_ascii=False, + ) + ) + return "\n".join(lines) + + +def _platform_base() -> str: + return (os.getenv("PLATFORM_BASE") or os.getenv("GATEWAY_BASE") or "http://127.0.0.1:8180").rstrip("/") + + +def _web_base() -> str: + return (os.getenv("WEB_BASE") or "http://127.0.0.1:5173").rstrip("/") + + +def real_screen_enabled() -> bool: + v = (os.getenv("FIDELITY_REAL_SCREEN") or "1").strip().lower() + return v not in {"0", "false", "off", "no"} + + +def _login_platform() -> str: + user = os.getenv("FIDELITY_USER") or "demo" + password = os.getenv("FIDELITY_PASSWORD") or "demo123" + with httpx.Client(timeout=30.0) as client: + r = client.post( + f"{_platform_base()}/api/v1/auth/login", + json={"username": user, "password": password}, + ) + r.raise_for_status() + return r.json()["access_token"] + + +def _publish_draft(draft: dict[str, Any], token: str) -> str: + """发布蓝图并返回 slug。用独立 slug 避免误伤正式应用时可设 FIDELITY_SLUG_SUFFIX=_fidcheck。""" + meta = draft.setdefault("meta", {}) + base_slug = str(meta.get("slug") or "app").strip() or "app" + suffix = (os.getenv("FIDELITY_SLUG_SUFFIX") or "").strip() + slug = f"{base_slug}{suffix}" if suffix else base_slug + meta["slug"] = slug + # 保证 apis.base_path 一致 + apis = draft.setdefault("apis", {}) + if isinstance(apis, dict): + apis["base_path"] = f"/api/v1/apps/{slug}" + with httpx.Client(timeout=120.0) as client: + r = client.post( + f"{_platform_base()}/api/v1/apps/{slug}/publish", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"blueprint": draft}, + ) + r.raise_for_status() + return slug + + +def _capture_real_app_screenshot_impl( + draft: dict[str, Any], + *, + preview_rows: list[dict[str, Any]] | None = None, +) -> tuple[bytes | None, str, str | None]: + """优先走草稿预览 /#/preview/{id}(无需登录/不必等发布);失败再回退公开应用页。""" + try: + from playwright.sync_api import sync_playwright # type: ignore + except Exception as e: # noqa: BLE001 + return None, f"无 playwright,无法截真页面: {e}", None + + from preview_store import create_preview, get_preview + + web = _web_base() + out_dir = Path(os.getenv("FIDELITY_SHOT_DIR") or "").resolve() if os.getenv("FIDELITY_SHOT_DIR") else None + goto_timeout = int(os.getenv("FIDELITY_GOTO_TIMEOUT_MS") or "45000") + slug = str(((draft.get("meta") or {}).get("slug")) or "preview") + preview_id = create_preview( + json.loads(json.dumps(draft)), + rows=preview_rows or [], + ) + target = f"{web}/#/preview/{preview_id}" + pack = get_preview(preview_id) or { + "id": preview_id, + "ok": True, + "blueprint": draft, + "rows": preview_rows or [], + "resource": "records", + } + # 浏览器侧可能访问不到 AI;把预览包注入页面 + 拦截 /preview API + inject_payload = { + "ok": True, + "id": preview_id, + "blueprint": pack.get("blueprint") or draft, + "rows": pack.get("rows") or preview_rows or [], + "resource": pack.get("resource") or "records", + } + # 清掉本机过期登录态,避免 React 的 session-expired 把 /#/preview 踢回控制台 + inject_js = ( + "try{localStorage.removeItem('ajz_session');}catch(e){}" + "window.__AJZ_PREVIEW__ = " + + json.dumps(inject_payload, ensure_ascii=False) + + ";" + ) + + def _fmt_err(err: BaseException) -> str: + msg = str(err).strip() or repr(err) + return msg.replace("\n", " ")[:500] + + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + try: + page = browser.new_page(viewport={"width": 1600, "height": 1100}) + page.add_init_script(inject_js) + + def _fulfill_preview(route): # type: ignore + route.fulfill( + status=200, + content_type="application/json; charset=utf-8", + body=json.dumps(inject_payload, ensure_ascii=False).encode("utf-8"), + ) + + page.route("**/api/v1/preview/**", _fulfill_preview) + page.route("**/ai/api/v1/preview/**", _fulfill_preview) + page.goto(target, wait_until="domcontentloaded", timeout=goto_timeout) + # 再写一次,防止极端时序下 init 被覆盖 + page.evaluate(inject_js) + try: + page.wait_for_selector( + ".sf-shell-bar, .sf-root, .gen-app-ops, .gen-app, .sf-line-wrap", + timeout=45000, + ) + except Exception: + snip = "" + try: + snip = page.locator("body").inner_text(timeout=2000)[:240] + except Exception: + snip = page.url + raise RuntimeError( + f"预览壳未出现(请确认 Web 已含 /#/preview 前端)。page={page.url} text={snip!r}" + ) from None + # 仅当仍在登录台(无预览壳)时判定失败 + if page.locator("button").filter(has_text="登录").count() and not page.locator( + ".sf-shell-bar, .sf-root, .gen-app" + ).count(): + raise RuntimeError("预览页落到登录界面") + page.wait_for_timeout(2500) + if page.locator(".gen-app").count(): + png = page.locator(".gen-app").first.screenshot(type="png") + else: + png = page.screenshot(type="png", full_page=True) + finally: + browser.close() + if out_dir: + out_dir.mkdir(parents=True, exist_ok=True) + (out_dir / f"preview_{preview_id}.png").write_bytes(png) + return ( + png, + f"已截取草稿预览(/#/preview/{preview_id},注入数据无需再拉 AI)参与对比", + slug, + ) + except Exception as e: # noqa: BLE001 + # 回退:尝试发布后的公开应用页(仍不登录) + try: + token = _login_platform() + pub_slug = _publish_draft(json.loads(json.dumps(draft)), token) + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + try: + page = browser.new_page(viewport={"width": 1600, "height": 1100}) + page.add_init_script( + "try{localStorage.removeItem('ajz_session');}catch(e){}" + ) + page.goto( + f"{web}/#/app/{pub_slug}", + wait_until="domcontentloaded", + timeout=goto_timeout, + ) + page.wait_for_selector( + ".sf-shell-bar, .sf-root, .gen-app-ops, .gen-app", + timeout=45000, + ) + if page.locator("button").filter(has_text="登录").count() and not page.locator( + ".sf-shell-bar, .sf-root, .gen-app" + ).count(): + raise RuntimeError("公开应用页仍显示登录") + page.wait_for_timeout(2500) + if page.locator(".gen-app").count(): + png = page.locator(".gen-app").first.screenshot(type="png") + else: + png = page.screenshot(type="png", full_page=True) + finally: + browser.close() + if out_dir: + out_dir.mkdir(parents=True, exist_ok=True) + (out_dir / f"real_{pub_slug}.png").write_bytes(png) + return png, f"预览失败后回退公开应用页(slug={pub_slug}):{_fmt_err(e)}", pub_slug + except Exception as e2: # noqa: BLE001 + return ( + None, + f"真页面截图失败: preview={_fmt_err(e)}; fallback={_fmt_err(e2)}(目标 {target})", + slug, + ) + + +def capture_real_app_screenshot( + draft: dict[str, Any], + *, + preview_rows: list[dict[str, Any]] | None = None, +) -> tuple[bytes | None, str, str | None]: + """ + 草稿预览截图。Windows+uvicorn 下禁止同进程 Playwright,改为独立 shot_worker.py。 + """ + if not real_screen_enabled(): + return None, "FIDELITY_REAL_SCREEN=0,跳过真页面截图", None + + if (os.getenv("FIDELITY_SHOT_INPROCESS") or "").strip() in {"1", "true", "yes"}: + return _capture_real_app_screenshot_impl(draft, preview_rows=preview_rows) + + import subprocess + import tempfile + + from preview_store import create_preview + + ai_dir = str(Path(__file__).resolve().parent) + worker = Path(ai_dir) / "shot_worker.py" + preview_id = create_preview(json.loads(json.dumps(draft)), rows=preview_rows or []) + resource = "records" + try: + apis = draft.get("apis") or {} + res0 = (apis.get("resources") or [None])[0] + if isinstance(res0, dict) and res0.get("path"): + resource = str(res0["path"]).lstrip("/") + elif (draft.get("resources") or [{}])[0].get("name"): + resource = str(draft["resources"][0]["name"]) + except Exception: + pass + + with tempfile.TemporaryDirectory(prefix="ajz_shot_") as td: + inp = Path(td) / "in.json" + outp = Path(td) / "out.json" + inp.write_text( + json.dumps( + { + "draft": draft, + "rows": preview_rows or [], + "preview_id": preview_id, + "resource": resource, + }, + ensure_ascii=False, + default=str, + ), + encoding="utf-8", + ) + env = os.environ.copy() + env["FIDELITY_SHOT_INPUT"] = str(inp) + env["FIDELITY_SHOT_OUTPUT"] = str(outp) + env.setdefault("WEB_BASE", _web_base()) + env.pop("PYTHONASYNCIODEBUG", None) + creationflags = 0 + if sys.platform == "win32": + # Detach from uvicorn's console/asyncio job so Playwright can spawn Chromium + creationflags = getattr(subprocess, "CREATE_NO_WINDOW", 0) | getattr( + subprocess, "CREATE_NEW_PROCESS_GROUP", 0 + ) + try: + proc = subprocess.run( + [sys.executable, str(worker)], + cwd=ai_dir, + env=env, + timeout=190, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + creationflags=creationflags, + ) + except subprocess.TimeoutExpired: + return None, "真页面截图子进程超时(190s)", None + err_tail = ((proc.stderr or "") + "\n" + (proc.stdout or "")).strip()[-600:] + if not outp.is_file(): + return ( + None, + f"真页面截图子进程无输出(code={proc.returncode}): {err_tail or 'no stderr'}", + None, + ) + try: + data = json.loads(outp.read_text(encoding="utf-8")) + except Exception as e: # noqa: BLE001 + return None, f"真页面截图结果无法解析: {e}; stderr={err_tail}", None + png_b64 = data.get("png_b64") + png = base64.b64decode(png_b64) if png_b64 else None + note = str(data.get("note") or "") + if not note: + note = f"shot_worker code={proc.returncode} stderr={err_tail or 'empty'}" + elif not png: + note = f"{note} | via=shot_worker code={proc.returncode}" + if err_tail: + note = f"{note} stderr={err_tail[:400]}" + else: + if "via=shot_worker" not in note: + note = f"{note} | via=shot_worker" + return png, note, data.get("slug") + + +def _try_preview_screenshot( + draft: dict[str, Any], + *, + preview_rows: list[dict[str, Any]] | None = None, +) -> tuple[bytes | None, str, bool]: + """截取预览/真页面。返回 (png, note, is_real_screen)。""" + real, note, _slug = capture_real_app_screenshot(draft, preview_rows=preview_rows) + if real: + return real, note, True + return None, note, False + + +_COMPARE_PROMPT = """你是严格的 UI 还原验收员。必须「逐区对照真页面截图与参考图」,禁止因文案大致相似就给高分。 + +输入图顺序: +- 图1起:参考原系统截图(可能多张,为同页不同展开态,合并理解) +- 最后一张(若有):【系统真页面截图】——只以这张判定还原度,文字计划仅作辅助 + +重要:文案计划一致 ≠ 画面合格。真页面缺主图、主图被监督条替代、分区错位,必须大幅扣分。 + +## 必检分区(zone_checklist,每项 true/false;任一项 false → 对应 fails 必写) +1) shell_header:顶栏平台名/系统名/右侧链接是否与原图一致 +2) nav:次级导航文案与高亮是否大体一致 +3) filter_bar:工程上下文、统计、单选、工点下拉、红色提示是否齐全且文案接近 +4) main_chart:是否存在「纵断面/主曲线图」——带坐标轴的折线或柱线混合大图(不是数字格、不是表格、不是监督条) +5) chart_legend:主图上方/旁侧图例是否存在且系列名接近 +6) status_strip:按时测量监督是否为上灰下绿双段竖条(不是单色数字格矩阵、不是把主图区填成监督条) +7) exceed_table:底部超限表标题与列是否接近 +8) side_labels:左侧竖标是否与三区(主图/监督/表)一一对应且正向可读 +9) float_dock:右侧浮条是否存在 + +## 硬性否决(任一条成立 → fails 必写,且 score 上限) +1) 文字倒置/镜像 → score≤35 +2) 文字严重倾斜、重叠、裁切变形 → score≤45 +3) 主图未横向铺满(右侧大片空白)→ score≤55 +4) 顶栏/导航/筛选/侧标/表头/图例文案缺失或不一致 → score≤70 +5) 分区结构不对(缺侧标、缺状态条、缺浮条、侧标与内容错位)→ score≤60 +6) 图表形态不符,或真页面用监督条/数字格顶替主曲线图 → score≤50 +7) zone_checklist.main_chart=false → score≤50 +8) zone_checklist.status_strip=false 且原图有监督条 → score≤55 + +若没有「系统真页面截图」: +- 不得判定 pass=true;score 最高 75 +- fails 必须含:「缺少真页面截图,无法验收字形方向/铺满/畸形」 + +## 禁止虚高 +- 不得因为「顶栏和表头大致对上」就把 score 抬到 ≥90 +- 缺主图或主图形态错误时,即使其它区都对,score 也不得超过 50 +- fails 为空仅当 zone_checklist 全部为 true 且肉眼无明显布局差异 +- 上一轮差异(若有)必须再次核对,未修复的必须继续写进 fails + +上一轮未修复差异: +{prev_fails} + +输出【仅 JSON 对象】: +{{ + "score": 0到100的整数, + "pass": true或false, + "fails": ["区名: 现象;应如何改正"], + "passes": ["已对齐项"], + "zone_checklist": {{ + "shell_header": true或false, + "nav": true或false, + "filter_bar": true或false, + "main_chart": true或false, + "chart_legend": true或false, + "status_strip": true或false, + "exceed_table": true或false, + "side_labels": true或false, + "float_dock": true或false + }}, + "visual_checks": {{ + "text_upright": true或false, + "chart_fills_width": true或false, + "no_malformation": true或false + }}, + "patch_hints": {{ + "meta.ui": {{}} + }} +}} +pass 仅当:score>={target} 且 fails 为空 且 zone_checklist 全 true 且 visual_checks 三项均为 true 且已提供真页面截图。 + +【截图视觉摘录】 +{vision} + +【当前蓝图界面计划】 +{plan} + +【是否有系统真页面截图】 +{has_preview} +""" + + +_CRITICAL_ZONES = ( + ("main_chart", "主图区: 真页面缺少与原图对应的折线/柱状主曲线图(或被监督条/数字格顶替)", 50), + ("status_strip", "监督条: 真页面缺少上灰下绿双段竖条监督区,或形态不符", 55), + ("side_labels", "侧标: 左侧竖标缺失、错位或与内容区不对应", 60), + ("exceed_table", "超限表: 底部超限测点表缺失或列/标题不符", 65), + ("filter_bar", "筛选条: 工程上下文/统计/单选/提示缺失或不一致", 70), + ("shell_header", "顶栏: 平台抬头/系统名/右侧链接缺失或不一致", 70), + ("nav", "导航: 次级导航文案或高亮与原图不一致", 70), + ("chart_legend", "图例: 主图图例缺失或系列名不符", 65), + ("float_dock", "浮条: 右侧浮条缺失", 75), +) + + +def _blueprint_structure_fails(draft: dict[str, Any]) -> list[tuple[str, int]]: + """蓝图结构硬校验:缺主图/监督条/表时不允许高分达标。""" + pages = draft.get("pages") or [] + dash = next((p for p in pages if isinstance(p, dict) and p.get("type") == "dashboard"), None) + widgets = ((dash or {}).get("layout") or {}).get("widgets") or [] + types = {str(w.get("type")) for w in widgets if isinstance(w, dict)} + ui = ((draft.get("meta") or {}).get("ui") or {}) if isinstance(draft.get("meta"), dict) else {} + out: list[tuple[str, int]] = [] + if not (types & {"line_chart", "bar_chart", "area_chart"}): + out.append(("蓝图结构: dashboard 缺少折线/柱状主图 widget", 45)) + if "status_strip" not in types: + out.append(("蓝图结构: dashboard 缺少 status_strip 监督条", 50)) + if "table" not in types: + out.append(("蓝图结构: dashboard 缺少超限 table", 55)) + if not (ui.get("chart_side_label") and ui.get("strip_side_label") and ui.get("table_side_label")): + out.append(("蓝图结构: meta.ui 三区侧标不全(chart/strip/table_side_label)", 60)) + if not (ui.get("legend_items") or ui.get("chart_legend")): + # legend_items 更常见 + if not ui.get("legend_items"): + out.append(("蓝图结构: meta.ui.legend_items 为空,主图图例难对齐", 65)) + return out + + +def _enforce_score_caps( + score: int, + fails: list[str], + checks: dict[str, Any], + zones: dict[str, Any], + *, + has_real: bool, + draft: dict[str, Any] | None, + prev_fails: list[str] | None, +) -> tuple[int, list[str], bool]: + """服务端强制扣分/否决,防止视觉模型虚高。""" + fails = list(fails) + if not has_real: + score = min(score, 75) + msg = "缺少真页面截图,无法验收字形方向/铺满/畸形" + if msg not in fails: + fails.append(msg) + + for key, label, cap in ( + ("text_upright", "竖排/正文存在倒置或不可正向阅读", 35), + ("chart_fills_width", "主图未横向铺满(右侧大片空白)", 55), + ("no_malformation", "存在明显畸形/变形", 45), + ): + if checks.get(key) is False: + score = min(score, cap) + if not any(label[:6] in f for f in fails): + fails.append(label) + + for key, label, cap in _CRITICAL_ZONES: + if zones.get(key) is False: + score = min(score, cap) + if not any(key in f or label[:4] in f for f in fails): + fails.append(label) + + # zone_checklist 未返回时,不默认信任满分 + if has_real and not zones: + score = min(score, 80) + msg = "视觉打分未返回 zone_checklist,按不完整验收处理" + if msg not in fails: + fails.append(msg) + + if draft is not None: + for label, cap in _blueprint_structure_fails(draft): + score = min(score, cap) + if label not in fails: + fails.append(label) + + # 上一轮硬伤若本轮 fails 空但分数暴涨,仍保守封顶(模型常漏检) + if prev_fails and score >= 90: + severe = [f for f in prev_fails if any(k in f for k in ("主图", "折线", "分区", "侧标", "监督", "形态"))] + if severe and not fails: + score = min(score, 70) + fails.append( + "上一轮结构性差异疑似未复核:" + + ";".join(severe[:3]) + + "。请对照真页面复查,勿虚高满分" + ) + + ok = score >= fidelity_target() and not fails and has_real + if zones: + ok = ok and all(zones.get(k) is True for k, _, _ in _CRITICAL_ZONES) + for key in ("text_upright", "chart_fills_width", "no_malformation"): + if checks.get(key) is False: + ok = False + return score, fails, ok + + +def _parse_json_obj(text: str) -> dict[str, Any] | None: + t = (text or "").strip() + if t.startswith("```"): + t = t.strip("`") + if t.startswith("json"): + t = t[4:].lstrip() + try: + obj = json.loads(t) + return obj if isinstance(obj, dict) else None + except json.JSONDecodeError: + m = re.search(r"\{[\s\S]*\}", t) + if not m: + return None + try: + obj = json.loads(m.group(0)) + return obj if isinstance(obj, dict) else None + except json.JSONDecodeError: + return None + + +def vision_score_fidelity( + ref_images: list[tuple[str, bytes, str]], + plan: str, + vision_extract: str, + preview_png: bytes | None, + provider_id: str | None = None, + model: str | None = None, + *, + is_real_screen: bool = False, + draft: dict[str, Any] | None = None, + prev_fails: list[str] | None = None, +) -> tuple[dict[str, Any], list[str]]: + notes: list[str] = [] + target = fidelity_target() + pid, meta, model_name = resolve_vision_provider(provider_id, model) + if not meta.get("supports_vision"): + return {"score": 0, "pass": False, "fails": ["视觉模型不可用"], "passes": []}, [ + f"{meta['label']} 无视觉能力,跳过打分" + ] + api_key = _api_key(meta) + if not api_key: + return {"score": 0, "pass": False, "fails": ["未配置视觉 API Key"], "passes": []}, [ + f"未配置 {meta.get('api_key_env')}" + ] + + has_real = bool(preview_png) and is_real_screen + prev_txt = ";".join((prev_fails or [])[:8]) if prev_fails else "(无,首轮)" + content: list[dict[str, Any]] = [ + { + "type": "text", + "text": _COMPARE_PROMPT.format( + target=target, + vision=vision_extract or "(无)", + plan=plan, + prev_fails=prev_txt, + has_preview="有真页面截图(请逐区检查;缺主图不得高分)" + if has_real + else "无真页面截图(禁止判定达标)", + ), + } + ] + for name, raw, ctype in ref_images[:3]: + if not ctype.startswith("image/"): + ctype = "image/png" + b64 = base64.b64encode(raw).decode("ascii") + content.append({"type": "image_url", "image_url": {"url": f"data:{ctype};base64,{b64}"}}) + if preview_png and is_real_screen: + b64 = base64.b64encode(preview_png).decode("ascii") + content.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}) + + base = (meta.get("base_url") or "").rstrip("/") + try: + with httpx.Client(timeout=150.0) as client: + def _post(mname: str): + return client.post( + f"{base}/chat/completions", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json={ + "model": mname, + "temperature": 0.0, + "messages": [{"role": "user", "content": content}], + }, + ) + + resp = _post(model_name) + if resp.status_code >= 400: + alt = "qwen-vl-plus" + err = resp.text[:300] + if model_name != alt: + notes.append(f"{model_name} 打分 {resp.status_code},尝试 {alt}: {err}") + resp = _post(alt) + model_name = alt + resp.raise_for_status() + text = resp.json()["choices"][0]["message"]["content"] + if isinstance(text, list): + text = "".join((x.get("text") if isinstance(x, dict) else str(x)) for x in text) + obj = _parse_json_obj(str(text)) + if not obj: + notes.append("视觉打分返回非 JSON,本轮记 0 分") + return { + "score": 0, + "pass": False, + "fails": ["打分解析失败"], + "passes": [], + "raw": str(text)[:800], + }, notes + raw_score = int(obj.get("score") or 0) + fails = [str(x) for x in (obj.get("fails") or []) if x] + passes = [str(x) for x in (obj.get("passes") or []) if x] + checks = obj.get("visual_checks") if isinstance(obj.get("visual_checks"), dict) else {} + zones = obj.get("zone_checklist") if isinstance(obj.get("zone_checklist"), dict) else {} + + score, fails, ok = _enforce_score_caps( + raw_score, + fails, + checks, + zones, + has_real=has_real, + draft=draft, + prev_fails=prev_fails, + ) + # 模型自称 pass 但服务端否决时记一条 + if bool(obj.get("pass")) and not ok: + notes.append( + f"视觉模型自评达标被否决:原始 {raw_score} → 校正 {score}(fails={len(fails)})" + ) + notes.append( + f"视觉打分 {meta['label']}/{model_name}: {score}/100" + f"(原始 {raw_score},真页面={'是' if has_real else '否'})" + ) + return { + "score": score, + "pass": ok, + "fails": fails, + "passes": passes, + "visual_checks": checks, + "zone_checklist": zones, + "raw_score": raw_score, + "patch_hints": obj.get("patch_hints") if isinstance(obj.get("patch_hints"), dict) else {}, + }, notes + except Exception as e: # noqa: BLE001 + return {"score": 0, "pass": False, "fails": [f"打分调用失败: {e}"], "passes": []}, [str(e)] + + +def _deep_merge_ui(dst: dict[str, Any], src: dict[str, Any]) -> dict[str, Any]: + out = dict(dst) + for k, v in src.items(): + if v is None or v == "": + continue + if isinstance(v, dict) and isinstance(out.get(k), dict): + out[k] = {**out[k], **v} + else: + out[k] = v + return out + + +def apply_patch_hints(draft: dict[str, Any], hints: dict[str, Any]) -> dict[str, Any]: + """把视觉 patch_hints 机械合并进 draft(再交给代码模型细修)。""" + if not hints: + return draft + meta = draft.setdefault("meta", {}) + for key in ("name", "platform_title", "project_context", "ui_preset"): + if hints.get(f"meta.{key}"): + meta[key] = hints[f"meta.{key}"] + elif hints.get(key) and key in {"name", "platform_title", "project_context"}: + meta[key] = hints[key] + ui_hint = hints.get("meta.ui") or hints.get("ui") + if isinstance(ui_hint, dict): + meta["ui"] = _deep_merge_ui(meta.get("ui") or {}, ui_hint) + return draft + + +def refine_blueprint_against_fails( + draft: dict[str, Any], + fails: list[str], + vision_extract: str, + plan: str, + provider_id: str | None = None, + model: str | None = None, +) -> tuple[dict[str, Any], list[str]]: + """代码模型按差异清单改蓝图。""" + instruction = ( + "根据视觉验收 fails 修改 draft,目标还原参考截图。" + "只改 meta / meta.ui / pages 标题与 dashboard widgets 配置;" + "禁止改成无关行业模板;禁止删除 entities。" + "必须把 fails 中的原文写进对应字段。" + "返回完整 draft JSON。" + ) + prompt = ( + f"{instruction}\n\n[视觉摘录]\n{vision_extract}\n\n[界面计划]\n{plan}\n\n" + f"[fails]\n" + "\n".join(f"- {x}" for x in fails) + ) + return enhance_blueprint_with_llm(draft, prompt, "fidelity_refine", provider_id, model) + + +def run_fidelity_loop( + draft: dict[str, Any], + ref_images: list[tuple[str, bytes, str]], + vision_extract: str, + *, + llm_provider: str | None = None, + llm_model: str | None = None, + vision_model: str | None = None, + harden_fn=None, + baseline: dict[str, Any] | None = None, + preview_rows: list[dict[str, Any]] | None = None, +) -> tuple[dict[str, Any], dict[str, Any], list[str]]: + """ + 返回 (draft, report, notes)。 + report: {target, rounds:[{score,fails,passes}], final_score, passed} + """ + notes: list[str] = [] + log = glog() + if not fidelity_enabled(): + if log: + log.stage("fidelity_loop", "已关闭 FIDELITY_LOOP=0") + return draft, {"skipped": True, "reason": "FIDELITY_LOOP=0"}, ["还原度迭代已关闭"] + if not ref_images: + if log: + log.stage("fidelity_loop", "无参考截图") + return draft, {"skipped": True, "reason": "no_images"}, ["无参考截图,跳过还原度迭代"] + + target = fidelity_target() + max_rounds = fidelity_max_rounds() + rounds: list[dict[str, Any]] = [] + current = draft + notes.append(f"开始还原度迭代:目标>={target}%,最多 {max_rounds} 轮") + if log: + log.notes(notes[-1:]) + log.info( + "fidelity.begin", + target=target, + max_rounds=max_rounds, + ref_images=len(ref_images), + preview_rows=len(preview_rows or []), + ) + + for i in range(1, max_rounds + 1): + plan = ui_plan_from_draft(current) + preview, preview_note, is_real = _try_preview_screenshot( + current, preview_rows=preview_rows + ) + notes.append(f"第{i}轮: {preview_note}") + if log: + log.stage( + f"fidelity_round_{i}", + preview_note, + round=i, + real_screen=is_real, + ) + prev_fails = list(rounds[-1].get("fails") or []) if rounds else [] + score_obj, score_notes = vision_score_fidelity( + ref_images, + plan, + vision_extract, + preview, + llm_provider, + vision_model, # 只用视觉模型,不用 deepseek-chat 等文本模型名 + is_real_screen=is_real, + draft=current, + prev_fails=prev_fails, + ) + notes.extend(score_notes) + if log: + log.notes(score_notes) + log.info( + f"fidelity.round_{i}.score", + round=i, + score=score_obj.get("score"), + raw_score=score_obj.get("raw_score"), + passed=bool(score_obj.get("pass")), + fails_n=len(score_obj.get("fails") or []), + ) + rounds.append( + { + "round": i, + "score": score_obj.get("score"), + "raw_score": score_obj.get("raw_score"), + "pass": score_obj.get("pass"), + "fails": score_obj.get("fails") or [], + "passes": score_obj.get("passes") or [], + "zone_checklist": score_obj.get("zone_checklist") or {}, + "real_screen": is_real, + } + ) + if score_obj.get("pass"): + notes.append(f"第{i}轮达标:{score_obj.get('score')}% ≥ {target}%(已对照真页面)") + if log: + log.note(notes[-1]) + break + + fails = list(score_obj.get("fails") or []) + if not fails: + fails = [f"总分仅 {score_obj.get('score')},未达 {target},请按视觉摘录补全 meta.ui"] + if log: + log.info( + f"fidelity.round_{i}.patch", + round=i, + fails="; ".join(fails[:8]), + ) + current = apply_patch_hints(current, score_obj.get("patch_hints") or {}) + current, refine_notes = refine_blueprint_against_fails( + current, fails, vision_extract, plan, llm_provider, llm_model + ) + notes.extend(refine_notes) + if log: + log.notes(refine_notes) + if harden_fn and baseline is not None: + current = harden_fn(current, baseline) + + final = rounds[-1]["score"] if rounds else 0 + report = { + "target": target, + "max_rounds": max_rounds, + "rounds": rounds, + "final_score": final, + "passed": bool(rounds and rounds[-1].get("pass")), + } + if not report["passed"]: + notes.append( + f"还原度迭代结束未达标:最终 {final}%(目标 {target}%),请根据 fails 继续生成或手工改蓝图" + ) + if log: + log.warning(notes[-1], final_score=final, target=target) + elif log: + log.info("fidelity.passed", final_score=final, target=target) + return current, report, notes diff --git a/ai-service/generate_log.py b/ai-service/generate_log.py new file mode 100644 index 0000000..effdc30 --- /dev/null +++ b/ai-service/generate_log.py @@ -0,0 +1,173 @@ +"""蓝图生成过程日志:控制台 + 落盘,便于对账。 + +环境变量: +- GENERATE_LOG_DIR:日志目录(默认仓库 .runtime/logs/generate) +- GENERATE_LOG_LEVEL:DEBUG/INFO/WARNING(默认 INFO) +""" +from __future__ import annotations + +import logging +import os +import threading +import uuid +from contextvars import ContextVar +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +_root = Path(__file__).resolve().parents[1] +_LOGGER_NAME = "aijianzhan.generate" +_setup_lock = threading.Lock() +_configured = False + +_current: ContextVar["GenerateTrace | None"] = ContextVar("generate_trace", default=None) + + +def _log_dir() -> Path: + raw = (os.getenv("GENERATE_LOG_DIR") or "").strip() + if raw: + return Path(raw).expanduser().resolve() + return (_root / ".runtime" / "logs" / "generate").resolve() + + +def _level() -> int: + name = (os.getenv("GENERATE_LOG_LEVEL") or "INFO").strip().upper() + return getattr(logging, name, logging.INFO) + + +def ensure_logging() -> logging.Logger: + """幂等配置:stdout + 按日滚动的 generate.log。""" + global _configured + logger = logging.getLogger(_LOGGER_NAME) + if _configured: + return logger + with _setup_lock: + if _configured: + return logger + logger.setLevel(_level()) + logger.propagate = False + fmt = logging.Formatter( + "%(asctime)s [%(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + sh = logging.StreamHandler() + sh.setLevel(_level()) + sh.setFormatter(fmt) + logger.addHandler(sh) + try: + d = _log_dir() + d.mkdir(parents=True, exist_ok=True) + fh = logging.FileHandler(d / "generate.log", encoding="utf-8") + fh.setLevel(_level()) + fh.setFormatter(fmt) + logger.addHandler(fh) + except OSError as e: + logger.warning("无法写入生成日志目录: %s", e) + _configured = True + return logger + + +class GenerateTrace: + """单次 /apps/generate 请求的对账轨迹。""" + + def __init__(self, run_id: str | None = None) -> None: + self.run_id = run_id or uuid.uuid4().hex[:10] + self.started_at = datetime.now(timezone.utc).isoformat() + self.lines: list[dict[str, Any]] = [] + self._token = None + self._logger = ensure_logging() + self._run_file: Path | None = None + try: + d = _log_dir() + d.mkdir(parents=True, exist_ok=True) + self._run_file = d / f"run_{self.run_id}.log" + except OSError: + self._run_file = None + + def __enter__(self) -> "GenerateTrace": + self._token = _current.set(self) + self.info("generate.start", run_id=self.run_id) + return self + + def __exit__(self, exc_type, exc, tb) -> None: + if exc_type is not None: + self.error(f"generate.failed: {exc}", error=str(exc)) + else: + self.info("generate.done") + if self._token is not None: + _current.reset(self._token) + return False + + def _emit(self, level: str, message: str, **fields: Any) -> None: + ts = datetime.now(timezone.utc).isoformat() + entry = {"ts": ts, "level": level, "message": message} + if fields: + # 仅保留可 JSON 序列化的简单字段 + clean: dict[str, Any] = {} + for k, v in fields.items(): + if v is None: + continue + if isinstance(v, (str, int, float, bool)): + clean[k] = v + elif isinstance(v, (list, tuple)) and all( + isinstance(x, (str, int, float, bool)) for x in v + ): + clean[k] = list(v) + else: + clean[k] = str(v)[:500] + entry.update(clean) + self.lines.append(entry) + + extra = " ".join(f"{k}={v}" for k, v in entry.items() if k not in {"ts", "level", "message"}) + line = f"[{self.run_id}] {message}" + (f" | {extra}" if extra else "") + log_fn = getattr(self._logger, level if level in {"debug", "info", "warning", "error"} else "info") + log_fn(line) + if self._run_file is not None: + try: + with self._run_file.open("a", encoding="utf-8") as f: + f.write(f"{ts} [{level.upper()}] {line}\n") + except OSError: + pass + + def debug(self, message: str, **fields: Any) -> None: + self._emit("debug", message, **fields) + + def info(self, message: str, **fields: Any) -> None: + self._emit("info", message, **fields) + + def warning(self, message: str, **fields: Any) -> None: + self._emit("warning", message, **fields) + + def error(self, message: str, **fields: Any) -> None: + self._emit("error", message, **fields) + + def stage(self, name: str, detail: str = "", **fields: Any) -> None: + msg = f"stage.{name}" + (f": {detail}" if detail else "") + self.info(msg, stage=name, **fields) + + def note(self, text: str) -> None: + """把 warnings/notes 同步进日志。""" + t = (text or "").strip() + if t: + self.info(t) + + def notes(self, items: list[str] | None) -> None: + for n in items or []: + self.note(str(n)) + + def as_payload(self) -> dict[str, Any]: + return { + "run_id": self.run_id, + "started_at": self.started_at, + "log_file": str(self._run_file) if self._run_file else "", + "lines": list(self.lines), + } + + +def get_trace() -> GenerateTrace | None: + return _current.get() + + +def glog() -> GenerateTrace | None: + """当前请求轨迹;无上下文时返回 None(调用方需判空)。""" + return get_trace() diff --git a/ai-service/generation_rules.py b/ai-service/generation_rules.py new file mode 100644 index 0000000..0738fa7 --- /dev/null +++ b/ai-service/generation_rules.py @@ -0,0 +1,219 @@ +"""生成时由服务端注入的规则,对用户不可见。""" + +from __future__ import annotations + +import re +from typing import Any + +# 用户明确不要按截图还原时才退出忠实模式 +_SCREENSHOT_OPT_OUT = ( + "不要还原截图", + "不按截图", + "无需还原截图", + "忽略截图布局", + "自定义布局", + "改成通用后台", + "不要一模一样", + "重新设计界面", + "不要照搬截图", +) + +_SCREENSHOT_OPT_IN = ( + "还原截图", + "对齐截图", + "一模一样", + "截图还原", + "screenshot_faithful", +) + +DEFAULT_ACTION_LABELS: dict[str, str] = { + "create": "新增", + "refresh": "刷新", + "import": "导入 Excel", + "export": "导出 Excel", + "edit": "编辑", + "delete": "删除", + "save": "保存", + "cancel": "取消", + "back": "返回", + "search": "查询", +} + +# 较长别名优先匹配 +_ACTION_ALIASES: list[tuple[str, str]] = [ + ("导入 excel", "import"), + ("导出 excel", "export"), + ("导入xlsx", "import"), + ("导出xlsx", "export"), + ("导入csv", "import"), + ("导出csv", "export"), + ("导入", "import"), + ("导出", "export"), + ("新建", "create"), + ("新增", "create"), + ("添加", "create"), + ("创建", "create"), + ("刷新", "refresh"), + ("重载", "refresh"), + ("编辑", "edit"), + ("修改", "edit"), + ("删除", "delete"), + ("移除", "delete"), + ("保存", "save"), + ("取消", "cancel"), + ("返回", "back"), + ("查询", "search"), + ("搜索", "search"), + ("筛选", "search"), +] + +SYSTEM_RULES = """ +[系统规则 · 用户不可见] +你是业务前端生成器。输出 App Blueprint(entities / apis / pages / widgets)。 +适用于任意行业,禁止把所有应用生成成同一种通用绿主题 CRUD 壳。 + +还原原则(全局): +- 有截图/HTML:默认一模一样(meta.ui_preset=screenshot_faithful,dashboard layout.preset=screenshot_faithful)。 + 用户文字点名要改的才改;未提及的顶栏/导航/筛选/图表/表格/按键/配色保持与截图一致。 +- 生成闭环(有截图时由服务端自动执行):视觉模型摘录 → 代码模型出蓝图 → **发布并截取 Web 真页面** + → 视觉模型对照原图与真页面打分 → 按差异再改蓝图,直到还原度≥95% + (环境变量:FIDELITY_TARGET / FIDELITY_MAX_ROUNDS / FIDELITY_REAL_SCREEN)。 +- 无截图:严格按用户文字与数据字段还原;勿套默认「记录列表/概览」。 +- 业务名称、平台抬头、页签、按钮文案必须来自用户/截图/HTML,禁止擅自换成套话。 +- 尽量写全 meta.platform_title / project_context / ui.nav_items / ui.filter_radios / + ui.select_field / ui.filter_hint / ui.*_side_label / ui.table_title(无材料则留空,禁止编造行业词)。 + +其它: +- 功能按键写入 pages[].layout.actions + action_labels,原文显示。 +- 数据:Excel/CSV 表头→字段;JSON list→表;并行数组先展平再关联。snake_case;高基数不做 enum。 +- REST:GET/POST/PUT/DELETE;看板 widgets(kpi/line_chart/status_strip/table/bar/pie)按截图或需求取舍。 +- 全量导入:校验失败只记错误,不提前中断。 +""".strip() + +SCREENSHOT_RULES = """ +[截图忠实 · 用户不可见 · 适用于任意业务] +已上传界面截图。目标:展示页与截图一模一样(screenshot_faithful)。 +必须从截图提取并写入蓝图:平台抬头、系统名称、页签文案、筛选控件(单选/下拉)、工具栏按键原文、 +主区分区(含左侧竖标签文案)、图表类型与系列名、表格列名。 +主图为「着色测点/短棒 + 设计曲线」时:meta.ui.chart_style=section_marks,并写 +value_field / color_field / design_field / legend_items / y_axis_label / invert_y。 +监督条为上灰下绿双段时:status_strip.variant=stacked_days,value_field+secondary_field,cycle_days。 +禁止只有侧标「主图」却不配 line_chart widget 或让主图区空白。 +多张图若为同一页展开/收起/滚动态:合并为一套布局,以信息最完整的一帧为准,不要生成两套互斥页面。 +禁止套用其它行业模板(含铁路沉降专用文案),除非截图本身就是该业务。 +""".strip() + + +def screenshot_opted_out(prompt: str) -> bool: + p = prompt or "" + return any(k in p for k in _SCREENSHOT_OPT_OUT) + + +def wants_screenshot_layout(prompt: str, image_count: int, html_count: int = 0) -> bool: + """有截图或页面 HTML 即默认忠实还原;用户显式退出除外。""" + p = prompt or "" + if screenshot_opted_out(p): + return False + if image_count > 0 or html_count > 0: + return True + if any(k in p for k in _SCREENSHOT_OPT_IN): + return True + return False + + +def _map_button_label(raw: str) -> tuple[str, str] | None: + """返回 (action_key, display_label)。""" + label = re.sub(r"\s+", " ", (raw or "").strip()).strip("。.;;") + if not label or len(label) > 32: + return None + low = label.lower().replace(" ", "") + for alias, key in _ACTION_ALIASES: + a = alias.replace(" ", "") + if low == a or low.startswith(a) or a in low: + return key, label + slug = re.sub(r"[^a-z0-9_]+", "_", low)[:24].strip("_") or "action" + return f"custom_{slug}", label + + +def parse_action_bar(prompt: str) -> dict[str, Any]: + """从用户说明/截图理解中解析功能按键,得到 actions 顺序与 action_labels。""" + p = prompt or "" + labels: dict[str, str] = {} + order: list[str] = [] + + def add(raw: str) -> None: + mapped = _map_button_label(raw) + if not mapped: + return + key, lab = mapped + if key not in labels: + order.append(key) + labels[key] = lab + + for m in re.finditer( + r"(?:操作|功能按键|按键|按钮|工具栏|工具条)[::]\s*(.+)", + p, + ): + chunk = m.group(1).splitlines()[0] + chunk = re.split(r"[。;;((]", chunk, 1)[0] + for part in re.split(r"[、,,/||]\s*", chunk): + add(part) + + for m in re.finditer( + r"(?:包含|含有|提供|支持)[::]?\s*((?:新增|新建|刷新|导入|导出|编辑|删除|查询|保存)[^。\n]{0,40})", + p, + ): + for part in re.split(r"[、,,/||]\s*", m.group(1)): + add(part) + + for m in re.finditer(r"(?:工具栏|顶栏)?按钮[::\s]+(.+)", p): + chunk = m.group(1).splitlines()[0][:80] + for part in re.split(r"[、,,/||\s]{1,}", chunk): + if part.strip(): + add(part) + + if not order: + return {} + + for k in ("edit", "delete"): + if k not in labels: + labels[k] = DEFAULT_ACTION_LABELS[k] + + toolbar = [k for k in order if k not in ("edit", "delete")] + actions = toolbar + [k for k in ("edit", "delete") if k in labels] + seen: set[str] = set() + uniq: list[str] = [] + for k in actions: + if k not in seen: + seen.add(k) + uniq.append(k) + + return {"actions": uniq, "action_labels": labels, "toolbar_from_user": True} + + +def compose_effective_prompt( + user_prompt: str, + image_count: int, + vision_text: str = "", + html_layout_text: str = "", + html_count: int = 0, +) -> str: + """把系统规则拼进有效提示词;控制台只传用户原文。""" + parts = [SYSTEM_RULES] + if wants_screenshot_layout(user_prompt, image_count, html_count): + parts.append(SCREENSHOT_RULES) + if html_count: + parts.append( + "[HTML 布局优先 · 用户不可见]\n" + "已上传浏览器另存为的 HTML/MHTML。文案与控件名以 HTML 为准;分区与配色以截图为准。" + ) + user = (user_prompt or "").strip() + if user: + parts.append("[用户需求]\n" + user) + else: + parts.append("[用户需求]\n(未填写文字需求:仅依据数据文件、截图与 HTML 生成。)") + if html_layout_text: + parts.append(html_layout_text) + if vision_text: + parts.append("[截图理解]\n" + vision_text) + return "\n\n".join(parts) diff --git a/ai-service/html_layout.py b/ai-service/html_layout.py new file mode 100644 index 0000000..81f1f05 --- /dev/null +++ b/ai-service/html_layout.py @@ -0,0 +1,370 @@ +"""从 Ctrl+S 保存的 HTML / MHTML 提取布局真源,配合截图做更精确还原(行业无关)。""" + +from __future__ import annotations + +import email +import re +from email import policy +from html.parser import HTMLParser +from typing import Any + + +_SKIP_TAGS = {"script", "style", "noscript", "svg", "path"} +_MAX_TEXT = 48 +_NAV_NOISE = { + "欢迎您", + "退出", + "帮助", + "登录", + "注册", + "返回", + "首页", + "客服", + "电话", + "反馈", + "消息", + "系统消息", +} +# 泛化业务词:用于区分「姓名」与「页签」 +_BUSINESS_HINTS = ( + "列表", + "新增", + "创建", + "看板", + "概览", + "统计", + "报表", + "管理", + "设置", + "详情", + "汇总", + "筛选", + "查询", + "导入", + "导出", + "设备", + "人员", + "信息", + "数据", + "记录", + "测点", + "断面", + "工点", + "观测", + "订单", + "商品", + "库存", + "客户", +) + + +def _is_nav_noise(text: str) -> bool: + t = (text or "").strip() + if not t or t in _NAV_NOISE: + return True + if len(t) <= 3 and re.fullmatch(r"[\u4e00-\u9fff]{2,3}", t): + if not any(b in t for b in _BUSINESS_HINTS): + return True + return False + + +class _LayoutHTMLParser(HTMLParser): + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self.title = "" + self._in_title = False + self._skip = 0 + self.headings: list[str] = [] + self.nav_items: list[str] = [] + self.buttons: list[str] = [] + self.labels: list[str] = [] + self.table_headers: list[list[str]] = [] + self.radio_options: list[str] = [] + self._cur_th: list[str] = [] + self._in_th = False + self._in_nav = 0 + self._in_button = False + self._in_label = False + self._pending_radio = False + self._buf = "" + self.meta_desc = "" + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + t = tag.lower() + ad = {k.lower(): (v or "") for k, v in attrs} + if t in _SKIP_TAGS: + self._skip += 1 + return + if self._skip: + return + if t == "title": + self._in_title = True + self._buf = "" + elif t == "meta" and ad.get("name", "").lower() == "description": + self.meta_desc = (ad.get("content") or "")[:200] + elif t in {"nav", "header"} or "nav" in (ad.get("class") or "").lower() or ad.get("role") == "navigation": + self._in_nav += 1 + elif t in {"button"} or ad.get("role") == "button" or ( + t == "input" and ad.get("type") in {"button", "submit", "reset"} + ): + self._in_button = True + self._buf = ad.get("value") or ad.get("aria-label") or ad.get("title") or "" + elif t == "input" and ad.get("type") == "radio": + # 值若是纯数字/无意义,等后续文本当选项;有中文 value 则直接采用 + self._pending_radio = True + val = (ad.get("value") or "").strip() + if val and val not in {"", "on"} and not re.fullmatch(r"\d+", val) and len(val) <= 16: + self.radio_options.append(val) + elif t == "a" and self._in_nav: + self._buf = "" + elif t in {"h1", "h2", "h3", "h4"}: + self._buf = "" + elif t == "label": + self._in_label = True + self._buf = "" + elif t == "th": + self._in_th = True + self._buf = "" + elif t == "tr": + self._cur_th = [] + + def handle_endtag(self, tag: str) -> None: + t = tag.lower() + if t in _SKIP_TAGS: + if self._skip: + self._skip -= 1 + return + if self._skip: + return + text = re.sub(r"\s+", " ", self._buf or "").strip() + if t == "title": + self._in_title = False + if text: + self.title = text[:80] + elif t in {"nav", "header"}: + if self._in_nav: + self._in_nav -= 1 + elif t in {"button"} or self._in_button and t in {"input", "a", "span", "div"}: + if text and len(text) <= _MAX_TEXT: + self.buttons.append(text) + self._in_button = False + elif t == "a" and self._in_nav and text and len(text) <= _MAX_TEXT: + if not _is_nav_noise(text): + self.nav_items.append(text) + elif t in {"h1", "h2", "h3", "h4"} and text and len(text) <= 64: + if not _is_nav_noise(text): + self.headings.append(text) + elif t == "label": + self._in_label = False + cleaned = text.rstrip("::") + if cleaned and len(cleaned) <= _MAX_TEXT: + # 单选旁的短文案不要当导航噪声丢掉(如「全部」「类型A」) + if self._pending_radio and 1 <= len(cleaned) <= 12: + self.radio_options.append(cleaned) + elif not _is_nav_noise(cleaned): + self.labels.append(cleaned) + self._pending_radio = False + elif t == "th": + self._in_th = False + if text: + self._cur_th.append(text[:32]) + elif t == "tr" and self._cur_th: + self.table_headers.append(self._cur_th[:12]) + self._cur_th = [] + self._buf = "" + + def handle_data(self, data: str) -> None: + if self._skip: + return + if self._in_title or self._in_button or self._in_nav or self._in_th or self._in_label: + self._buf += data + return + self._buf += data + + +def _decode_bytes(raw: bytes) -> str: + for enc in ("utf-8", "gb18030", "gbk", "latin-1"): + try: + return raw.decode(enc) + except UnicodeDecodeError: + continue + return raw.decode("utf-8", errors="ignore") + + +def extract_html_from_mhtml(raw: bytes) -> str: + msg = email.message_from_bytes(raw, policy=policy.default) + if msg.is_multipart(): + for part in msg.walk(): + ctype = (part.get_content_type() or "").lower() + if ctype in {"text/html", "application/xhtml+xml"}: + try: + return part.get_content() + except Exception: # noqa: BLE001 + payload = part.get_payload(decode=True) or b"" + return _decode_bytes(payload) + text = _decode_bytes(raw) + i = text.lower().find("= 0: + return text[i:] + return text + + +def parse_layout_file(filename: str, content: bytes) -> dict[str, Any]: + name = (filename or "").lower() + if name.endswith((".mhtml", ".mht")): + html = extract_html_from_mhtml(content) + source = "mhtml" + elif name.endswith((".html", ".htm", ".xhtml")): + html = _decode_bytes(content) + source = "html" + else: + html = _decode_bytes(content) + source = "text" + + if len(html) > 2_000_000: + html = html[:2_000_000] + + parser = _LayoutHTMLParser() + try: + parser.feed(html) + parser.close() + except Exception as e: # noqa: BLE001 + return {"ok": False, "error": str(e), "filename": filename, "source": source} + + # 兜底:radio 后紧跟的短文案(常见 value=数字 + 中文标签) + for m in re.finditer( + r'type=["\']radio["\'][^>]*>\s*(?:<[^>]+>\s*)*([\u4e00-\u9fffA-Za-z][\u4e00-\u9fffA-Za-z0-9]{0,11})', + html, + flags=re.I, + ): + lab = m.group(1).strip() + if lab and not re.fullmatch(r"\d+", lab): + parser.radio_options.append(lab) + + def uniq(items: list[str], limit: int = 40) -> list[str]: + seen: set[str] = set() + out: list[str] = [] + for x in items: + x = x.strip() + if not x or x in seen: + continue + seen.add(x) + out.append(x) + if len(out) >= limit: + break + return out + + ths = parser.table_headers[0] if parser.table_headers else [] + return { + "ok": True, + "filename": filename, + "source": source, + "title": parser.title, + "meta_description": parser.meta_desc, + "headings": uniq(parser.headings, 20), + "nav_items": uniq(parser.nav_items, 24), + "buttons": uniq(parser.buttons, 30), + "labels": uniq(parser.labels, 30), + "radio_options": uniq(parser.radio_options, 12), + "table_headers": uniq(ths, 16), + "table_header_rows": parser.table_headers[:3], + } + + +def layout_summary_text(parsed: dict[str, Any]) -> str: + if not parsed or not parsed.get("ok"): + err = (parsed or {}).get("error") or "parse failed" + return f"[页面 HTML 解析失败] {err}" + lines = [ + "[页面 HTML 布局真源 · 优先于猜测 · 禁止套用其它行业模板]", + f"文件: {parsed.get('filename')} ({parsed.get('source')})", + ] + if parsed.get("title"): + lines.append(f"文档标题: {parsed['title']}") + if parsed.get("headings"): + lines.append("标题层级: " + " / ".join(parsed["headings"][:12])) + if parsed.get("nav_items"): + lines.append("导航页签: " + "、".join(parsed["nav_items"][:16])) + if parsed.get("buttons"): + lines.append("操作按键: " + "、".join(parsed["buttons"][:20])) + if parsed.get("labels"): + lines.append("分区/筛选项标签: " + "、".join(parsed["labels"][:20])) + if parsed.get("radio_options"): + lines.append("单选选项: " + "、".join(parsed["radio_options"][:12])) + if parsed.get("table_headers"): + lines.append("表格列: " + "、".join(parsed["table_headers"][:16])) + lines.append( + "要求:pages 标题、导航、action_labels、表格列、筛选文案必须优先采用上述 HTML 原文;" + "meta.ui_preset=screenshot_faithful;文案以 HTML 为准、分区以截图为准;" + "禁止擅自换成其它业务的固定文案。" + ) + return "\n".join(lines) + + +def merge_layout_into_ui_hints(hints: dict[str, Any], parsed: dict[str, Any]) -> dict[str, Any]: + """把 HTML 解析结果并入 ui_hints。只使用文件里的原文,不写死行业词。""" + if not parsed or not parsed.get("ok"): + return hints + ui = hints.setdefault("ui", {}) + + if not hints.get("app_name") and parsed.get("title"): + title = str(parsed["title"]) + parts = re.split(r"\s*[-_||]\s*", title) + if parts: + cand = parts[0][:64] + if cand and not _is_nav_noise(cand): + hints["app_name"] = cand + if len(parts) > 1 and "platform_title" not in ui: + ui["platform_title"] = parts[-1][:64] + + labels = [x for x in (parsed.get("labels") or []) if x and not _is_nav_noise(x)] + # 按出现顺序把较长分区标签分给 图 / 条 / 表(通用,不认行业) + region_labels = [x for x in labels if len(x) >= 4 and not x.startswith("(")] + if region_labels: + if len(region_labels) >= 1: + ui.setdefault("chart_side_label", region_labels[0]) + hints.setdefault("chart_title", region_labels[0]) + if len(region_labels) >= 2: + ui.setdefault("strip_side_label", region_labels[1]) + hints.setdefault("strip_title", region_labels[1]) + if len(region_labels) >= 3: + ui.setdefault("table_side_label", region_labels[2]) + # 表格横标题:优先含「表」或最长的后续标签 + for lab in reversed(region_labels): + if "表" in lab or len(lab) >= 8: + hints.setdefault("table_title", lab) + ui.setdefault("table_title", lab) + break + + nav = [x for x in (parsed.get("nav_items") or []) if not _is_nav_noise(x)] + if nav: + ui["nav_items"] = nav[:16] + for n in nav: + if any(k in n for k in ("看板", "概览", "统计", "dashboard")): + hints.setdefault("dash_title", n) + elif any(k in n for k in ("新增", "创建", "新建")): + hints.setdefault("create_title", n) + elif any(k in n for k in ("列表", "明细", "记录", "数据", "测点", "订单", "商品")): + hints.setdefault("list_title", n) + + if parsed.get("buttons"): + hints["_html_buttons_line"] = "操作:" + "、".join(parsed["buttons"][:20]) + if parsed.get("table_headers"): + ui["table_headers"] = parsed["table_headers"] + if labels: + ui.setdefault("filter_labels", labels[:12]) + + radios = [ + x + for x in (parsed.get("radio_options") or []) + if x + and x not in {"全部", "all", "All"} + and not re.fullmatch(r"\d+", x) + and len(x) <= 12 + ] + if len(radios) >= 2: + ui.setdefault("section_options", radios[:8]) + ui.setdefault("filter_radios", radios[:8]) + ui.setdefault("filter_style", "section_radios") + + return hints diff --git a/ai-service/llm.py b/ai-service/llm.py new file mode 100644 index 0000000..39962cf --- /dev/null +++ b/ai-service/llm.py @@ -0,0 +1,418 @@ +"""多厂商 LLM:供应商表从 etc/llm.yaml 加载(密钥仍走环境变量)。""" + +from __future__ import annotations + +import base64 +import json +import os +from functools import lru_cache +from pathlib import Path +from typing import Any + +import httpx +import yaml + +_DEFAULT_CONFIG_CANDIDATES = ( + Path(__file__).resolve().parent / "etc" / "llm.yaml", + Path("/app/etc/llm.yaml"), +) + + +def _config_path() -> Path: + for key in ("AI_CONFIG_PATH", "LLM_CONFIG_PATH"): + raw = (os.getenv(key) or "").strip() + if raw: + return Path(raw) + for p in _DEFAULT_CONFIG_CANDIDATES: + if p.is_file(): + return p + return _DEFAULT_CONFIG_CANDIDATES[0] + + +@lru_cache(maxsize=1) +def load_ai_config() -> dict[str, Any]: + path = _config_path() + if not path.is_file(): + raise FileNotFoundError( + f"AI 配置文件不存在: {path}(可用 AI_CONFIG_PATH / LLM_CONFIG_PATH 指定)" + ) + with path.open("r", encoding="utf-8") as f: + data = yaml.safe_load(f) or {} + if not isinstance(data, dict): + raise ValueError(f"AI 配置格式错误(须为 mapping): {path}") + providers = data.get("providers") or {} + if not isinstance(providers, dict) or not providers: + raise ValueError(f"AI 配置缺少 providers: {path}") + return data + + +def reload_ai_config() -> dict[str, Any]: + """供热加载 / 测试清空缓存。""" + load_ai_config.cache_clear() + return load_ai_config() + + +def get_providers() -> dict[str, dict[str, Any]]: + raw = load_ai_config().get("providers") or {} + out: dict[str, dict[str, Any]] = {} + for pid, meta in raw.items(): + if not isinstance(meta, dict): + continue + out[str(pid).strip().lower()] = dict(meta) + return out + + +# 兼容旧代码:PROVIDERS 为属性式访问,始终读当前配置 +class _ProvidersProxy(dict): + def _sync(self) -> None: + self.clear() + self.update(get_providers()) + + def __contains__(self, key: object) -> bool: # type: ignore[override] + self._sync() + return dict.__contains__(self, key) + + def __getitem__(self, key: str) -> dict[str, Any]: + self._sync() + return dict.__getitem__(self, key) + + def get(self, key: str, default: Any = None) -> Any: # type: ignore[override] + self._sync() + return dict.get(self, key, default) + + def items(self): # type: ignore[override] + self._sync() + return dict.items(self) + + def keys(self): # type: ignore[override] + self._sync() + return dict.keys(self) + + def values(self): # type: ignore[override] + self._sync() + return dict.values(self) + + +PROVIDERS: dict[str, dict[str, Any]] = _ProvidersProxy() # type: ignore[assignment] + + +def _default_provider_id() -> str: + cfg = load_ai_config() + return ( + (os.getenv("LLM_PROVIDER") or "").strip().lower() + or str(cfg.get("default_provider") or "deepseek").strip().lower() + or "deepseek" + ) + + +def _aliases() -> dict[str, str]: + raw = load_ai_config().get("aliases") or {} + return {str(k).strip().lower(): str(v).strip().lower() for k, v in raw.items()} + + +def _vision_cfg() -> dict[str, Any]: + v = load_ai_config().get("vision") or {} + return v if isinstance(v, dict) else {} + + +def _normalize_pid(provider_id: str | None, *, aliases: dict[str, str] | None = None) -> str: + pid = (provider_id or "").strip().lower() + amap = aliases if aliases is not None else _aliases() + if pid in amap: + pid = amap[pid] + return pid + + +def list_providers() -> list[dict[str, Any]]: + out: list[dict[str, Any]] = [] + for pid, meta in get_providers().items(): + key_env = meta.get("api_key_env") or "" + configured = True + if key_env: + configured = bool(os.getenv(str(key_env), "").strip()) + out.append( + { + "id": pid, + "label": meta.get("label") or pid, + "configured": configured, + "default_model": meta.get("default_model") or "", + "models": meta.get("models") or [], + "supports_vision": bool(meta.get("supports_vision")), + } + ) + return out + + +def resolve_provider(provider_id: str | None, model: str | None = None) -> tuple[str, dict[str, Any], str]: + """返回 (provider_id, meta, model_name)。""" + providers = get_providers() + pid = _normalize_pid(provider_id) or _default_provider_id() + if pid not in providers: + fallback = _default_provider_id() + pid = fallback if fallback in providers else next(iter(providers)) + meta = dict(providers[pid]) + # 单供应商可被环境变量覆盖 base_url(如 DASHSCOPE_BASE_URL) + env_base_key = f"{pid.upper()}_BASE_URL" + env_base = (os.getenv(env_base_key) or "").strip() + if env_base: + meta["base_url"] = env_base + elif pid == "dashscope": + # 兼容旧变量名 + legacy = (os.getenv("DASHSCOPE_BASE_URL") or "").strip() + if legacy: + meta["base_url"] = legacy + vision = _vision_cfg() + if pid == (str(vision.get("provider") or "dashscope").strip().lower()): + if (os.getenv("VISION_MODEL") or "").strip() and not (model or "").strip(): + meta["default_model"] = os.getenv("VISION_MODEL", "").strip() + model_name = (model or "").strip() or str(meta.get("default_model") or "") + return pid, meta, model_name + + +def resolve_vision_provider( + provider_id: str | None = None, + model: str | None = None, +) -> tuple[str, dict[str, Any], str]: + """优先配置 vision / 环境变量 VISION_*;禁止把纯文本模型名传给视觉通道。""" + providers = get_providers() + vision = _vision_cfg() + vision_aliases = { + **_aliases(), + **{ + str(k).strip().lower(): str(v).strip().lower() + for k, v in (vision.get("aliases") or {}).items() + }, + } + env_pid = (os.getenv("VISION_PROVIDER") or "").strip().lower() + cfg_pid = str(vision.get("provider") or "").strip().lower() + vision_pid = _normalize_pid(env_pid or provider_id or cfg_pid, aliases=vision_aliases) + + env_model = (os.getenv("VISION_MODEL") or "").strip() + cfg_model = str(vision.get("model") or "").strip() + passed = (model or "").strip() + hints = [str(h).lower() for h in (load_ai_config().get("vision_model_hints") or [])] + if not hints: + hints = ["vl", "vision", "qwen3", "qwen-vl", "gpt-4o", "gemini"] + vision_like = bool( + passed + and any(k in passed.lower() for k in hints) + and "deepseek" not in passed.lower() + and passed.lower() != "chat" + ) + vision_model = env_model or cfg_model or (passed if vision_like else "") or "" + + def _has_key(pid: str) -> bool: + meta = providers.get(pid) or {} + env = meta.get("api_key_env") or "" + return bool(env) and bool(os.getenv(str(env), "").strip()) + + # 文本供应商无视觉时,回退到配置的视觉供应商(若已配密钥) + if vision_pid in providers and not providers[vision_pid].get("supports_vision"): + fallback = _normalize_pid(cfg_pid or "dashscope", aliases=vision_aliases) + if fallback in providers and providers[fallback].get("supports_vision") and _has_key(fallback): + vision_pid = fallback + else: + vision_pid = "" + + if vision_pid and vision_pid in providers and providers[vision_pid].get("supports_vision"): + return resolve_provider(vision_pid, vision_model or None) + + pid, meta, _ = resolve_provider(provider_id, None) + if meta.get("supports_vision"): + return resolve_provider(pid, vision_model or None) + + fallback = _normalize_pid(cfg_pid or "dashscope", aliases=vision_aliases) + if fallback in providers and providers[fallback].get("supports_vision") and _has_key(fallback): + return resolve_provider(fallback, vision_model or None) + return pid, meta, vision_model or str(meta.get("default_model") or "") + + +def _api_key(meta: dict[str, Any]) -> str: + env = meta.get("api_key_env") or "" + if not env: + return "" + return os.getenv(str(env), "").strip() + + +def enhance_blueprint_with_llm( + draft: dict[str, Any], + prompt: str, + excel_summary: str, + provider_id: str | None = None, + model: str | None = None, +) -> tuple[dict[str, Any], list[str]]: + pid, meta, model_name = resolve_provider(provider_id, model) + if pid == "heuristic": + return draft, ["使用本地启发式蓝图(未调用大模型)"] + + api_key = _api_key(meta) + if not api_key: + env = meta.get("api_key_env") or "" + return draft, [f"未配置 {env},回退启发式蓝图"] + + base = (meta.get("base_url") or "").rstrip("/") + system = ( + "你是低代码 CMS 蓝图助手。只能输出 JSON 对象,字段必须兼容现有 draft 结构。" + "field.type 仅允许: string,text,int,bigint,decimal,boolean,date,datetime,enum,json,file_ref。" + "所有 name/slug/table/path 必须是 snake_case(小写字母数字下划线),禁止驼峰与连字符。" + "可优化 meta.name/description、field.label、pages.title;不要发明新 type,不要输出 SQL。" + "优先在给定 draft 上微调,保留 entities/fields/apis/pages 结构完整。" + "若 draft.meta.ui_preset 或页面 layout.preset 为 screenshot_faithful,必须原样保留," + "禁止改成通用后台壳;用户未声明修改的标题/分区/筛选/图表/功能按键文案不得擅自更换。" + "若 layout.action_labels 已给出按钮原文,必须保留;actions 顺序也尽量保留。" + "页面形态须跟随用户需求与截图还原,禁止所有应用统一成同一种列表模板;" + "禁止把任意业务改写成固定行业文案(如铁路沉降),除非用户或截图本身如此。" + ) + user = { + "user_prompt": prompt, + "excel_summary": excel_summary, + "draft": draft, + "instruction": "返回完整 draft JSON(不要 markdown)", + } + body: dict[str, Any] = { + "model": model_name, + "temperature": 0.2, + "messages": [ + {"role": "system", "content": system}, + {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, + ], + } + if meta.get("json_mode"): + body["response_format"] = {"type": "json_object"} + + try: + with httpx.Client(timeout=90.0) as client: + resp = client.post( + f"{base}/chat/completions", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=body, + ) + resp.raise_for_status() + content = resp.json()["choices"][0]["message"]["content"] + text = content.strip() + if text.startswith("```"): + text = text.strip("`") + if text.startswith("json"): + text = text[4:].lstrip() + enhanced = json.loads(text) + if "version" in enhanced and "entities" in enhanced: + enhanced.setdefault("storage", draft.get("storage")) + enhanced.setdefault("security", draft.get("security")) + enhanced.setdefault("apis", draft.get("apis")) + return enhanced, [f"已使用 {meta.get('label', pid)}/{model_name} 润色蓝图"] + if "draft" in enhanced and isinstance(enhanced["draft"], dict): + d = enhanced["draft"] + d.setdefault("storage", draft.get("storage")) + d.setdefault("security", draft.get("security")) + return d, [f"已使用 {meta.get('label', pid)}/{model_name} 润色蓝图"] + return draft, ["LLM 返回结构无效,回退启发式"] + except Exception as e: # noqa: BLE001 + return draft, [f"{meta.get('label', pid)} 调用失败,回退启发式: {e}"] + + +_VISION_PROMPT = """这些是目标业务系统的界面截图。请用中文输出「可直接写入领域说明」的结构化要点,务必逐字抄录可见文案,不要臆造。 + +多图规则(重要): +- 若多张图是**同一页面**的不同状态(展开/收起某一区、滚动到不同里程、筛选前后、空表/有数据),视为**一个界面**,合并描述,不要当成多个互斥布局。 +- 以「信息最完整」的那张为主结构(例如展开后能看见主图+监督条+表);收起态只补充「可折叠/可滚动」交互,不要因此删掉展开态才有的分区。 +- 若文案冲突,优先采用更清晰、更完整的一帧;并在备注里写「同页多状态」。 +- 仅当明显是不同页面(不同顶栏系统名/不同主导航页)时,才分页面描述。 + +按下列小标题组织(若没有的写「未见」): +### 顶栏 +- 左侧平台抬头、中间系统名、右侧链接原文 +### 次级导航 +- 页签原文(顿号分隔) +### 筛选条 +- 工程/业务上下文原文 +- 统计文案(个数类,原文照抄) +- 单选选项原文(全部 / …) +- 下拉标签原文 +- 右侧提示原文(若有) +### 主图区 +- 左侧竖排标签原文 +- 图例系列名原文 +- 图表类型与坐标大致含义 +### 状态条/副图 +- 左侧竖排标签原文 +- 形态:单色格 / 双段条 / 其它(据实描述) +- 条上是否显示类目文字 +- 是否可折叠/收起(若另一张图为收起态请注明) +### 表格区 +- 左侧竖排标签、表头横标题、列名原文 +### 操作按键 +- 格式:操作:……、…… +### 多图关系(若有多张) +- 一句话说明:同页展开/收起,或不同页 + +用户补充需求:{prompt} +""" + + +def understand_images( + prompt: str, + images: list[tuple[str, bytes, str]], + provider_id: str | None = None, + model: str | None = None, +) -> tuple[str, list[str]]: + """返回补充描述 + warnings。无视觉能力时回退 VISION_PROVIDER / 配置中的视觉供应商。""" + warnings: list[str] = [] + if not images: + return "", warnings + + names = [n for n, _, _ in images] + hint = ( + f"用户上传了界面截图: {', '.join(names)}。" + "默认按截图原样还原(screenshot_faithful):顶栏、页签、筛选、工具栏功能按键原文、图表分区、表格列均与原图一致;" + "仅用户文字明确要求修改的部分可调整。" + ) + + pid, meta, model_name = resolve_vision_provider(provider_id, model) + if not meta.get("supports_vision"): + warnings.append( + f"当前视觉通道 {pid or '无'} 不支持看图;请在 etc/llm.yaml 配置 vision.provider 并设置对应 API Key" + ) + return hint, warnings + + api_key = _api_key(meta) + if not api_key: + env = meta.get("api_key_env") or "" + warnings.append(f"未配置 {env},截图仅作占位提示") + return hint, warnings + + base = (meta.get("base_url") or "").rstrip("/") + content: list[dict[str, Any]] = [ + {"type": "text", "text": _VISION_PROMPT.format(prompt=prompt or "(无额外文字)")} + ] + for name, raw, mime in images: + b64 = base64.b64encode(raw).decode("ascii") + content.append( + { + "type": "image_url", + "image_url": {"url": f"data:{mime or 'image/png'};base64,{b64}"}, + } + ) + _ = name + + body = { + "model": model_name, + "temperature": 0.1, + "messages": [{"role": "user", "content": content}], + } + try: + with httpx.Client(timeout=120.0) as client: + resp = client.post( + f"{base}/chat/completions", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=body, + ) + resp.raise_for_status() + text = (resp.json()["choices"][0]["message"]["content"] or "").strip() + if not text: + return hint, warnings + ["视觉模型返回空内容"] + return f"{hint}\n\n【截图理解】\n{text}", warnings + [ + f"已用 {meta.get('label', pid)}/{model_name} 理解截图" + ] + except Exception as e: # noqa: BLE001 + warnings.append(f"看图失败: {e}") + return hint, warnings diff --git a/ai-service/make_sample_excel.py b/ai-service/make_sample_excel.py new file mode 100644 index 0000000..e82dc33 --- /dev/null +++ b/ai-service/make_sample_excel.py @@ -0,0 +1,14 @@ +"""生成测试用库存 Excel""" +from openpyxl import Workbook +from pathlib import Path + +wb = Workbook() +ws = wb.active +ws.title = "库存" +ws.append(["SKU", "商品名称", "仓库", "数量", "单价", "状态", "更新时间"]) +ws.append(["A-1001", "无线鼠标", "华东仓", 120, 59.9, "在售", "2026-07-01 10:00:00"]) +ws.append(["A-1002", "机械键盘", "华南仓", 35, 299.0, "在售", "2026-07-02 11:00:00"]) +ws.append(["B-2001", "显示器支架", "华北仓", 8, 129.0, "停售", "2026-07-03 12:00:00"]) +out = Path(__file__).resolve().parent / "sample_inventory.xlsx" +wb.save(out) +print(out) diff --git a/ai-service/preview_store.py b/ai-service/preview_store.py new file mode 100644 index 0000000..834d7c6 --- /dev/null +++ b/ai-service/preview_store.py @@ -0,0 +1,97 @@ +"""草稿预览:未发布蓝图的内部可访问快照(供 /#/preview/{id} 与真页面截图)。""" +from __future__ import annotations + +import json +import threading +import time +import uuid +from pathlib import Path +from typing import Any + +_lock = threading.Lock() +_store: dict[str, dict[str, Any]] = {} +_TTL_SEC = 3600 +_disk = (Path(__file__).resolve().parent / ".runtime" / "preview").resolve() +_alt_disk = (Path(__file__).resolve().parents[1] / ".runtime" / "preview").resolve() + + +def _preview_dirs() -> list[Path]: + dirs = [_disk] + if _alt_disk != _disk: + dirs.append(_alt_disk) + return dirs + + +def _purge() -> None: + now = time.time() + dead = [k for k, v in _store.items() if now - float(v.get("ts") or 0) > _TTL_SEC] + for k in dead: + _store.pop(k, None) + for d in _preview_dirs(): + p = d / f"{k}.json" + if p.is_file(): + try: + p.unlink() + except OSError: + pass + + +def create_preview( + blueprint: dict[str, Any], + *, + rows: list[dict[str, Any]] | None = None, + resource: str = "", +) -> str: + """写入预览包,返回 preview_id。""" + pid = uuid.uuid4().hex[:12] + res = resource + if not res: + apis = (blueprint.get("apis") or {}).get("resources") or [] + if apis and isinstance(apis[0], dict): + res = str(apis[0].get("path") or "records").lstrip("/") + else: + res = "records" + pack = { + "id": pid, + "ts": time.time(), + "blueprint": blueprint, + "rows": list(rows or [])[:800], + "resource": res, + } + with _lock: + _purge() + _store[pid] = pack + for d in _preview_dirs(): + try: + d.mkdir(parents=True, exist_ok=True) + (d / f"{pid}.json").write_text( + json.dumps(pack, ensure_ascii=False), + encoding="utf-8", + ) + except OSError: + continue + return pid + + +def get_preview(preview_id: str) -> dict[str, Any] | None: + pid = (preview_id or "").strip() + if not pid: + return None + with _lock: + _purge() + pack = _store.get(pid) + if pack: + return pack + for d in _preview_dirs(): + p = d / f"{pid}.json" + if not p.is_file(): + continue + try: + pack = json.loads(p.read_text(encoding="utf-8")) + if isinstance(pack, dict): + with _lock: + _store[pid] = pack + return pack + except (OSError, json.JSONDecodeError): + continue + return None diff --git a/ai-service/requirements.txt b/ai-service/requirements.txt new file mode 100644 index 0000000..946cc35 --- /dev/null +++ b/ai-service/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.115.6 +uvicorn[standard]==0.32.1 +python-multipart==0.0.17 +openpyxl==3.1.5 +httpx==0.28.1 +python-dotenv>=1.1.0 +PyYAML>=6.0.1 diff --git a/ai-service/sample_inventory.xlsx b/ai-service/sample_inventory.xlsx new file mode 100644 index 0000000..9f22cf8 Binary files /dev/null and b/ai-service/sample_inventory.xlsx differ diff --git a/ai-service/shot_worker.py b/ai-service/shot_worker.py new file mode 100644 index 0000000..219b473 --- /dev/null +++ b/ai-service/shot_worker.py @@ -0,0 +1,125 @@ +"""Standalone Playwright screenshot worker (fresh process, Windows-safe). + +Invoked by fidelity_loop.capture_real_app_screenshot — never import uvicorn here. +""" +from __future__ import annotations + +import asyncio +import base64 +import json +import os +import sys +from pathlib import Path + + +def _force_proactor() -> None: + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + +async def _shot_async(payload: dict) -> tuple[bytes | None, str, str | None]: + from playwright.async_api import async_playwright + + draft = payload.get("draft") or {} + rows = payload.get("rows") or [] + web = (os.getenv("WEB_BASE") or "http://127.0.0.1:5173").rstrip("/") + goto_timeout = int(os.getenv("FIDELITY_GOTO_TIMEOUT_MS") or "45000") + slug = str(((draft.get("meta") or {}).get("slug")) or "preview") + preview_id = str(payload.get("preview_id") or "preview") + inject_payload = { + "ok": True, + "id": preview_id, + "blueprint": draft, + "rows": rows, + "resource": payload.get("resource") or "records", + } + inject_js = ( + "try{localStorage.removeItem('ajz_session');}catch(e){}" + "window.__AJZ_PREVIEW__ = " + + json.dumps(inject_payload, ensure_ascii=False) + + ";" + ) + target = f"{web}/#/preview/{preview_id}" + + def fmt(err: BaseException) -> str: + msg = str(err).strip() or repr(err) + return msg.replace("\n", " ")[:500] + + try: + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + try: + page = await browser.new_page(viewport={"width": 1600, "height": 1100}) + await page.add_init_script(inject_js) + + async def fulfill(route): + await route.fulfill( + status=200, + content_type="application/json; charset=utf-8", + body=json.dumps(inject_payload, ensure_ascii=False).encode("utf-8"), + ) + + await page.route("**/api/v1/preview/**", fulfill) + await page.route("**/ai/api/v1/preview/**", fulfill) + await page.goto(target, wait_until="domcontentloaded", timeout=goto_timeout) + await page.evaluate(inject_js) + try: + await page.wait_for_selector( + ".sf-shell-bar, .sf-root, .gen-app-ops, .gen-app, .sf-line-wrap", + timeout=45000, + ) + except Exception: + try: + snip = (await page.locator("body").inner_text(timeout=2000))[:240] + except Exception: + snip = page.url + raise RuntimeError( + f"preview shell missing. page={page.url} text={snip!r}" + ) from None + login_btns = page.locator("button").filter(has_text="登录") + shell = page.locator(".sf-shell-bar, .sf-root, .gen-app") + if await login_btns.count() and not await shell.count(): + raise RuntimeError("preview fell back to login") + await page.wait_for_timeout(2500) + if await page.locator(".gen-app").count(): + png = await page.locator(".gen-app").first.screenshot(type="png") + else: + png = await page.screenshot(type="png", full_page=True) + finally: + await browser.close() + return ( + png, + f"已截取草稿预览(/#/preview/{preview_id},注入数据无需再拉 AI)参与对比", + slug, + ) + except Exception as e: # noqa: BLE001 + return None, f"真页面截图失败: preview={fmt(e)}(目标 {target})", slug + + +def main() -> int: + _force_proactor() + inp = Path(os.environ["FIDELITY_SHOT_INPUT"]) + outp = Path(os.environ["FIDELITY_SHOT_OUTPUT"]) + payload = json.loads(inp.read_text(encoding="utf-8")) + png, note, slug = asyncio.run(_shot_async(payload)) + out_dir = os.getenv("FIDELITY_SHOT_DIR") or "" + if png and out_dir: + d = Path(out_dir) + d.mkdir(parents=True, exist_ok=True) + (d / f"preview_{payload.get('preview_id') or 'x'}.png").write_bytes(png) + outp.write_text( + json.dumps( + { + "png_b64": base64.b64encode(png).decode("ascii") if png else None, + "note": note, + "slug": slug, + }, + ensure_ascii=False, + ), + encoding="utf-8", + ) + return 0 if png else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ai-service/url_capture.py b/ai-service/url_capture.py new file mode 100644 index 0000000..c9f53e0 --- /dev/null +++ b/ai-service/url_capture.py @@ -0,0 +1,170 @@ +"""解析首页抓包 url.config:key/value 成对,对应菜单/图表/统计等接口回包。""" + +from __future__ import annotations + +import json +import re +from html.parser import HTMLParser +from typing import Any + + +class _MenuHTMLParser(HTMLParser): + def __init__(self) -> None: + super().__init__(convert_charrefs=True) + self.items: list[str] = [] + self._in_a = False + self._buf = "" + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if tag.lower() == "a": + self._in_a = True + self._buf = "" + + def handle_endtag(self, tag: str) -> None: + if tag.lower() == "a" and self._in_a: + text = re.sub(r"\s+", " ", self._buf).strip() + if text: + self.items.append(text) + self._in_a = False + self._buf = "" + + def handle_data(self, data: str) -> None: + if self._in_a: + self._buf += data + + +def parse_url_config(content: bytes | str) -> dict[str, Any]: + """ + 格式示例: + key https://.../left!newleftmenu.action?... + value + + key https://.../hightchartCjlAndJianDu.action?... + value { ...json... } + """ + if isinstance(content, bytes): + text = content.decode("utf-8-sig", errors="ignore") + if not text.strip(): + for enc in ("gb18030", "utf-16", "utf-16-le"): + try: + text = content.decode(enc) + if text.strip(): + break + except Exception: # noqa: BLE001 + continue + else: + text = content + + text = text.replace("\r\n", "\n").replace("\r", "\n") + # 按「行首 key 」切开 + parts = re.split(r"(?m)^key\s+", text) + pairs: list[dict[str, Any]] = [] + for part in parts: + part = part.strip() + if not part: + continue + # 第一行 URL,其后 value 段 + if "\n" in part: + first, rest = part.split("\n", 1) + else: + first, rest = part, "" + url = first.strip() + m = re.match(r"(?is)^value\s*(.*)\Z", rest.strip(), flags=re.S) + body = (m.group(1) if m else rest).strip() + # 去掉仅有的空白 + if not body: + body = "" + action = "" + am = re.search(r"!([a-zA-Z0-9_]+)\.action", url) + if am: + action = am.group(1) + else: + am = re.search(r"/([a-zA-Z0-9_!]+)\.action", url) + if am: + action = am.group(1).split("!")[-1] + kind = "empty" + parsed: Any = None + if not body: + kind = "empty" + elif body.lstrip().startswith("{") or body.lstrip().startswith("["): + kind = "json" + try: + parsed = json.loads(body) + except json.JSONDecodeError: + kind = "json_invalid" + parsed = None + elif "<" in body and ">" in body: + kind = "html" + parsed = body + else: + kind = "text" + parsed = body + pairs.append( + { + "url": url, + "action": action, + "kind": kind, + "body": body, + "parsed": parsed, + "empty": kind == "empty", + } + ) + + nav_items: list[str] = [] + chart_json: dict[str, Any] | None = None + stats_json: dict[str, Any] | None = None + exceed_json: Any = None + warnings: list[str] = [] + + for p in pairs: + act = (p.get("action") or "").lower() + url = p.get("url") or "" + if p["kind"] == "html" and ("leftmenu" in act or "left" in url or "menu" in act): + parser = _MenuHTMLParser() + try: + parser.feed(str(p["parsed"] or "")) + parser.close() + except Exception: # noqa: BLE001 + pass + if parser.items: + nav_items = parser.items + elif p["kind"] == "json" and isinstance(p["parsed"], dict): + data = p["parsed"] + if "mcljChartData" in data or "cljdChartData" in data: + chart_json = data + elif any(k in data for k in ("yqwccds", "xzcxcds", "nodisposecount", "bdlength")): + stats_json = data + elif "gonghou" in act.lower() or "cjlcx" in act.lower() or "cx" in act: + exceed_json = data + elif chart_json is None and any( + isinstance(v, (list, dict)) for v in data.values() + ): + # 兜底:第一个复杂 JSON 当图表 + chart_json = data + elif p["empty"] and ("gonghou" in act.lower() or "cjlcx" in act.lower() or "CX" in url): + warnings.append( + f"接口 {p.get('action') or url} 返回为空:" + "超限测点表无抓包数据,将由主图表 JSON 按超限条件推导" + ) + elif p["empty"]: + warnings.append(f"接口 {p.get('action') or url} 返回为空,已跳过") + + return { + "ok": True, + "pairs": [ + { + "url": p["url"], + "action": p["action"], + "kind": p["kind"], + "empty": p["empty"], + "body_len": len(p.get("body") or ""), + } + for p in pairs + ], + "nav_items": nav_items, + "chart_json": chart_json, + "stats_json": stats_json, + "exceed_json": exceed_json, + "warnings": warnings, + "source": "url_config", + } diff --git a/blueprint/README.md b/blueprint/README.md new file mode 100644 index 0000000..8cf29c0 --- /dev/null +++ b/blueprint/README.md @@ -0,0 +1,79 @@ +# 应用蓝图落地说明(v1) + +用户上传「描述/图片 + Excel」后,系统只围绕一份 **AppBlueprint JSON** 工作:AI 生成 → 校验 → 用户确认 → 中台执行建库/挂 API/渲染页面。 + +## 目录 + +| 路径 | 作用 | +|------|------| +| `schema/app-blueprint.schema.json` | 蓝图 JSON Schema(契约) | +| `examples/inventory-ledger.blueprint.json` | 完整示例(库存台账) | +| `openapi/dynamic-crud.openapi.yaml` | 动态 API 形状(按蓝图挂载) | +| `platform/execution-steps.md` | 中台执行步骤与安全闸门 | +| `ai/prompt-contract.md` | AI 服务输入输出约定 | + +## 主链路 + +```text +1) 上传 excel + prompt/image +2) FastAPI:解析 Excel + 多模态理解 → 产出 blueprint draft +3) 用 JSON Schema 校验;置信度 < 0.75 强制人工确认 +4) 用户确认/改字段 +5) go-zero: + - 分配 schema_name / database + - CREATE TABLE(白名单 DDL) + - 注册元数据 + - 按 apis 挂动态 CRUD + - 可选导入 Excel 行 +6) 前端:按 pages[] 渲染,不落不可控源码 +``` + +## 服务边界 + +### AI 服务(FastAPI) + +- **能做**:推断实体、字段类型、页面布局、图表建议 +- **不能做**:直接连业务库执行 SQL、注册网关路由、写连接串 + +输出:`AppBlueprint` draft + `warnings[]` + +### 中台(go-zero) + +- **能做**:租户鉴权、DDL 执行、元数据落库、动态 API、审计 +- **不能做**:信任模型原文;必须 Schema 校验 + 标识符白名单 + +### 前端渲染器 + +输入:`GET /api/v1/apps/{slug}/blueprint` +按 `pages[].type` 渲染 list/form/dashboard。 + +## 动态 API(示例应用) + +确认并发布后,库存示例会得到: + +```http +GET /api/v1/apps/inventory_ledger/inventory_items +GET /api/v1/apps/inventory_ledger/inventory_items/{id} +POST /api/v1/apps/inventory_ledger/inventory_items +PATCH /api/v1/apps/inventory_ledger/inventory_items/{id} +DELETE /api/v1/apps/inventory_ledger/inventory_items/{id} +POST /api/v1/apps/inventory_ledger/inventory_items:import +GET /api/v1/apps/inventory_ledger/inventory_items:export +``` + +所有请求强制:`Authorization` + 租户上下文;`row_policies=tenant_isolated` 由服务端注入,客户端不可关。 + +## 本地快速校验示例 + +用任意 JSON Schema 校验器验证示例: + +```bash +# 需本机有 ajv-cli 或等价工具 +npx --yes ajv-cli validate -s blueprint/schema/app-blueprint.schema.json -d blueprint/examples/inventory-ledger.blueprint.json +``` + +## 版本策略 + +- `version: "1.0"` 固定本契约 +- 不兼容变更升 `1.1` / `2.0`,旧应用蓝图可继续读取 +- AI 与中台只认 Schema 内字段;多余字段校验失败 diff --git a/blueprint/ai/prompt-contract.md b/blueprint/ai/prompt-contract.md new file mode 100644 index 0000000..32f993a --- /dev/null +++ b/blueprint/ai/prompt-contract.md @@ -0,0 +1,71 @@ +# AI 服务输入输出约定 + +## 输入 + +`POST /api/v1/apps:generate`(可由网关转到 FastAPI) + +| 字段 | 必填 | 说明 | +|------|------|------| +| prompt | 是 | 用户自然语言需求 | +| excel | 否 | xlsx/xls/csv | +| images[] | 否 | 页面截图/手绘线框,最多 10 | +| storage_mode | 否 | 默认 `schema_per_app` | + +Excel 解析侧建议先产出中间结构再喂给 LLM: + +```json +{ + "sheets": [ + { + "name": "Sheet1", + "headers": ["SKU", "商品名称", "仓库", "数量", "单价", "状态", "更新时间"], + "sample_rows": [ + ["A-1001", "无线鼠标", "华东仓", 120, 59.9, "在售", "2026-07-01 10:00:00"] + ], + "inferred_types": { + "SKU": "string", + "数量": "int", + "单价": "decimal" + } + } + ] +} +``` + +## 输出 + +```json +{ + "draft": { "...": "AppBlueprint" }, + "warnings": [ + "列「备注」样本过少,已标为可空 text", + "截图中有图表区域,已生成 dashboard 草案" + ], + "confidence": 0.86, + "require_confirm": true +} +``` + +规则: + +- `draft` 必须能通过 `app-blueprint.schema.json` +- `confidence < 0.75` ⇒ `require_confirm=true`(前端禁止一键跳过) +- 字段名、表名由 AI 生成后,**发布前中台会再次规范化/重写** +- 不得在输出中包含数据库连接串、密码、任意 SQL + +## 系统提示词要点(实现时使用) + +1. 只输出符合 Schema 的 JSON(可包在 draft 字段) +2. 一表对应 Excel 主 sheet;多 sheet 才多 entity(≤ 20) +3. 优先 enum:当某列 distinct 值 ≤ 20 且稳定 +4. 页面至少包含一个 `list`;若用户提到统计再加 `dashboard` +5. API operations 默认 `list/get/create/update/delete`;提到导入导出再加 +6. `security.visibility` 默认 `private`,`row_policies` 默认 `tenant_isolated` + +## 与中台的分工 + +```text +AI:理解意图,填蓝图 +中台:校验、改写标识符、建表、挂路由、强制租户隔离 +前端:确认页编辑蓝图 → publish → 用 blueprint 渲染 +``` diff --git a/blueprint/examples/inventory-ledger.blueprint.json b/blueprint/examples/inventory-ledger.blueprint.json new file mode 100644 index 0000000..01073c4 --- /dev/null +++ b/blueprint/examples/inventory-ledger.blueprint.json @@ -0,0 +1,336 @@ +{ + "version": "1.0", + "meta": { + "name": "库存台账", + "slug": "inventory_ledger", + "description": "根据上传的库存 Excel 与「要一个可筛选的库存列表页」描述生成", + "locale": "zh-CN", + "source": { + "prompt": "根据这张库存表生成可筛选、可编辑的数据展示页,支持按仓库和状态过滤", + "image_refs": ["oss://uploads/u_1001/mockups/inventory-list.png"], + "excel_ref": "oss://uploads/u_1001/excel/inventory.xlsx" + }, + "confidence": 0.86 + }, + "storage": { + "mode": "schema_per_app", + "engine": "postgres", + "schema_name": "app_inventory_ledger" + }, + "entities": [ + { + "name": "inventory_item", + "table": "inventory_item", + "label": "库存明细", + "primary_key": "id", + "fields": [ + { + "name": "id", + "label": "ID", + "type": "bigint", + "nullable": false, + "ui": { "widget": "hidden", "listable": false } + }, + { + "name": "sku", + "label": "SKU", + "type": "string", + "nullable": false, + "unique": true, + "max_length": 64, + "ui": { + "widget": "input", + "listable": true, + "filterable": true, + "sortable": true, + "width": "md" + }, + "from_excel": { + "column": "SKU", + "sample_values": ["A-1001", "A-1002", "B-2001"] + } + }, + { + "name": "product_name", + "label": "商品名称", + "type": "string", + "nullable": false, + "max_length": 128, + "ui": { + "widget": "input", + "listable": true, + "filterable": true, + "sortable": true, + "width": "lg" + }, + "from_excel": { + "column": "商品名称", + "sample_values": ["无线鼠标", "机械键盘", "显示器支架"] + } + }, + { + "name": "warehouse", + "label": "仓库", + "type": "enum", + "nullable": false, + "enum_values": ["华东仓", "华南仓", "华北仓"], + "ui": { + "widget": "select", + "listable": true, + "filterable": true, + "sortable": false, + "width": "sm" + }, + "from_excel": { + "column": "仓库", + "sample_values": ["华东仓", "华南仓"] + } + }, + { + "name": "qty", + "label": "库存数量", + "type": "int", + "nullable": false, + "default": 0, + "ui": { + "widget": "number", + "listable": true, + "filterable": false, + "sortable": true, + "width": "sm" + }, + "from_excel": { + "column": "数量", + "sample_values": ["120", "35", "8"] + } + }, + { + "name": "unit_price", + "label": "单价", + "type": "decimal", + "nullable": true, + "precision": 12, + "scale": 2, + "ui": { + "widget": "number", + "listable": true, + "sortable": true, + "width": "sm" + }, + "from_excel": { + "column": "单价", + "sample_values": ["59.90", "299.00"] + } + }, + { + "name": "status", + "label": "状态", + "type": "enum", + "nullable": false, + "enum_values": ["在售", "停售", "盘点中"], + "ui": { + "widget": "select", + "listable": true, + "filterable": true, + "width": "sm" + }, + "from_excel": { + "column": "状态", + "sample_values": ["在售", "停售"] + } + }, + { + "name": "updated_at", + "label": "更新时间", + "type": "datetime", + "nullable": false, + "ui": { + "widget": "datepicker", + "listable": true, + "sortable": true, + "width": "md" + }, + "from_excel": { + "column": "更新时间", + "sample_values": ["2026-07-01 10:00:00"] + } + } + ], + "indexes": [ + { + "name": "idx_inventory_item_warehouse_status", + "columns": ["warehouse", "status"], + "unique": false + }, + { + "name": "idx_inventory_item_sku", + "columns": ["sku"], + "unique": true + } + ] + } + ], + "apis": { + "base_path": "/api/v1/apps/inventory_ledger", + "resources": [ + { + "entity": "inventory_item", + "path": "/inventory_items", + "operations": [ + "list", + "get", + "create", + "update", + "delete", + "import", + "export" + ], + "list": { + "default_page_size": 20, + "max_page_size": 100, + "allowed_filters": ["sku", "product_name", "warehouse", "status"], + "allowed_sorts": ["sku", "qty", "unit_price", "updated_at"] + } + } + ] + }, + "pages": [ + { + "id": "inventory_list", + "title": "库存列表", + "route": "/inventory", + "type": "list", + "entity": "inventory_item", + "layout": { + "columns": [ + "sku", + "product_name", + "warehouse", + "qty", + "unit_price", + "status", + "updated_at" + ], + "filters": ["warehouse", "status", "sku"], + "actions": ["create", "edit", "delete", "export", "import", "refresh"] + } + }, + { + "id": "inventory_create", + "title": "新增库存", + "route": "/inventory/create", + "type": "form_create", + "entity": "inventory_item", + "layout": { + "form_fields": [ + "sku", + "product_name", + "warehouse", + "qty", + "unit_price", + "status" + ], + "actions": ["create"] + } + }, + { + "id": "inventory_edit", + "title": "编辑库存", + "route": "/inventory/:id/edit", + "type": "form_edit", + "entity": "inventory_item", + "layout": { + "form_fields": [ + "sku", + "product_name", + "warehouse", + "qty", + "unit_price", + "status" + ], + "actions": ["edit"] + } + }, + { + "id": "inventory_dashboard", + "title": "库存概览", + "route": "/inventory/dashboard", + "type": "dashboard", + "entity": "inventory_item", + "layout": { + "widgets": [ + { + "type": "kpi", + "title": "SKU 总数", + "metric": "count", + "entity": "inventory_item" + }, + { + "type": "kpi", + "title": "总库存量", + "metric": "sum:qty", + "entity": "inventory_item" + }, + { + "type": "bar_chart", + "title": "各仓库库存量", + "metric": "sum:qty", + "group_by": "warehouse", + "entity": "inventory_item" + }, + { + "type": "pie_chart", + "title": "状态分布", + "metric": "count", + "group_by": "status", + "entity": "inventory_item" + } + ] + } + } + ], + "security": { + "visibility": "private", + "roles": [ + { + "name": "owner", + "permissions": [ + "app.read", + "app.write", + "app.admin", + "row.create", + "row.read", + "row.update", + "row.delete", + "row.export", + "row.import" + ] + }, + { + "name": "editor", + "permissions": [ + "app.read", + "row.create", + "row.read", + "row.update", + "row.export", + "row.import" + ] + }, + { + "name": "viewer", + "permissions": ["app.read", "row.read", "row.export"] + } + ], + "row_policies": [ + { + "entity": "inventory_item", + "rule": "tenant_isolated" + } + ] + }, + "seed": { + "import_excel": true, + "max_rows": 5000 + } +} diff --git a/blueprint/openapi/README.md b/blueprint/openapi/README.md new file mode 100644 index 0000000..991cd68 --- /dev/null +++ b/blueprint/openapi/README.md @@ -0,0 +1,39 @@ +# API 契约(唯一) + +- **运行时契约**:`GET /api/v1/meta/openapi.yaml`(与代码同嵌入) +- **目录清单**:`GET /api/v1/meta/apis` +- **源文件**:`platform/internal/handler/openapi.yaml` + `platform/internal/apidef/catalog.go` + +## 动词 + +| 方法 | 用途 | +|------|------| +| GET | 读取 | +| POST | 创建,或无幂等动作(login / import / publish / generate) | +| PUT | 更新 | +| DELETE | 删除 | + +禁止再用 PATCH 作为更新主路径;禁止为行业场景新增平行 CRUD。 + +## 动态业务数据 + +仅: + +```http +GET|POST /api/v1/apps/{slug}/{resource} +GET|PUT|DELETE /api/v1/apps/{slug}/{resource}/{id} +POST /api/v1/apps/{slug}/{resource}/import +GET /api/v1/apps/{slug}/{resource}/export +GET /api/v1/apps/{slug}/{resource}/aggregate +``` + +字段由蓝图定义,平台不做行业写死接口。 + +## AI(不重复实现) + +经网关 `/ai` 前缀: + +```http +POST /ai/api/v1/apps/generate +GET /ai/api/v1/llm/providers +``` diff --git a/blueprint/openapi/dynamic-crud.openapi.yaml b/blueprint/openapi/dynamic-crud.openapi.yaml new file mode 100644 index 0000000..a253b2b --- /dev/null +++ b/blueprint/openapi/dynamic-crud.openapi.yaml @@ -0,0 +1,326 @@ +openapi: 3.0.3 +info: + title: AI建站 Platform API + version: 1.0.0 + description: | + 通用中台契约。业务行数据仅通过 apps/{slug}/{resource} CRUD 传输; + 行业字段由蓝图定义,不在此增加行业专用路由。 + 动词约定:GET 读 / POST 创建或动作 / PUT 更新 / DELETE 删除。 +servers: + - url: http://127.0.0.1:8180 +paths: + /api/v1/meta/apis: + get: + operationId: listApis + summary: API 目录 + responses: + "200": + description: OK + /api/v1/meta/openapi.yaml: + get: + operationId: getOpenAPI + summary: OpenAPI 原文 + responses: + "200": + description: YAML + /api/v1/auth/register: + post: + operationId: authRegister + summary: 注册 + responses: + "201": { description: Created } + /api/v1/auth/login: + post: + operationId: authLogin + summary: 登录 + responses: + "200": { description: OK } + /api/v1/auth/token: + post: + operationId: authToken + summary: 服务签发 JWT + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/publish: + post: + operationId: publishApp + summary: 发布蓝图 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + /api/v1/apps/{slug}/blueprint: + get: + operationId: getBlueprint + summary: 读取蓝图 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + /api/v1/apps/{slug}/agent-capsule: + get: + operationId: getAgentCapsule + summary: 智能体胶囊 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/{resource}: + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + get: + operationId: listRows + summary: 列表 + parameters: + - name: page + in: query + schema: { type: integer, minimum: 1, default: 1 } + - name: page_size + in: query + schema: { type: integer, minimum: 1, maximum: 100, default: 20 } + - name: sort + in: query + schema: { type: string } + - name: filter.* + in: query + description: 如 filter.status=在售,键须在蓝图 allowed_filters + schema: { type: string } + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PageResult" + post: + operationId: createRow + summary: 创建 + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + + /api/v1/apps/{slug}/{resource}/{id}: + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Id" + - $ref: "#/components/parameters/Authorization" + get: + operationId: getRow + summary: 详情 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + "404": + $ref: "#/components/responses/NotFound" + put: + operationId: updateRow + summary: 更新 + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + delete: + operationId: deleteRow + summary: 删除 + responses: + "204": { description: No Content } + + /api/v1/apps/{slug}/{resource}/import: + post: + operationId: importRows + summary: 导入 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/{resource}/export: + get: + operationId: exportRows + summary: 导出 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + - name: format + in: query + schema: { type: string, enum: [xlsx, csv], default: xlsx } + responses: + "200": { description: 文件流 } + + /api/v1/apps/{slug}/{resource}/aggregate: + get: + operationId: aggregateRows + summary: 聚合 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + - name: group_by + in: query + schema: { type: string } + - name: sum + in: query + schema: { type: string } + responses: + "200": { description: OK } + + /api/v1/audit/logs: + get: + operationId: listAuditLogs + summary: 审计日志 + parameters: + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + + /api/v1/storage: + post: + operationId: uploadObject + summary: 上传 + parameters: + - $ref: "#/components/parameters/Authorization" + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/storage/{tenant}/{day}/{name}: + get: + operationId: downloadObject + summary: 下载 + parameters: + - $ref: "#/components/parameters/Authorization" + - name: tenant + in: path + required: true + schema: { type: string } + - name: day + in: path + required: true + schema: { type: string } + - name: name + in: path + required: true + schema: { type: string } + responses: + "200": { description: 文件流 } + + /api/v1/apps/generate: + post: + operationId: generateBlueprint + summary: 生成蓝图(AI 服务,经网关 /ai 前缀) + description: 实际请求 /ai/api/v1/apps/generate;勿在 platform 重复实现。 + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + prompt: { type: string } + excel: { type: string, format: binary } + images: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/llm/providers: + get: + operationId: listLlmProviders + summary: LLM 厂商(AI 服务) + responses: + "200": { description: OK } + +components: + parameters: + Authorization: + name: Authorization + in: header + required: true + schema: { type: string } + description: Bearer JWT + Slug: + name: slug + in: path + required: true + schema: { type: string, pattern: "^[a-z][a-z0-9_]{1,47}$" } + Resource: + name: resource + in: path + required: true + schema: { type: string } + description: 蓝图 apis.resources.path(无前导 /);不可为保留名 + Id: + name: id + in: path + required: true + schema: { type: string } + schemas: + PageResult: + type: object + properties: + items: { type: array, items: { $ref: "#/components/schemas/Row" } } + total: { type: integer } + Row: + type: object + additionalProperties: true + responses: + NotFound: + description: Not Found + content: + application/json: + schema: + type: object + properties: + code: { type: integer } + message: { type: string } diff --git a/blueprint/platform/execution-steps.md b/blueprint/platform/execution-steps.md new file mode 100644 index 0000000..d0c75e0 --- /dev/null +++ b/blueprint/platform/execution-steps.md @@ -0,0 +1,128 @@ +# 中台执行步骤与安全闸门 + +面向 go-zero(或等价中台)实现 `POST /api/v1/apps/{slug}:publish`。 + +## 1. 入口校验 + +1. 鉴权:用户已登录,具备 `app.admin` 或「创建应用」权限 +2. 限流:每租户每小时发布次数上限(建议 ≤ 20) +3. Body 必须是完整 `AppBlueprint` +4. 用 `app-blueprint.schema.json` 校验;失败直接 400 +5. `meta.slug` 与 path 中 slug 一致;租户内唯一 + +## 2. 标识符白名单(防 SQL 注入) + +仅允许匹配: + +```text +^[a-z][a-z0-9_]{1,47}$ +``` + +校验对象:`slug`、`entity.name/table`、所有 `field.name`、`index.name` +拒绝:大小写混用、连字符、空格、引号、注释符、SQL 关键字作表名(建议黑名单:`select/drop/user/...`) + +## 3. 分配存储(用户不可指定 DSN) + +| storage.mode | 行为 | +|--------------|------| +| `schema_per_app` | 平台在共享实例创建 `schema_name`(可忽略 AI 填的名字,按 `app_{tenant}_{slug}` 重写) | +| `database_per_app` | 平台开通独立库,凭证写入 KMS/密钥服务 | + +落库元数据示例: + +```text +tenant_apps(app_id, tenant_id, slug, schema_name, engine, blueprint_json, status, created_at) +tenant_app_entities(...) +tenant_app_fields(...) +tenant_app_apis(...) +``` + +## 4. 生成并执行 DDL(参数化/白名单拼接) + +伪代码原则: + +- 只拼已经过白名单的标识符 +- 字段类型映射用固定字典,禁止把 AI 的 type 字符串直接塞进 SQL + +类型映射建议(Postgres): + +| blueprint type | SQL | +|----------------|-----| +| string | VARCHAR(n) | +| text | TEXT | +| int | INTEGER | +| bigint | BIGINT | +| decimal | NUMERIC(p,s) | +| boolean | BOOLEAN | +| date | DATE | +| datetime | TIMESTAMPTZ | +| enum | VARCHAR(n) + CHECK | +| json | JSONB | +| file_ref | VARCHAR(512) | + +每个表强制附加系统列(即使蓝图未写): + +```sql +tenant_id BIGINT NOT NULL, +created_by BIGINT, +created_at TIMESTAMPTZ NOT NULL DEFAULT now(), +updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +``` + +`row_policies=tenant_isolated` → 所有查询自动 `AND tenant_id = :current_tenant`。 + +## 5. 注册动态 API + +读取 `apis.resources[]`: + +- 在网关/服务路由表注册 path +- `operations` 决定开放方法 +- `allowed_filters/sorts` 写入配置;运行时拒绝未声明字段 + +禁止:按请求参数动态选物理表名。 +允许:`app_slug + resource` → 元数据查出 `schema.table`。 + +## 6. 种子数据 + +若 `seed.import_excel=true`: + +1. 从 `meta.source.excel_ref` 拉文件 +2. 按 `field.from_excel.column` 映射 +3. 行数 ≤ `seed.max_rows` +4. 批量插入,单行失败记入 errors,不中断整单(或按策略 fail-fast) + +## 7. 发布事务与回滚 + +建议状态机: + +```text +draft → validating → provisioning → importing → published + ↘ failed(保留草稿,DDL 尽量事务/可回滚) +``` + +失败时: + +- schema 已建:标记 `failed`,提供 `DELETE app` 清理 +- 写审计:谁、何时、哪份 blueprint hash、结果 + +## 8. 运行时读路径(列表页) + +```text +Auth → 解析 tenant → 查 app 元数据 → 校验 resource +→ 校验 filter/sort 白名单 → 组装 SELECT +→ 强制 tenant_id 条件 → 分页返回 +``` + +前端只读 blueprint 渲染,不信任客户端传来的「表名/SQL」。 + +## 9. 最小落地服务拆分 + +| 服务 | 职责 | +|------|------| +| `ai-generate` | 素材 → draft blueprint | +| `app-meta` | 蓝图存储、校验、发布状态 | +| `schema-runner` | DDL / 迁移 / 清理 | +| `dynamic-crud` | 统一 CRUD / import / export | +| `gateway` | 鉴权、限流、WAF、路由 | + +首版可先 2 个进程:`ai-generate(FastAPI)` + `platform(go-zero 含 meta/runner/crud)`。 diff --git a/blueprint/schema/app-blueprint.schema.json b/blueprint/schema/app-blueprint.schema.json new file mode 100644 index 0000000..cae0bc4 --- /dev/null +++ b/blueprint/schema/app-blueprint.schema.json @@ -0,0 +1,483 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://local.dev/schemas/app-blueprint/v1.json", + "title": "AppBlueprint", + "description": "AI 生成 → 用户确认 → 中台执行的唯一契约(v1)", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "meta", + "storage", + "entities", + "apis", + "pages", + "security" + ], + "properties": { + "version": { + "type": "string", + "const": "1.0" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["name", "slug", "locale"], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "应用显示名" + }, + "slug": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$", + "description": "应用标识,用于路径与资源前缀" + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "locale": { + "type": "string", + "default": "zh-CN" + }, + "source": { + "type": "object", + "additionalProperties": true, + "properties": { + "prompt": { "type": "string" }, + "image_refs": { + "type": "array", + "items": { "type": "string" }, + "maxItems": 10 + }, + "excel_ref": { "type": "string" }, + "layout_refs": { + "type": "array", + "items": { "type": "string" } + }, + "screenshot_faithful": { "type": "boolean" }, + "vision_summary": { "type": "string" }, + "layout_summary": { "type": "string" }, + "llm_provider": { "type": "string" }, + "llm_model": { "type": "string" }, + "data_format": { "type": "string" }, + "row_count": { "type": "number" } + } + }, + "ui_preset": { + "type": "string", + "enum": ["default", "screenshot_faithful", "ops_monitor"] + }, + "platform_title": { "type": "string", "maxLength": 64 }, + "project_context": { "type": "string", "maxLength": 120 }, + "ui": { + "type": "object", + "additionalProperties": true + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "AI 整体置信度,低于阈值应强制人工确认" + } + } + }, + "storage": { + "type": "object", + "additionalProperties": false, + "required": ["mode", "engine"], + "properties": { + "mode": { + "type": "string", + "enum": ["schema_per_app", "database_per_app"], + "description": "schema_per_app=共享实例独立 schema;database_per_app=独立库(更高隔离)" + }, + "engine": { + "type": "string", + "enum": ["postgres", "mysql"] + }, + "schema_name": { + "type": "string", + "pattern": "^app_[a-z0-9_]{1,48}$", + "description": "由平台生成或校验;禁止用户/AI 指定任意库名" + } + } + }, + "entities": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { "$ref": "#/$defs/entity" } + }, + "apis": { + "type": "object", + "additionalProperties": false, + "required": ["base_path", "resources"], + "properties": { + "base_path": { + "type": "string", + "pattern": "^/api/v1/apps/[a-z][a-z0-9_]{1,47}$" + }, + "resources": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/apiResource" } + } + } + }, + "pages": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/page" } + }, + "security": { + "type": "object", + "additionalProperties": false, + "required": ["visibility", "roles", "row_policies"], + "properties": { + "visibility": { + "type": "string", + "enum": ["private", "org", "public_readonly"] + }, + "roles": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "permissions"], + "properties": { + "name": { + "type": "string", + "enum": ["owner", "editor", "viewer"] + }, + "permissions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "app.read", + "app.write", + "app.admin", + "row.create", + "row.read", + "row.update", + "row.delete", + "row.export", + "row.import" + ] + } + } + } + } + }, + "row_policies": { + "type": "array", + "description": "行级规则,中台强制注入,不可被前端绕过", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["entity", "rule"], + "properties": { + "entity": { "type": "string" }, + "rule": { + "type": "string", + "enum": ["tenant_isolated", "owner_only", "org_shared"] + } + } + } + } + } + }, + "seed": { + "type": "object", + "additionalProperties": false, + "properties": { + "import_excel": { + "type": "boolean", + "default": true + }, + "max_rows": { + "type": "integer", + "minimum": 1, + "maximum": 100000, + "default": 5000 + } + } + } + }, + "$defs": { + "entity": { + "type": "object", + "additionalProperties": false, + "required": ["name", "table", "label", "fields", "primary_key"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$", + "description": "逻辑实体名" + }, + "table": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$", + "description": "物理表名(不含 schema 前缀)" + }, + "label": { "type": "string", "maxLength": 64 }, + "primary_key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$" + }, + "fields": { + "type": "array", + "minItems": 1, + "maxItems": 80, + "items": { "$ref": "#/$defs/field" } + }, + "indexes": { + "type": "array", + "maxItems": 20, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "columns"], + "properties": { + "name": { + "type": "string", + "pattern": "^idx_[a-z0-9_]{1,48}$" + }, + "columns": { + "type": "array", + "minItems": 1, + "maxItems": 5, + "items": { "type": "string" } + }, + "unique": { "type": "boolean", "default": false } + } + } + } + } + }, + "field": { + "type": "object", + "additionalProperties": false, + "required": ["name", "type", "label"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$" + }, + "label": { "type": "string", "maxLength": 64 }, + "type": { + "type": "string", + "enum": [ + "string", + "text", + "int", + "bigint", + "decimal", + "boolean", + "date", + "datetime", + "enum", + "json", + "file_ref" + ] + }, + "nullable": { "type": "boolean", "default": true }, + "unique": { "type": "boolean", "default": false }, + "default": {}, + "max_length": { + "type": "integer", + "minimum": 1, + "maximum": 4000 + }, + "precision": { "type": "integer", "minimum": 1, "maximum": 38 }, + "scale": { "type": "integer", "minimum": 0, "maximum": 10 }, + "enum_values": { + "type": "array", + "items": { "type": "string" }, + "maxItems": 50 + }, + "ui": { + "type": "object", + "additionalProperties": false, + "properties": { + "widget": { + "type": "string", + "enum": [ + "input", + "textarea", + "number", + "select", + "switch", + "datepicker", + "upload", + "hidden" + ] + }, + "listable": { "type": "boolean", "default": true }, + "filterable": { "type": "boolean", "default": false }, + "sortable": { "type": "boolean", "default": false }, + "width": { + "type": "string", + "enum": ["sm", "md", "lg"] + } + } + }, + "from_excel": { + "type": "object", + "additionalProperties": false, + "properties": { + "column": { "type": "string" }, + "sample_values": { + "type": "array", + "items": { "type": "string" }, + "maxItems": 5 + } + } + } + } + }, + "apiResource": { + "type": "object", + "additionalProperties": false, + "required": ["entity", "path", "operations"], + "properties": { + "entity": { "type": "string" }, + "path": { + "type": "string", + "pattern": "^/[a-z][a-z0-9_]{1,47}$" + }, + "operations": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": ["list", "get", "create", "update", "delete", "import", "export"] + } + }, + "list": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_page_size": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "max_page_size": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 100 + }, + "allowed_filters": { + "type": "array", + "items": { "type": "string" } + }, + "allowed_sorts": { + "type": "array", + "items": { "type": "string" } + } + } + } + } + }, + "page": { + "type": "object", + "additionalProperties": false, + "required": ["id", "title", "route", "type", "entity"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{1,47}$" + }, + "title": { "type": "string", "maxLength": 64 }, + "route": { + "type": "string", + "pattern": "^/[a-z0-9\\-_/:]{1,64}$", + "description": "支持 :id 这类路径参数" + }, + "type": { + "type": "string", + "enum": ["list", "detail", "form_create", "form_edit", "dashboard"] + }, + "entity": { "type": "string" }, + "layout": { + "type": "object", + "additionalProperties": false, + "properties": { + "preset": { + "type": "string", + "enum": ["default", "screenshot_faithful", "ops_monitor"] + }, + "action_labels": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "filter_style": { "type": "string" }, + "columns": { + "type": "array", + "description": "list 页展示列(字段名)", + "items": { "type": "string" } + }, + "filters": { + "type": "array", + "items": { "type": "string" } + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "enum": ["create", "edit", "delete", "export", "import", "refresh"] + } + }, + "form_fields": { + "type": "array", + "items": { "type": "string" } + }, + "widgets": { + "type": "array", + "description": "dashboard 组件", + "items": { + "type": "object", + "additionalProperties": true, + "required": ["type", "title"], + "properties": { + "type": { + "type": "string", + "enum": ["kpi", "bar_chart", "line_chart", "pie_chart", "table", "status_strip"] + }, + "title": { "type": "string" }, + "metric": { "type": "string" }, + "metrics": { + "type": "array", + "items": { "type": "string" } + }, + "group_by": { "type": "string" }, + "x_field": { "type": "string" }, + "y_unit": { "type": "string" }, + "entity": { "type": "string" }, + "columns": { + "type": "array", + "items": { "type": "string" } + }, + "label_field": { "type": "string" }, + "value_field": { "type": "string" }, + "warn_field": { "type": "string" }, + "filter_field": { "type": "string" }, + "filter_op": { "type": "string" }, + "filter_value": {} + } + } + } + } + } + } + } + } +} diff --git a/blueprint/tools/validate_blueprint.py b/blueprint/tools/validate_blueprint.py new file mode 100644 index 0000000..6857499 --- /dev/null +++ b/blueprint/tools/validate_blueprint.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Validate AppBlueprint JSON against the local JSON Schema (Draft 2020-12).""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +try: + import jsonschema + from jsonschema import Draft202012Validator +except ImportError: + print("请先安装: pip install jsonschema", file=sys.stderr) + sys.exit(2) + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + parser = argparse.ArgumentParser(description="Validate AppBlueprint") + parser.add_argument( + "blueprint", + nargs="?", + default=str(root / "examples" / "inventory-ledger.blueprint.json"), + help="blueprint json path", + ) + parser.add_argument( + "--schema", + default=str(root / "schema" / "app-blueprint.schema.json"), + help="json schema path", + ) + args = parser.parse_args() + + schema = json.loads(Path(args.schema).read_text(encoding="utf-8")) + data = json.loads(Path(args.blueprint).read_text(encoding="utf-8")) + + validator = Draft202012Validator(schema) + errors = sorted(validator.iter_errors(data), key=lambda e: list(e.path)) + if errors: + print(f"FAIL: {len(errors)} error(s)") + for err in errors: + path = ".".join(str(p) for p in err.path) or "$" + print(f" - {path}: {err.message}") + return 1 + + print("OK: blueprint is valid") + print(f" app: {data['meta']['name']} ({data['meta']['slug']})") + print(f" entities: {len(data['entities'])}") + print(f" pages: {len(data['pages'])}") + print(f" apis: {len(data['apis']['resources'])}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/database_fastapi修改意见.md b/database_fastapi修改意见.md new file mode 100644 index 0000000..3bc3e27 --- /dev/null +++ b/database_fastapi修改意见.md @@ -0,0 +1,107 @@ +# database_fastapi 修改意见 + +> 针对文件:`E:\project\yh_one\new_project\yuhengyihao_client\yxd\app_fastapi\database_fastapi.py` +> 背景:需要与线上主库强一致;断网可记日志,恢复后合并回放至线上;主写在线上。 +> 对照能力:宇信达智建平台 `dbsync` 出站队列 / 冲突 / 对账(见 `ai建站/docs/数据同步-中间件.md`)。 + +--- + +## 落地状态(已实现) + +| 项 | 状态 | 位置 | +|----|------|------| +| 统一写网关 `apply_write` | ✅ | `db_write_gateway.py` | +| 写接口收口 | ✅ | insert / update / update/id / delete / sql(DML) | +| 库绑定防串库 | ✅ | `sync_binding.py` → `cache/db_bindings/bindings.json` | +| 配置 | ✅ | `sync_config.py`(环境变量) | +| 断网 pending | ✅ | 复用 `QueueDatabaseManager`,库名默认 `sync_pending` | +| 手动回放 | ✅ | `POST /database/sync/replay` | +| 绑定/状态 API | ✅ | `/database/sync/*` | +| 后台自动回放 | ✅ 可选 | `sync_replay_worker.py`,需 `YXD_SYNC_REPLAY_WORKER=1` | +| DDL / 批量 import 收口 | ⏳ 未做 | 仍可直写本地;后续按需收口 | +| 冲突队列 API | ⏳ 未做 | 回放失败即停保序,暂无独立 conflicts 列表 | + +**默认安全**:`YXD_SYNC_MODE` 未配且无 `YXD_ONLINE_API_BASE` 时为 **`local_only`**(只写本地,仍会 ensure 绑定信息)。 +配了 `YXD_ONLINE_API_BASE` 且未显式写 `YXD_SYNC_MODE` 时自动升为 **`online_primary`**。 + +### 环境变量 + +| 变量 | 说明 | 默认 | +|------|------|------| +| `YXD_SYNC_MODE` | `local_only` \| `online_primary` | `local_only`(有 ONLINE_BASE 且未设 mode 则升 primary) | +| `YXD_ONLINE_API_BASE` | 线上 API 根,如 `https://api.example.com` | 空 | +| `YXD_ONLINE_TIMEOUT_SEC` | 线上写超时 | `8` | +| `YXD_OFFLINE_POLICY` | `pending_log` \| `reject` | `pending_log` | +| `YXD_SYNC_ENFORCE_BINDING` | 是否强制已绑定 | `true` | +| `YXD_SYNC_AUTO_ENSURE_BINDING` | 写时自动 ensure | `true` | +| `YXD_SYNC_PENDING_QUEUE` | pending 队列库名 | `sync_pending` | +| `YXD_ONLINE_TOKEN_HEADER` | 透传 token 头 | `Authorization` | +| `YXD_SYNC_REPLAY_WORKER` | 启后台回放 | 关 | +| `YXD_SYNC_REPLAY_INTERVAL_SEC` | 回放间隔秒 | `30` | + +### 新增/改动文件 + +```text +yxd/app_fastapi/ + db_write_gateway.py # apply_write / replay_pending + sync_config.py + sync_binding.py + sync_context.py # Request → WriteContext + sync_replay_worker.py + database_fastapi.py # 写路由改走网关 + /sync/* API +``` + +### 同步 API + +- `GET /database/sync/status` +- `POST /database/sync/binding/ensure` +- `GET /database/sync/binding` +- `GET /database/sync/pending` +- `POST /database/sync/replay` body: `{ "limit": 50 }` + +请求头需带:`Database-ID`、`User-ID`(或应用已登录)、可选 `Tenant-ID` / `Authorization`。 + +### 写路径行为(`online_primary`) + +```text +联网:先写线上(Database-ID=online_db_id)→ 成功再写本地镜像 → synced=true +离线 pending_log:不写正式本地业务结果 → 入 sync_pending 队列 → pending=true +离线 reject:直接失败 +回放:按 id 升序提交线上 → 成功后写本地并删队列记录;失败即停保序 +``` + +绑定键:`(tenant_id, user_id, local_database_id) → online_db_id`(全局唯一)。 + +--- + +## 一、原状结论(改造前) + +| 项 | 原状 | +|----|------| +| 写入口 | `/database/permanent/table/data/insert\|update\|update/id\|delete`、`/permanent/sql` 等 | +| 落库方式 | 直接 `PermanentDatabaseManager`,**只写本地 SQLite** | +| 队列能力 | `queue_database_fastapi.py` 与 permanent **未打通** | +| 线上主写 / 断网日志 | **未实现** | + +--- + +## 二、目标模型 + +```text +联网:API 写 → 先写线上主库 → 再写本地镜像 +断网:API 写 → pending 日志(queue_database)→ 不 formal commit +恢复:有序回放到线上主 → 成功再对齐本地 +``` + +硬约束:主写在线上;断网日志只能回放到线上主;断网窗口非强一致。 + +--- + +## 三~十、设计说明(保留) + +原 P0–P4、响应约定、勿做事项、分阶段 M1–M4 已按上文落地;M5(SQL/导入全收口 + 冲突监控)待续。 + +与智建平台 dbsync:客户端强一致仍以「API 同步写线上」为准,不要只靠触发器异步 outbox 充当主路径。 + +本意见文档:`E:\project\ai建站\database_fastapi修改意见.md`。 +实现目录:`yuhengyihao_client\yxd\app_fastapi\`。 diff --git a/deploy-menu.sh b/deploy-menu.sh new file mode 100644 index 0000000..a4f4900 --- /dev/null +++ b/deploy-menu.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# 跳转到同级 ops 多项目菜单 +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +MENU="${OPS_MENU:-$ROOT/../ops/deploy.sh}" +if [ ! -f "$MENU" ]; then + echo "找不到菜单: $MENU" >&2 + echo "请把 ops/ 放在与本项目同级,或设置 OPS_MENU=/path/to/ops/deploy.sh" >&2 + exit 1 +fi +exec bash "$MENU" "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..67bb9fb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,107 @@ +# aijianzhan full stack (default) +# Data under ./.runtime/ +# Web: host-built dist mounted into nginx (no node image pull each time) +# +# 宿主机端口默认绑 127.0.0.1,避免与同机 yh_web(8088/9080/9081 + 宿主机 80/443)及公网误暴露冲突。 +# 可用 .env 覆盖(compose 变量替换),例如宿主机已有 Postgres 时: +# AIJZ_PG_PUBLISH=127.0.0.1:15432 +# 需要局域网访问时: +# AIJZ_WEB_PUBLISH=0.0.0.0:5173 +# +# China: base images default to DaoCloud mirror via DOCKER_BASE_REGISTRY +# docker compose up -d --build +# docker compose down + +x-build-args: &build_args + BASE_REGISTRY: ${DOCKER_BASE_REGISTRY:-docker.m.daocloud.io/library} + +services: + postgres: + image: ${DOCKER_BASE_REGISTRY:-docker.m.daocloud.io/library}/postgres:16-alpine + environment: + POSTGRES_USER: platform + POSTGRES_PASSWORD: platform + POSTGRES_DB: platform + ports: + - "${AIJZ_PG_PUBLISH:-127.0.0.1:5432}:5432" + volumes: + - ./.runtime/pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U platform -d platform"] + interval: 3s + timeout: 5s + retries: 20 + + platform: + build: + context: ./platform + args: + <<: *build_args + depends_on: + postgres: + condition: service_healthy + ports: + - "${AIJZ_PLATFORM_PUBLISH:-127.0.0.1:8888}:8888" + volumes: + - ./.runtime/uploads:/app/data/uploads + - ./.runtime/dbsync:/app/data/dbsync + - ./platform/etc/platform.docker.yaml:/app/etc/platform.yaml:ro + environment: + TZ: Asia/Shanghai + + ai: + build: + context: ./ai-service + args: + <<: *build_args + ports: + - "${AIJZ_AI_PUBLISH:-127.0.0.1:8001}:8001" + env_file: + - .env + volumes: + - ./ai-service:/app + - ./ai-service/etc/llm.yaml:/app/etc/llm.yaml:ro + - ./test:/test:ro + - ./.runtime:/runtime + environment: + TZ: Asia/Shanghai + AI_CONFIG_PATH: /app/etc/llm.yaml + DEMO_FIXTURES_DIR: /test + GENERATE_LOG_DIR: /runtime/logs/generate + FIDELITY_SHOT_DIR: /runtime/fidelity_shots + WEB_BASE: http://web:80 + PLATFORM_BASE: http://platform:8888 + GATEWAY_BASE: http://gateway:8180 + LLM_PROVIDER: ${LLM_PROVIDER:-deepseek} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} + MINIMAX_API_KEY: ${MINIMAX_API_KEY:-} + PIP_INDEX_URL: https://pypi.tuna.tsinghua.edu.cn/simple + PIP_TRUSTED_HOST: pypi.tuna.tsinghua.edu.cn + + gateway: + build: + context: ./gateway + args: + <<: *build_args + depends_on: + - platform + - ai + ports: + - "${AIJZ_GATEWAY_PUBLISH:-127.0.0.1:8180}:8180" + volumes: + - ./gateway/etc/gateway.docker.yaml:/app/etc/gateway.yaml:ro + environment: + TZ: Asia/Shanghai + GOPROXY: https://goproxy.cn,direct + + web: + image: ${DOCKER_BASE_REGISTRY:-docker.m.daocloud.io/library}/nginx:1.27-alpine + depends_on: + - gateway + ports: + - "${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}:80" + volumes: + - ./web/dist:/usr/share/nginx/html:ro + - ./web/nginx.conf:/etc/nginx/conf.d/default.conf:ro + environment: + TZ: Asia/Shanghai diff --git a/docker/daemon.json.example b/docker/daemon.json.example new file mode 100644 index 0000000..08b6ffc --- /dev/null +++ b/docker/daemon.json.example @@ -0,0 +1,7 @@ +{ + "registry-mirrors": [ + "https://docker.m.daocloud.io", + "https://docker.1ms.run", + "https://docker.xuanyuan.me" + ] +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f634638 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,27 @@ +# 文档目录 + +本目录为智能体「生成 / 发布 / 灌数」相关说明(中文)。 + +| 文档 | 说明 | +|------|------| +| [智能体-生成发布-能力说明.md](./智能体-生成发布-能力说明.md) | **需求 / 能力边界**(做什么、不做什么、工作流、验收) | +| [智能体-生成发布-API.md](./智能体-生成发布-API.md) | **接口契约**(路径、请求/响应、宿主回执字段) | +| [数据同步-中间件.md](./数据同步-中间件.md) | 跨库实时同步(SQLite/MySQL/Postgres) | + +> 业务用语称「**模块**」。HTTP 路径仍为 `/api/v1/apps/...`。智能体默认无需配置模块白名单即可自建发布。 + +## Linux 部署 + +本仓库根目录提供 `start.sh` / `restart.sh` / `stop.sh` / `pull-and-restart.sh`。 +与宇恒 Web 合并的交互菜单见同级目录 [`../ops/README.md`](../../ops/README.md)(`./deploy-menu.sh`)。 + +**配置热重载**(改域名/Nginx/yaml 不必整栈 rebuild):[`../nginx/README.md`](../nginx/README.md),执行 `./reload-config.sh`。 + +| 改什么 | 文件 | 命令 | +|--------|------|------| +| 容器反代 | `web/nginx.conf` | `./reload-config.sh web` | +| 公开 URL / 发布回执 | `.env` 的 `AIJZ_PUBLIC_BASE_URL` + `platform/etc/platform.docker.yaml` | `./reload-config.sh platform` | +| Gateway | `gateway/etc/gateway.docker.yaml` | `./reload-config.sh gateway` | +| LLM Key | `.env` | `./reload-config.sh ai` | +| 供应商 / 模型列表 | `ai-service/etc/llm.yaml` | `./reload-config.sh ai` | +| 域名 HTTPS | `nginx/aijz.host.conf` + 证书 | `./reload-config.sh host-nginx` | diff --git a/docs/外公司部署-授权与登录控制.md b/docs/外公司部署-授权与登录控制.md new file mode 100644 index 0000000..dc74b6b --- /dev/null +++ b/docs/外公司部署-授权与登录控制.md @@ -0,0 +1,53 @@ +# 登录策略与外公司部署授权 + +## 删文件后旧包还能用吗? + +分几层: + +| 客户删了什么 | 能否拦住旧延期包 | +|--------------|------------------| +| 只删 `leases/*.json` | **能** — 消费记录在 `state/_consumed.json` | +| 只删 leases + 改/清 state 文件 | **能**(有库时)— Postgres `license_consumed` 双写,可恢复 | +| **leases + state + 数据库全删** | 本地拦不住 → 需配置 **`RedeemURL` 联网核销**,或你们**永不重签同一 id** | + +纯离线、客户把机器数据全部清空,没有任何方案能 100% 防复用(等于新装机)。要硬保证:配核销服务,或只发一次性 id 且服务端登记。 + +--- + +## 推荐:延期软件 + 分层持久化 + +```text +./data/license/leases/ # 租约文件(可删) +./data/license/state/ # _consumed.json 已消费 id(签名) +Postgres license_consumed # 第二副本 +RedeemURL(可选) # 你们服务端核销 id +``` + +### yaml + +```yaml +License: + Enabled: true + Customer: "A公司" + LeaseDir: "./data/license/leases" + StateDir: "./data/license/state" + ControlSecret: "换成强密钥" + RedeemURL: "https://license.你们的域名" # 建议生产打开 + SeedNotAfter: "2027-07-30" +``` + +`RedeemURL` 时:导入包会 `POST {RedeemURL}/v1/license/redeem`,服务端若该 id 已核销则拒绝。删光客户机数据后旧包仍无效。 + +临时延期:**仅 1 次**、最多 30 天。同一本地 id 不可二次导入。 + +--- + +## API + +`X-License-Secret`:`renew` / `extend` / `import` + +```bash +curl -X POST http://客户机:8888/api/v1/license/import \ + -H "X-License-Secret: 强密钥" -H "Content-Type: application/json" \ + --data-binary @pack.json +``` diff --git a/docs/数据同步-中间件.md b/docs/数据同步-中间件.md new file mode 100644 index 0000000..8ef77cd --- /dev/null +++ b/docs/数据同步-中间件.md @@ -0,0 +1,100 @@ +# 跨库数据同步中间件 + +支持 **SQLite ↔ MySQL ↔ Postgres**,不要求两端同一种数据库。变更经 **outbox 队列** 近实时投递;冲突进 **冲突队列**。 + +## 权限与隔离 + +| 项 | 说明 | +|----|------| +| 谁可配 | 仅公司**顶级权限(管理员)**,权限名「数据同步」 | +| 谁不可 | 编辑 / 只读、智能体账号(即使有「发布模块」) | +| 数据隔离 | 通道与冲突带 `tenant_id`;公司 A 看不到公司 B 的通道/DSN | +| 多服务器 | 同一公司可建多条通道,分别填 B、C 等库的 DSN | + +## 典型场景:A / B / C + +| 端 | 角色 | +|----|------| +| **A** | 线上库(用户增删改) | +| **B** | 本地库(本机业务 + 接收 C) | +| **C** | 额外数据源(Excel / API / 导入),只写入 **B** | + +推荐配置: + +1. 建一条通道:`local` = B,`remote` = A,**方向 `bidirectional`**,冲突策略 `queue`(或 LWW)。 +2. C 的数据用 **ingest API**(或业务直接写 B)写入本地;触发器进 outbox,再推到 A。 +3. A 上用户改的数据经 outbox 拉回 B。 +4. 怀疑漏数时点 **对账**,或等双向通道约每分钟自动对账。 + +如何保证**不漏、不多**: + +| 手段 | 防什么 | +|------|--------| +| 表触发器 → `_ajz_sync_outbox` | 漏(本地/线上变更必入队) | +| 应用远端时 `WithApplying`(触发器不写 outbox) | 多(A↔B 回声环) | +| 目标 meta 版本相等则跳过 | 多(重复投递) | +| 目标版本更新 → 冲突队列 / LWW | 并发改同一行 | +| 主键对账 reconcile | 漏(存量差、触发器未装前的行) | +| C→B upsert 同主键 | 多(重复灌入) | + +``` +C ──ingest/写库──► B (local) ◄──bidirectional outbox──► A (remote) +``` + +## 能力 + +| 项 | 说明 | +|----|------| +| 方言 | `sqlite` / `mysql` / `postgres` | +| 实时性 | 表触发器写 `_ajz_sync_outbox`,worker 默认每 500ms 拉取 | +| 方向 | 本地→线上 / 线上→本地 / **双向**(A↔B 场景用这个) | +| 冲突 | `queue`(入队)/ `lww_source` / `lww_target` | +| 对账 | `POST .../reconcile`;双向运行中约每分钟自动一次 | +| 外部源 | `POST .../ingest`:C → B,再同步到 A | +| 配置 | 控制台「数据同步」页;可改线上 DSN | + +配置与冲突持久化:`data/dbsync/channels.json`、`conflicts.json`(Docker:`.runtime/dbsync`)。 + +## 控制台用法 + +1. 登录 → **数据同步** → **新建通道** +2. 本地 B:如 `sqlite` + `file:./data/local.db`,表名逗号分隔 +3. 线上 A:`mysql` + `user:pass@tcp(host:3306)/db?parseTime=true` +4. 方向选 **双向** → **测试连接** → **保存** → **启动** +5. 需要补漏时点 **对账**;C 数据走业务写 B 或调用 ingest API + +## API(需公司顶级权限「数据同步」/ 管理员) + +| 方法 | 路径 | +|------|------| +| GET/POST | `/api/v1/admin/sync/channels` | +| GET/PUT/DELETE | `/api/v1/admin/sync/channels/{id}` | +| POST | `/api/v1/admin/sync/test` | +| POST | `/api/v1/admin/sync/channels/{id}/prepare\|start\|stop` | +| POST | `/api/v1/admin/sync/channels/{id}/reconcile` | +| POST | `/api/v1/admin/sync/channels/{id}/ingest` | +| GET | `/api/v1/admin/sync/conflicts` | +| POST | `/api/v1/admin/sync/conflicts/{id}/resolve` | + +### ingest 示例 + +```json +POST /api/v1/admin/sync/channels/{id}/ingest +{ + "table": "article", + "source": "excel", + "rows": [ + { "id": "c-001", "title": "来自 C" } + ] +} +``` + +按主键 upsert 写入本地 B,触发器入 outbox,worker 再推到线上 A。 + +## 注意 + +- 两端业务表结构需兼容(同名列);主键默认 `id`,可用 `pk_columns` 覆盖。 +- MySQL 需账号有建触发器权限。 +- 密钥在 DSN 中;列表页会打码显示。 +- 「实时」为亚秒级轮询 + 触发器,非 MySQL binlog CDC;同机延迟通常 <1s。 +- 对账按**主键集合**补缺行,不做逐字段内容 diff;同 PK 内容冲突仍靠版本 / 冲突队列。 diff --git a/docs/智能体-生成发布-API.md b/docs/智能体-生成发布-API.md new file mode 100644 index 0000000..1e8c1af --- /dev/null +++ b/docs/智能体-生成发布-API.md @@ -0,0 +1,450 @@ +# 智能体 · 生成与发布 API + +Base URL:`http://127.0.0.1:8180`(网关) + +需求 / 能力说明见:[智能体-生成发布-能力说明.md](./智能体-生成发布-能力说明.md) + +> 业务用语:**模块**。API 路径仍为 `/api/v1/apps/...`。 +> **智能体默认无需配置 `app_slugs`**:启用「生成发布」后可自由发布自建模块;`app_slugs` 仅在需要白名单限制时填写。 +> **禁止**在已发布模块的业务库查用户 / 权限。账号绑定只走「首次登记」。 + +--- + +## 总览 + +| 步骤 | 方法 | 路径 | 鉴权 | +|------|------|------|------| +| 首次登记 | POST | `/api/v1/auth/agent/register` | 公开 + `register_secret` | +| 换票 | POST | `/api/v1/auth/token` | 公开(client_credentials) | +| 列模块 | GET | `/api/v1/apps` | Bearer + 「读取模块」(管理=全部;智能体默认不限制) | +| 登记在建 | PUT | `/api/v1/apps/{slug}/draft` | Bearer + 「发布模块」 | +| 读蓝图 | GET | `/api/v1/apps/{slug}/blueprint` | Bearer + 「读取模块」 | +| 列模型 | GET | `/ai/api/v1/llm/providers` | 公开 | +| 生成 | POST | `/api/v1/apps/generate` | 网关可公开 | +| 发布 | POST | `/api/v1/apps/{slug}/publish` | Bearer + 「发布模块」 | +| 公开读(加密路径) | GET | `/api/v1/public/m/{token}/blueprint` | 公开 | +| 导入 | POST | `/api/v1/apps/{slug}/{resource}/import` | Bearer + `row.import` | +| 抽查 | GET | `/api/v1/apps/{slug}/{resource}` | Bearer + `row.read` | + +决策树: + +```text +GET /apps(本账号可见模块) + ├─ 选中已有模块 slug + │ → GET blueprint(避开已有 page id/route) + │ → generate(生成新页面) + │ → publish mode=add_pages|auto + │ → import + └─ 无合适模块 + → generate(完整多页蓝图) + → publish mode=create|auto(自定新 slug,无需预授权) + → import +``` + +--- + +## 0. 首次登记 + +`POST /api/v1/auth/agent/register`(公开) + +宿主第一次接入时调用。必须上传: + +| 宿主侧 | API 字段 | 必填 | 说明 | +|--------|----------|------|------| +| 宇恒 ID | `host_key` | 是 | 稳定唯一标识,幂等绑定 | +| 名称 | `name` | 是 | 控制台显示名 | +| — | `tenant_id` | 否 | 默认 `1` | +| — | `register_secret` | 是 | 与平台注册密钥一致 | + +```json +{ + "name": "宇恒节点显示名", + "host_key": "<宇恒ID>", + "tenant_id": 1, + "register_secret": "dev-only-change-me" +} +``` + +响应含 `client_id` / `client_secret`(只返回一次),`status` 为 `pending`。 +管理员在控制台选角色「生成发布」并启用后即可换票。**可访问模块可留空**(留空=可自由发布自建模块)。 + +同一 `host_key`:pending 重连会轮换 secret;已 active 再 register → 409。 + +--- + +## 1. 换票 + +`POST /api/v1/auth/token`(公开) + +```json +{ + "grant_type": "client_credentials", + "client_id": "agt_xxx", + "client_secret": "明文密钥" +} +``` + +取 `access_token`。后续列模块 / 读蓝图 / 发布 / 导入须: + +`Authorization: Bearer ` + +### 权限与模块授权 + +| 权限 | 用途 | +|------|------| +| 「发布模块」 | 发布模块(必选) | +| 「读取模块」 | 列模块、读蓝图(必选) | +| `row.import` | 灌数(必选) | +| `row.read` | 抽查(建议) | +| `storage.write` / `storage.read` | 素材(建议) | + +推荐角色:**生成发布(`publisher`)**。 + +`app_slugs`:**可选白名单**。 +- **留空(推荐默认)**:不限制,智能体可自定 slug 生成/发布 +- 填写若干 slug 或 `*`:仅允许列表内(`*`=全部) + +生成接口经网关可公开;发布与导入须鉴权(角色权限),**不强制**后台预授权模块。 + +--- + +## 2. 列出模块(先选目标) + +`GET /api/v1/apps` +需 Bearer + 「读取模块」。 + +| 调用方 | 可见范围(`scope`) | +|--------|---------------------| +| 管理账号(非 agent) | `all`:本租户全部(含在建) | +| 智能体(`app_slugs` 空或含 `*`) | `open`:不限制 | +| 智能体(配置了白名单) | `granted`:仅白名单 | + +```bash +curl -s http://127.0.0.1:8180/api/v1/apps \ + -H "Authorization: Bearer " +``` + +```json +{ + "scope": "all", + "items": [ + { + "app_id": "...", + "slug": "settlement", + "name": "沉降观测", + "status": "draft", + "status_label": "在建", + "building": true, + "page_count": 4, + "entity_count": 2, + "updated_at": "2026-07-24T08:00:00Z", + "created_at": "2026-07-24T08:00:00Z" + } + ] +} +``` + +| 情况 | 下一步 | +|------|--------| +| 有目标模块(含在建) | 记下 `slug` → 读蓝图 / 继续生成 → `publish` | +| 没有 | 选定新 `slug` → generate → `PUT .../draft` 登记在建 → `publish` 上线 | + +### 2.1 登记在建模块 + +生成蓝图后、正式发布前: + +`PUT /api/v1/apps/{slug}/draft` +Body:`{ "blueprint": { ... } }` +需 「发布模块」。不跑 DDL;状态为 `draft`(在建)。已发布模块不可用本接口覆盖。 + +--- + +## 3. 读取已发布/在建蓝图 + +`GET /api/v1/apps/{slug}/blueprint` +需 Bearer + 「读取模块」 + 该模块已授权。 + +用途: + +- 生成新页面前,收集已有 `pages[].id` / `route`,避免冲突 +- 发布后自检 + +--- + +## 4. 列出大模型(可选) + +`GET /ai/api/v1/llm/providers`(公开) +兼容:`http://127.0.0.1:8180/ai/api/v1/llm/providers` + +```json +{ + "default": "deepseek", + "providers": [ + { + "id": "deepseek", + "label": "DeepSeek", + "configured": true, + "default_model": "deepseek-chat", + "models": [{ "id": "deepseek-chat", "label": "deepseek-chat" }] + } + ] +} +``` + +--- + +## 5. 生成蓝图 / 生成新页面 + +`POST /api/v1/apps/generate` +(网关转发 AI;兼容 `POST /ai/api/v1/apps/generate`) + +`Content-Type: multipart/form-data` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `prompt` | string | 建议 | 需求描述。**已有模块**时写清:目标 slug、要新增哪些页面、勿复用已有 id/route | +| `storage_mode` | string | 否 | `schema_per_app`(默认)或 `database_per_app` | +| `llm_provider` | string | 否 | 默认 `deepseek` | +| `llm_model` | string | 否 | 空则用厂商默认 | +| `excel` | file | 否 | xlsx / csv / json | +| `data_file` | file | 否 | 同 excel | +| `images` | file[] | 否 | 界面截图 | +| `layout_files` | file[] | 否 | 页面 HTML/MHTML | + +```bash +# 示例:为已有模块生成新页面 +curl -s http://127.0.0.1:8180/api/v1/apps/generate \ + -F "prompt=目标模块 settlement。已有 page id: dash_main, list_records。请新生成「测点筛选」列表+新建表单,id/route 勿冲突。" \ + -F "storage_mode=schema_per_app" \ + -F "llm_provider=deepseek" \ + -F "llm_model=deepseek-chat" \ + -F "excel=@./settlement.xlsx" \ + -F "images=@./board_a.png" +``` + +成功响应要点: + +```json +{ + "draft": { + "version": "1.0", + "meta": { "slug": "settlement", "name": "..." }, + "entities": [], + "apis": {}, + "pages": [], + "storage": {}, + "security": {} + }, + "warnings": [], + "confidence": 0.86, + "require_confirm": true, + "llm_provider": "deepseek", + "llm_model": "deepseek-chat", + "fidelity": { "skipped": false, "target": 95, "final_score": 96, "passed": true }, + "generate_log": { "run_id": "...", "lines": [] } +} +``` + +| 字段 | 说明 | +|------|------| +| `draft` | 蓝图;发布时提交(可微调) | +| `draft.meta.slug` | 新建模块时用此 slug;已有模块发布时以路径 `{slug}` 为准 | +| `draft.pages` | **禁止只生成 1 个 page**;数量不设上限 | +| `draft == null` | 生成失败,看 `warnings` / `generate_log` | + +### 页面约定 + +| type | 用途 | +|------|------| +| `list` | 列表(建议 `actions` 含 `import`) | +| `form_create` | 新建表单 | +| `form_edit` | 编辑表单 | +| `dashboard` | 看板 | +| `detail` | 详情 | + +每个 page 必须有独立 `id` / `title` / `route` / `entity`。 + +```json +"layout": { + "columns": ["sku", "qty", "status"], + "actions": ["create", "edit", "delete", "export", "import", "refresh"] +} +``` + +**已有模块**:`draft` 应主要是**新生成的 pages**(及对应 entities / apis),不要把旧页面再写一遍。 +**新建模块**:`draft` 为完整多页蓝图。 + +--- + +## 6. 发布(宿主 → 建站平台) + +宿主(宇恒)侧「发布」应把建站蓝图与展示元数据 **POST 到本建站平台**;平台落库建站,并**按用户/智能体 ID 加密出文件路径**,回执给宿主表格展示。 + +`POST /api/v1/apps/{slug}/publish` + +Headers: + +- `Authorization: Bearer ` +- `Content-Type: application/json` + +Path `{slug}` = 选定的目标**模块**(新建时为新 slug)。`blueprint.meta.slug` 会对齐到路径 slug。 +默认无需预授权;仅当账号配置了 `app_slugs` 白名单时才校验。 + +```json +{ + "mode": "auto", + "blueprint": { }, + "host_meta": { + "module_name": "whm123", + "publish_style": "immediate", + "host_base_url": "https://whm123.yuheng.com" + } +} +``` + +| 字段 | 说明 | +|------|------| +| `mode` | 见下表 | +| `blueprint` | 建站蓝图(必填) | +| `host_meta.module_name` | 宿主表格「模块名称」 | +| `host_meta.publish_style` | `immediate` → 立即发布上线 | +| `host_meta.host_base_url` | 宿主访问域名;有则作为回执 `access_url` | + +| `mode` | 含义 | +|--------|------| +| `auto`(默认) | 模块已存在 → 发布新生成的页面;不存在 → 新建模块 | +| `add_pages` | 必须已存在;发布草稿中的**新页面** | +| `create` | 必须不存在;新建模块 | +| `replace` | 整份覆盖(慎用) | + +日常用 `auto` 或显式 `add_pages` / `create`。 + +### 加密访问路径 + +平台用当前账号的 **用户/智能体 ID(owner_id)** + 租户 + 模块 slug,加密生成逻辑文件路径: + +- `access_path`:如 `m/ajzm1_...`(路径中**无明文 slug / 用户 id**) +- `access_url`:优先宿主 `host_base_url`;否则 `{PublicBaseURL}/api/v1/public/{access_path}/blueprint` + +公开读取蓝图(无需登录): + +`GET /api/v1/public/m/{token}/blueprint` +其中 `{token}` 为 `access_path` 去掉前缀 `m/` 的段。 + +### 宿主「AI 表格数据」字段映射 + +| 宿主表格 | 取自发布响应 | +|----------|----------------| +| 模块名称 | `module_name` | +| 发布方式 | `publish_style`(`immediate` → 立即发布上线) | +| 访问地址 | `access_url`(或宿主自拼域名 + `access_path`) | +| 发布时间 | `published_at` | +| 状态 | `status` = `published` → 已发布 | + +下一步仍是:**灌数 → 打开模块**。 + +### 已有模块(`auto` / `add_pages`) + +- 保留该模块已有 pages / entities / apis +- 发布草稿中**新生成**的 page(`id`、`route` 不可冲突) +- 新 entity / resource 一并加入;已有 entity 只追加新字段 +- 若没有任何新 page / entity / resource → 400 + +### 新建模块时 blueprint 至少含 + +- `meta.slug` / `meta.name` +- `version`(如 `"1.0"`) +- `storage`:`{ "mode": "schema_per_app", "engine": "postgres" }` +- `security`:`{ "visibility": "private", "roles": [], "row_policies": [] }` +- `apis.base_path` = `/api/v1/apps/{slug}` +- `entities` / `apis` / `pages` + +```bash +curl -s http://127.0.0.1:8180/api/v1/apps/settlement/publish \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"mode":"create","host_meta":{"module_name":"whm123","publish_style":"immediate","host_base_url":"https://whm123.yuheng.com"},"blueprint":{...}}' +``` + +响应示例: + +```json +{ + "app_id": "...", + "slug": "settlement", + "schema_name": "app_t1_settlement", + "status": "published", + "publish_mode": "created", + "module_name": "whm123", + "publish_style": "immediate", + "access_path": "m/ajzm1_...", + "access_url": "https://whm123.yuheng.com", + "published_at": "2026-07-24T02:00:00Z", + "owner_id": 3, + "endpoints": ["GET /api/v1/apps/settlement/blueprint", "..."], + "ddl": [], + "memory_mode": false +} +``` + +| `publish_mode` | 含义 | +|----------------|------| +| `created` | 新建了模块 | +| `pages_added` | 向已有模块发布了新页面 | +| `replaced` | 整份覆盖 | + +| HTTP | 原因 | +|------|------| +| 401 | token 无效 | +| 403 | 缺 「发布模块」;或配置了白名单但不含该 slug | +| 400 | 蓝图非法 / page id 冲突 / mode 与是否存在不一致 | + +--- + +## 7. 导入业务数据(发布后必做) + +发布只建表,**不自动灌数**。 + +`POST /api/v1/apps/{slug}/{resource}/import` +`Content-Type: multipart/form-data` +需 Bearer + `row.import` + 模块授权。 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `file` | file | 是 | xlsx / csv / json | + +`resource` = `apis.resources[].path`(无前导 `/`),如 `records`。 + +```bash +curl -s "http://127.0.0.1:8180/api/v1/apps/settlement/records/import" \ + -H "Authorization: Bearer " \ + -F "file=@./settlement.xlsx" +``` + +```json +{ "inserted": 120, "skipped": 0, "errors": [] } +``` + +抽查: + +`GET /api/v1/apps/{slug}/{resource}?page=1&page_size=5`(需 `row.read`) +确认 `total > 0`,否则任务未完成。 + +--- + +## 8. 推荐调用顺序 + +```text +0. POST /api/v1/auth/agent/register 宇恒ID(host_key) + 名称(name) +1. (管理员)启用 + 角色 publisher(模块白名单可选,默认留空) +2. POST /api/v1/auth/token +3. GET /api/v1/apps 先选模块(仅本账号可见) +4. GET /api/v1/apps/{slug}/blueprint 仅已有模块:避让已有 page +5. POST /api/v1/apps/generate 生成新页面 或 完整蓝图 +6. POST /api/v1/apps/{slug}/publish mode=auto | add_pages | create +7. POST /api/v1/apps/{slug}/{resource}/import +8. GET /api/v1/apps/{slug}/{resource}?page=1&page_size=5 +``` + +不要:查业务库用户表;行级增删改(除 import);admin/audit;对已有模块默认 `replace`。 +语义:已有模块上是 **生成新页面再发布**;新建模块自定 slug 即可,无需后台预授权。 diff --git a/docs/智能体-生成发布-能力说明.md b/docs/智能体-生成发布-能力说明.md new file mode 100644 index 0000000..8dfcf46 --- /dev/null +++ b/docs/智能体-生成发布-能力说明.md @@ -0,0 +1,185 @@ +# 智能体能力说明:生成、发布与灌数 + +一类专用智能体:先选定**模块**,再生成页面并发布到建站平台,最后导入业务数据。 +接口细节见:[智能体-生成发布-API.md](./智能体-生成发布-API.md) + +> 业务用语称「**模块**」(API 路径仍为 `/apps`,字段仍为 `app_slugs`)。 +> **管理账号**可查看本租户全部模块(含在建)。 +> **智能体**:启用并赋予「生成发布」后即可**自由发布自建模块**;`app_slugs` 留空表示不限制,无需后台逐个授权。 + +--- + +## 1. 一句话定位 + +| 项目 | 说明 | +|------|------| +| 做什么 | 登记账号 → 选模块 → **生成页面** → **向建站平台发布** → 导入数据 | +| 得到什么 | 发布回执含模块名、加密访问路径、状态;控制台「打开模块」见多页业务后台 | +| 管理侧 | 管理账号在「模块管理」看到全部模块(在建 / 已发布 / 失败) | +| 不做什么 | 查业务库用户表;角色 / 邀请 / 组织管理;行级增删改(仅允许 import + 只读抽查) | + +--- + +## 2. 核心规则(必读) + +### 2.1 绑定宇恒 ID(首次必做) + +智能体账号**不在**已发布模块的业务表里。禁止在业务库翻用户 / 权限。 + +| 宿主侧 | API 字段 | 说明 | +|--------|----------|------| +| **宇恒 ID** | `host_key` | 稳定唯一标识;**必传** | +| **名称** | `name` | 控制台显示名;**必传** | + +`POST /api/v1/auth/agent/register` → `pending` → 管理员赋「生成发布」并**启用** → 换票。 +**不要求**再填写「可访问模块」才能发布。 + +### 2.2 管理账号与模块可见范围 + +| 账号类型 | `GET /api/v1/apps` 可见范围 | +|----------|------------------------------| +| **管理账号**(如 demo/`owner`,非 agent) | 本租户**全部**模块,含 **在建** 与已发布、失败 | +| **智能体(`app_slugs` 为空或含 `*`)** | 不限制(`scope=open`),可列本租户模块并自由发布自建 slug | +| **智能体(配置了白名单)** | 仅白名单内(`scope=granted`)——可选限制,非默认 | + +状态展示: + +| status | 中文 | 说明 | +|--------|------|------| +| `draft` / `validating` / `provisioning` | **在建** | 已生成蓝图或正在发布,尚未成功上线 | +| `published` | 已发布 | 可打开业务页、可灌数 | +| `failed` | 失败 | 发布失败,可继续编辑后重发 | + +生成蓝图成功后可 `PUT /api/v1/apps/{slug}/draft` 登记为在建。 + +### 2.3 先选模块,再生成 + +发布前建议 `GET /api/v1/apps` 查看已有模块: + +| 选择 | 含义 | +|------|------| +| **已有模块** | 为该模块 **生成新的页面**,再发布到该模块 | +| **没有合适模块** | **生成完整多页蓝图**,再 **新建模块**(自定 slug,无需后台预授权) | + +### 2.4 页面必须多页 + +一次生成应产出多个 `pages`(至少列表 + 新建表单;建议再加编辑 / 看板)。 +页面数量**不设上限**。列表页建议带 `import`。 + +### 2.5 发布 = 向建站平台送建站数据 + +宿主侧点击「发布」时,应把蓝图与展示元数据 **POST 到本建站平台**(不是只在宿主本地落库)。 + +平台会: + +1. 校验并落库模块蓝图(新建或向已有模块发布新页面) +2. **按当前用户 / 智能体 ID 加密生成访问文件路径**(`access_path`) +3. 回执模块名称、发布方式、访问地址、发布时间、状态,供宿主「AI 表格数据」展示 + +### 2.6 发布后必须灌数 + +发布只建结构,**不会自动灌 Excel**。成功后必须 `import`,并建议抽查 `total > 0`。 +宿主提示「下一步:灌数 → 打开模块」与此一致。 + +--- + +## 3. 首次接入流程 + +```text +宇恒 ID + 名称 + → POST /api/v1/auth/agent/register + → pending + → 管理员:角色「生成发布」+ 启用(可访问模块可留空) + → 换票后即可自定 slug 生成/发布 +``` + +`app_slugs` **默认留空即可自由发布**;仅在需要收紧范围时再配白名单。 + +--- + +## 4. 能力边界 + +### 允许 + +1. 首次登记(宇恒 ID + 名称) +2. 换票 +3. 列出本账号可访问模块、读取已发布蓝图 +4. 生成蓝图 / 生成新页面(可带 Excel / 截图 / HTML) +5. 发布到建站平台(新建模块或向已有模块发布新页面;拿加密路径回执) +6. 导入数据 + 只读抽查列表 + +### 禁止 + +- 业务库查用户 / 权限表 +- `row.create` / `row.update` / `row.delete`(除 import) +- admin / audit 等管理接口 +- 对已有模块默认 `mode=replace` 整站覆盖 +- 访问未授权模块(仅当管理员配置了 `app_slugs` 白名单时才受限) + +--- + +## 5. 角色与模块授权 + +推荐角色:**生成发布(`publisher`)** + +| 权限 | 用途 | +|------|------| +| 「发布模块」 | 发布模块 | +| `app.read` | 列模块 / 读蓝图 | +| `row.import` | 灌数 | +| `row.read` | 抽查 | +| `storage.write` / `storage.read` | 素材(建议) | + +`app_slugs`:**可选**。留空 = 不限制,智能体可随意发布自建模块;填写后才按白名单限制。 + +--- + +## 6. 宿主发布回执(需求) + +宿主「AI 表格数据」应展示建站平台发布回执,而不是本地假数据: + +| 表格项 | 含义 | 来自发布响应 | +|--------|------|----------------| +| 模块名称 | 业务显示名 | `module_name` | +| 发布方式 | 如立即发布上线 | `publish_style`(`immediate`) | +| 访问地址 | 可打开的地址 | `access_url`(或宿主域名 + `access_path`) | +| 发布时间 | 平台落库时间 | `published_at` | +| 状态 | 已发布 | `status` = `published` | + +加密路径规则: + +- 输入:租户 ID + 用户/智能体 ID + 模块 slug +- 输出:`access_path`(如 `m/ajzm1_...`),路径中**无明文用户 id / slug** +- 公开读蓝图:`GET /api/v1/public/m/{token}/blueprint` + +--- + +## 7. 标准工作流 + +```text +宇恒ID + 名称 + → register → 管理员启用(publisher;模块白名单可选) + → 换票 + → GET /apps 【先选模块】 + ├─ 已有 → GET blueprint → generate【生成新页面】→ publish(add_pages|auto) + └─ 没有 → generate【完整蓝图】→ publish(create|auto)【新建模块】 + → 宿主用回执展示「AI 表格数据」(含加密访问路径) + → import → 抽查 total > 0 → 打开模块 +``` + +--- + +## 8. 验收清单 + +- [ ] 管理账号可在「模块管理」看到全部模块(含在建) +- [ ] register 传了宇恒 ID + 名称 +- [ ] 角色为「生成发布」 +- [ ] 发布前 `GET /api/v1/apps` 选目标(仅见本账号模块) +- [ ] 已有模块:generate **生成新页面**再发布;无模块才新建 +- [ ] 新页面 `id` / `route` 不与已有冲突 +- [ ] `pages` ≥ 2(含 list,建议带 import) +- [ ] publish 回执含 `access_path` / `access_url` / `published_at` / `status` +- [ ] 宿主表格用回执字段,不写死假地址 +- [ ] generate → publish → import 成功,列表 `total > 0` +- [ ] 「打开模块」可见多页导航 +- [ ] 不会去业务库查用户表 diff --git a/docs/角色说明.md b/docs/角色说明.md new file mode 100644 index 0000000..d89a40b --- /dev/null +++ b/docs/角色说明.md @@ -0,0 +1,69 @@ +# 角色说明 + +权限与角色均使用**中文命名**。分两类:公司成员角色(登录账号)、智能体角色(机器账号)。 + +## 〇、平台超级管理员与权限收窄 + +| 层级 | 谁 | 做什么 | +|------|----|--------| +| 权限模块 | **超级管理员** | 管理全部权限模块;决定每个公司**拥有哪些权限** | +| 公司一级 | **超级管理员** | 平台工作台:新建/改名公司、权限额度、管理员邀请 | +| 打开某公司 | **超级管理员** | 「管理该公司」打开该公司内部视图;**身份仍是超管**,写操作需两次确认 | +| 公司内日常 | **公司管理员** | 额度内分配角色/智能体;**成员管理**(如 demo 属于「演示公司」) | +| 硬边界 | 系统 | 公司账号不能分配或调用未授予的权限;控制台 Tab 按额度显隐 | + +默认账号:**ljk_admin / ljk_admin**。 + +用法:平台工作台 → 某公司「管理该公司」→ 左侧出现角色/成员等 → 修改时两次确认。「返回平台工作台」回到公司列表。 + +开发演示数据:「演示公司」+ 账号 demo/demo123,与超管 ljk_admin 是不同身份。 + +常用权限:读取/写入/发布模块;数据 CRUD 与导入导出;上传下载;审计;管理智能体;邀请成员;管理组织;数据同步。写入模块=保存在建草稿;发布模块=上线。 + +新建公司默认全量公司权限(可收窄至空)。创建时同步生成该公司**管理员**账号(**用户名随机全局唯一**、初始密码随机;明文仅创建时展示一次;登录后可自行「修改密码」)。成员管理也可直接「新建成员」(同样随机用户名)。 + +每家公司有全局唯一 **路径 slug**(如 `demo` → 约定对外 `/{slug}/...`)。平台工作台创建/改名时可填;演示公司固定为 `demo`。规则:2–32 位、小写字母开头、仅 `a-z0-9-`;不可用 `api`/`admin`/`console` 等保留字。网关按 slug 分流属后续阶段。 + +## 一、公司成员角色 + +给真人登录账号用,存在用户表 / JWT 的 `role` 字段。 + +| 角色 | 能做什么 | 不能做什么 | +|------|----------|------------| +| **管理员** | 公司顶级权限:发布模块、管智能体/角色/组织/邀请、**数据同步**、全量数据 CRUD | — | +| **编辑** | 读写业务数据、导入导出、保存草稿(写入模块) | 发布、管组织/邀请/同步、删行 | +| **只读** | 看模块与数据、导出、下文件、看审计 | 任何写入、发布、管理类操作 | +| **待加入** | 仅能注册后等待;需邀请码加入公司 | 一切业务能力 | +| **智能体** | 不按上表套权限,而按所绑智能体角色的权限列表 | 不能配置「数据同步」(无该权限) | + +邀请成员时可选项一般为:**编辑** / **只读** / **管理员**。 + +## 二、智能体角色(默认) + +给 AI / 宿主程序用的服务账号,在「角色管理」里配置,编码与名称均为中文。 + +| 角色 | 适合场景 | 主要权限 | +|------|----------|----------| +| **生成发布** | 建站智能体最低可用集:生成蓝图并发布、导入样例数据 | 读取/发布模块、查询/导入数据、上传下载 | +| **只读** | 只查询、导出的助手 | 读取模块、查询/导出数据、下载、审计 | +| **读写** | 日常维护业务数据,不发布新模块 | 读写模块配置、增改查导入导出、文件 | +| **运维** | 需要删数据、全量行操作与发布的运维机器人 | 含发布、删除及上列大部分能力 | + +可在「角色管理」自定义角色并勾选中文权限;**不要**把「数据同步」赋给智能体(该权限仅管理员角色自带)。 + +## 三、与旧英文码 + +| 旧码 | 现中文 | +|------|--------| +| platform_admin / super_admin | 超级管理员 | +| owner | 管理员 | +| editor(成员) | 编辑 | +| viewer(成员) | 只读 | +| pending | 待加入 | +| agent | 智能体 | +| publisher | 生成发布 | +| editor(智能体角色) | 读写 | +| viewer(智能体角色) | 只读 | +| operator | 运维 | + +读写库与鉴权时会自动把旧英文码归一成中文;启动时默认智能体角色编码会尽量升级为中文。 diff --git a/gateway/.dockerignore b/gateway/.dockerignore new file mode 100644 index 0000000..4160517 --- /dev/null +++ b/gateway/.dockerignore @@ -0,0 +1,5 @@ +.git +gateway.exe +*.md +apisix +docker-compose.yml diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..c1ddda5 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,17 @@ +ARG BASE_REGISTRY=docker.m.daocloud.io/library +FROM ${BASE_REGISTRY}/golang:1.21-bookworm AS build +WORKDIR /src +ENV GOPROXY=https://goproxy.cn,direct +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /out/gateway . + +FROM ${BASE_REGISTRY}/debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY --from=build /out/gateway /app/gateway +COPY etc/gateway.docker.yaml /app/etc/gateway.yaml +EXPOSE 8180 +CMD ["/app/gateway", "-f", "/app/etc/gateway.yaml"] diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000..d02fa98 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,43 @@ +# 网关 + +统一入口:**默认用 Go 网关(本机 :8180)**;Docker 就绪后可切 APISIX(:9080)。 + +## 路由 + +| 对外路径 | 上游 | +|----------|------| +| `/api/v1/*` | platform `:8888` | +| `/ai/*` | AI `:8001`(去掉 `/ai` 前缀) | +| `/gateway/health` | 网关健康检查 | + +JWT 仍由中台校验;网关负责反代、CORS、限流、`X-Request-Id` / `X-Gateway`。 + +## 1)Go 网关(推荐本地) + +```powershell +# 先启动 platform + ai-service +cd gateway +go mod tidy +go run . -f etc/gateway.yaml +``` + +探测: + +```powershell +curl http://127.0.0.1:8180/gateway/health +curl -X POST http://127.0.0.1:8180/api/v1/auth/login -H "Content-Type: application/json" -d "{\"username\":\"demo\",\"password\":\"demo123\"}" +``` + +前端 `vite` 已代理到 `8180`。 + +## 2)APISIX(Docker) + +先启动 Docker Desktop,再: + +```powershell +cd gateway +docker compose up -d +``` + +入口:`http://127.0.0.1:9080` +路由见 `apisix/apisix.yaml`(`host.docker.internal` 访问宿主机服务)。 diff --git a/gateway/apisix/apisix.yaml b/gateway/apisix/apisix.yaml new file mode 100644 index 0000000..3eb9da1 --- /dev/null +++ b/gateway/apisix/apisix.yaml @@ -0,0 +1,38 @@ +routes: + - id: platform-api + uri: /api/v1/* + name: platform-api + methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] + upstream: + type: roundrobin + nodes: + "host.docker.internal:8888": 1 + plugins: + cors: + allow_origins: "**" + allow_methods: "**" + allow_headers: "**" + limit-req: + rate: 50 + burst: 30 + key_type: var + key: remote_addr + rejected_code: 429 + + - id: ai-service + uri: /ai/* + name: ai-service + methods: ["GET", "POST", "OPTIONS"] + upstream: + type: roundrobin + nodes: + "host.docker.internal:8001": 1 + plugins: + cors: + allow_origins: "**" + allow_methods: "**" + allow_headers: "**" + proxy-rewrite: + regex_uri: ["^/ai/(.*)", "/$1"] + +#END diff --git a/gateway/apisix/config.yaml b/gateway/apisix/config.yaml new file mode 100644 index 0000000..d373247 --- /dev/null +++ b/gateway/apisix/config.yaml @@ -0,0 +1,14 @@ +apisix: + node_listen: 9080 + enable_ipv6: false + +deployment: + role: data_plane + role_data_plane: + config_provider: yaml + +plugin_attr: + prometheus: + export_addr: + ip: 0.0.0.0 + port: 9091 diff --git a/gateway/docker-compose.yml b/gateway/docker-compose.yml new file mode 100644 index 0000000..127bb03 --- /dev/null +++ b/gateway/docker-compose.yml @@ -0,0 +1,28 @@ +# APISIX 声明式网关(Docker 启动后使用) +# 前置:启动 Docker Desktop,并保证宿主机 platform:8888 / ai:8001 已运行 + +services: + etcd: + image: bitnami/etcd:3.5 + environment: + ALLOW_NONE_AUTHENTICATION: "yes" + ETCD_ADVERTISE_CLIENT_URLS: http://etcd:2379 + ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 + volumes: + - etcd_data:/bitnami/etcd + + apisix: + image: apache/apisix:3.11.0-debian + depends_on: + - etcd + ports: + - "9080:9080" # HTTP 入口 + - "9180:9180" # Admin API + volumes: + - ./apisix/config.yaml:/usr/local/apisix/conf/config.yaml:ro + - ./apisix/apisix.yaml:/usr/local/apisix/conf/apisix.yaml:ro + environment: + TZ: Asia/Shanghai + +volumes: + etcd_data: diff --git a/gateway/etc/gateway.docker.yaml b/gateway/etc/gateway.docker.yaml new file mode 100644 index 0000000..c55bdd5 --- /dev/null +++ b/gateway/etc/gateway.docker.yaml @@ -0,0 +1,12 @@ +Name: gateway +Host: 0.0.0.0 +Port: 8180 + +PlatformURL: "http://platform:8888" +AIURL: "http://ai:8001" +RateLimitPerMin: 6000 +ProxyTimeoutSec: 60 +CorsEnable: true + +JWTEnable: true +JWTSecret: "dev-only-change-me" diff --git a/gateway/etc/gateway.yaml b/gateway/etc/gateway.yaml new file mode 100644 index 0000000..6ec8885 --- /dev/null +++ b/gateway/etc/gateway.yaml @@ -0,0 +1,12 @@ +Name: gateway +Host: 0.0.0.0 +Port: 8180 + +PlatformURL: "http://127.0.0.1:8888" +AIURL: "http://127.0.0.1:8001" +RateLimitPerMin: 6000 +ProxyTimeoutSec: 60 +CorsEnable: true + +JWTEnable: true +JWTSecret: "dev-only-change-me" diff --git a/gateway/gateway.go b/gateway/gateway.go new file mode 100644 index 0000000..8f82906 --- /dev/null +++ b/gateway/gateway.go @@ -0,0 +1,81 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + + "aijianzhan/gateway/internal/config" + "aijianzhan/gateway/internal/jwtmw" + "aijianzhan/gateway/internal/proxy" + "aijianzhan/gateway/internal/ratelimit" + + "github.com/zeromicro/go-zero/core/conf" +) + +var configFile = flag.String("f", "etc/gateway.yaml", "config file") + +func main() { + flag.Parse() + var c config.Config + conf.MustLoad(*configFile, &c) + + dir, err := proxy.New(c.PlatformURL, c.AIURL, c.ProxyTimeoutSec) + if err != nil { + log.Fatal(err) + } + limiter := ratelimit.New(c.RateLimitPerMin) + h := limiter.Middleware(dir.Handler()) + + if c.JWTEnable { + secret := c.JWTSecret + if secret == "" { + secret = "dev-only-change-me" + } + public := []string{ + "/gateway/health", + "/api/v1/auth/login", + "/api/v1/auth/register", + "/api/v1/auth/token", + "/api/v1/auth/agent/register", + "/api/v1/meta/", + "/api/v1/public/", + "/ai/", + "/ai", + } + h = jwtmw.Middleware(secret, public)(h) + } + + mux := http.NewServeMux() + mux.HandleFunc("/gateway/health", dir.Health) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if c.CorsEnable { + origin := r.Header.Get("Origin") + if origin == "" { + origin = "*" + } + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id, X-Tenant-Id, X-User-Id, X-Role") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Credentials", "true") + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + } + if r.URL.Path == "/gateway/health" { + dir.Health(w, r) + return + } + h(w, r) + }) + + addr := fmt.Sprintf("%s:%d", c.Host, c.Port) + limitDesc := fmt.Sprintf("%d/min (loopback exempt)", c.RateLimitPerMin) + if c.RateLimitPerMin <= 0 { + limitDesc = "disabled" + } + fmt.Printf("gateway listening %s jwt=%v rate=%s → platform=%s ai=%s\n", addr, c.JWTEnable, limitDesc, c.PlatformURL, c.AIURL) + log.Fatal(http.ListenAndServe(addr, mux)) +} diff --git a/gateway/go.mod b/gateway/go.mod new file mode 100644 index 0000000..e51910a --- /dev/null +++ b/gateway/go.mod @@ -0,0 +1,22 @@ +module aijianzhan/gateway + +go 1.21 + +require ( + github.com/golang-jwt/jwt/v4 v4.5.2 + github.com/zeromicro/go-zero v1.6.6 +) + +require ( + github.com/fatih/color v1.17.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + golang.org/x/sys v0.21.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/gateway/go.sum b/gateway/go.sum new file mode 100644 index 0000000..c0170d1 --- /dev/null +++ b/gateway/go.sum @@ -0,0 +1,68 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zeromicro/go-zero v1.6.6 h1:nZTVYObklHiBdYJ/nPoAZ8kGVAplWSDjT7DGE7ur0uk= +github.com/zeromicro/go-zero v1.6.6/go.mod h1:olKf1/hELbSmuIgLgJeoeNVp3tCbLqj6UmO7ATSta4A= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/gateway/internal/config/config.go b/gateway/internal/config/config.go new file mode 100644 index 0000000..88af656 --- /dev/null +++ b/gateway/internal/config/config.go @@ -0,0 +1,16 @@ +package config + +type Config struct { + Name string `json:",optional"` + Host string `json:",default=0.0.0.0"` + Port int `json:",default=8180"` + PlatformURL string `json:",default=http://127.0.0.1:8888"` + AIURL string `json:",default=http://127.0.0.1:8001"` + RateLimitPerMin int `json:",default=240"` + ProxyTimeoutSec int `json:",default=60"` + CorsEnable bool `json:",default=true"` + // 与中台 Auth.AccessSecret 保持一致 + JWTSecret string `json:",optional"` + // 网关预检 JWT;AI 生成可匿名 + JWTEnable bool `json:",default=true"` +} diff --git a/gateway/internal/jwtmw/jwt.go b/gateway/internal/jwtmw/jwt.go new file mode 100644 index 0000000..07349f3 --- /dev/null +++ b/gateway/internal/jwtmw/jwt.go @@ -0,0 +1,102 @@ +package jwtmw + +import ( + "net/http" + "strings" + + "github.com/golang-jwt/jwt/v4" +) + +type Claims struct { + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Role string `json:"role"` + jwt.RegisteredClaims +} + +// Middleware 网关侧预检 JWT;公开路径放行。中台仍会再次校验。 +func Middleware(secret string, publicPrefixes []string) func(http.HandlerFunc) http.HandlerFunc { + parser := jwt.NewParser(jwt.WithValidMethods([]string{"HS256"})) + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + for _, p := range publicPrefixes { + if path == p || strings.HasPrefix(path, p) { + next(w, r) + return + } + } + if r.Method == http.MethodOptions { + next(w, r) + return + } + auth := r.Header.Get("Authorization") + if !strings.HasPrefix(strings.ToLower(auth), "bearer ") { + writeErr(w, http.StatusUnauthorized, "gateway: missing bearer token") + return + } + raw := strings.TrimSpace(auth[len("Bearer "):]) + if raw == "" || strings.Count(raw, ".") != 2 { + writeErr(w, http.StatusUnauthorized, "gateway: malformed token") + return + } + token, err := parser.ParseWithClaims(raw, &Claims{}, func(t *jwt.Token) (any, error) { + return []byte(secret), nil + }) + if err != nil || token == nil || !token.Valid { + writeErr(w, http.StatusUnauthorized, "gateway: invalid token ("+shortJWTErr(err)+"),请重新登录") + return + } + next(w, r) + } + } +} + +func shortJWTErr(err error) string { + if err == nil { + return "rejected" + } + msg := err.Error() + switch { + case strings.Contains(msg, "expired"): + return "expired" + case strings.Contains(msg, "signature"): + return "bad signature" + case strings.Contains(msg, "malformed"): + return "malformed" + case strings.Contains(msg, "used before"): + return "not yet valid" + default: + if len(msg) > 80 { + return msg[:80] + } + return msg + } +} + +func writeErr(w http.ResponseWriter, code int, msg string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + _, _ = w.Write([]byte(`{"code":` + itoa(code) + `,"message":"` + escapeJSON(msg) + `"}`)) +} + +func escapeJSON(s string) string { + s = strings.ReplaceAll(s, `\`, `\\`) + s = strings.ReplaceAll(s, `"`, `\"`) + s = strings.ReplaceAll(s, "\n", " ") + return s +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + var b [12]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + return string(b[i:]) +} diff --git a/gateway/internal/proxy/director.go b/gateway/internal/proxy/director.go new file mode 100644 index 0000000..a9be3ec --- /dev/null +++ b/gateway/internal/proxy/director.go @@ -0,0 +1,186 @@ +package proxy + +import ( + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "time" +) + +type Director struct { + Platform *url.URL + AI *url.URL + Timeout time.Duration +} + +func New(platformURL, aiURL string, timeoutSec int) (*Director, error) { + p, err := url.Parse(platformURL) + if err != nil { + return nil, err + } + a, err := url.Parse(aiURL) + if err != nil { + return nil, err + } + if timeoutSec <= 0 { + timeoutSec = 60 + } + return &Director{Platform: p, AI: a, Timeout: time.Duration(timeoutSec) * time.Second}, nil +} + +func (d *Director) Handler() http.HandlerFunc { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + return func(w http.ResponseWriter, r *http.Request) { + target, path := d.route(r.URL.Path) + proxy := httputil.NewSingleHostReverseProxy(target) + proxy.Transport = transport + proxy.FlushInterval = 100 * time.Millisecond + proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(http.StatusBadGateway) + _, _ = rw.Write([]byte(`{"code":502,"message":"upstream unavailable: ` + escape(err.Error()) + `"}`)) + } + proxy.ModifyResponse = func(resp *http.Response) error { + // 网关已统一加 CORS;去掉上游重复头,避免浏览器报 Failed to fetch + for _, h := range []string{ + "Access-Control-Allow-Origin", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Expose-Headers", + "Access-Control-Max-Age", + } { + resp.Header.Del(h) + } + resp.Header.Set("X-Gateway", "aijianzhan-gateway") + return nil + } + + // 重写路径 + r.URL.Path = path + r.Host = target.Host + r.Header.Set("X-Forwarded-Host", r.Header.Get("Host")) + r.Header.Set("X-Forwarded-Proto", "http") + if r.Header.Get("X-Request-Id") == "" { + r.Header.Set("X-Request-Id", newRequestID()) + } + + // 超时上下文 + ctx := r.Context() + proxy.ServeHTTP(w, r.WithContext(ctx)) + } +} + +func (d *Director) route(path string) (*url.URL, string) { + // AI:/ai/* → 上游去掉 /ai 前缀 + if strings.HasPrefix(path, "/ai/") || path == "/ai" { + next := strings.TrimPrefix(path, "/ai") + if next == "" { + next = "/" + } + return d.AI, next + } + // 生成蓝图:统一 /api/v1/apps/generate(兼容旧冒号路径,避免重复实现) + if path == "/api/v1/apps/generate" || strings.HasPrefix(path, "/api/v1/apps/generate?") { + return d.AI, path + } + if strings.HasPrefix(path, "/api/v1/apps:generate") || strings.Contains(path, "/apps%3Agenerate") { + return d.AI, "/api/v1/apps/generate" + } + // 其余 /api 走中台 + return d.Platform, path +} + +func escape(s string) string { + s = strings.ReplaceAll(s, `\`, `\\`) + s = strings.ReplaceAll(s, `"`, `\"`) + if len(s) > 200 { + s = s[:200] + } + return s +} + +func newRequestID() string { + return strings.ReplaceAll(time.Now().UTC().Format("20060102T150405.000000000"), ".", "") +} + +// Health 聚合探测 +func (d *Director) Health(w http.ResponseWriter, _ *http.Request) { + type st struct { + Name string `json:"name"` + OK bool `json:"ok"` + Detail string `json:"detail,omitempty"` + } + client := &http.Client{Timeout: 2 * time.Second} + out := []st{ + probe(client, "platform", d.Platform.String()+"/api/v1/auth/login"), + probe(client, "ai", d.AI.String()+"/health"), + } + allOK := true + for _, s := range out { + if !s.OK { + allOK = false + break + } + } + code := http.StatusOK + if !allOK { + code = http.StatusServiceUnavailable + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + _, _ = io.WriteString(w, `{"gateway":"ok","upstreams":[`) + for i, s := range out { + if i > 0 { + _, _ = io.WriteString(w, ",") + } + ok := "false" + if s.OK { + ok = "true" + } + _, _ = io.WriteString(w, `{"name":"`+s.Name+`","ok":`+ok+`}`) + } + _, _ = io.WriteString(w, `]}`) +} + +func probe(client *http.Client, name, rawURL string) struct { + Name string `json:"name"` + OK bool `json:"ok"` + Detail string `json:"detail,omitempty"` +} { + // login 用 OPTIONS/GET 可能 405;用短超时 HEAD/GET health 风格 + req, _ := http.NewRequest(http.MethodGet, rawURL, nil) + if name == "platform" { + // 未登录会 401/405 都说明服务活着;连接失败才算挂 + req, _ = http.NewRequest(http.MethodPost, rawURL, strings.NewReader(`{}`)) + req.Header.Set("Content-Type", "application/json") + } + resp, err := client.Do(req) + if err != nil { + return struct { + Name string `json:"name"` + OK bool `json:"ok"` + Detail string `json:"detail,omitempty"` + }{Name: name, OK: false, Detail: err.Error()} + } + defer resp.Body.Close() + return struct { + Name string `json:"name"` + OK bool `json:"ok"` + Detail string `json:"detail,omitempty"` + }{Name: name, OK: true} +} diff --git a/gateway/internal/ratelimit/limiter.go b/gateway/internal/ratelimit/limiter.go new file mode 100644 index 0000000..2ead0e4 --- /dev/null +++ b/gateway/internal/ratelimit/limiter.go @@ -0,0 +1,76 @@ +package ratelimit + +import ( + "net" + "net/http" + "strings" + "sync" + "time" +) + +type Limiter struct { + mu sync.Mutex + visitors map[string]*visitor + rate int // 0 = disabled + window time.Duration +} + +type visitor struct { + count int + reset time.Time +} + +// New creates a per-IP limiter. ratePerMinute <= 0 disables limiting. +func New(ratePerMinute int) *Limiter { + return &Limiter{visitors: map[string]*visitor{}, rate: ratePerMinute, window: time.Minute} +} + +func (l *Limiter) Middleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if l.rate <= 0 { + next(w, r) + return + } + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil || ip == "" { + ip = r.RemoteAddr + } + // 本机开发:Vite 代理与所有本机请求共用 127.0.0.1,不做限流 + if isLoopback(ip) { + next(w, r) + return + } + if !l.allow(ip) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusTooManyRequests) + _, _ = w.Write([]byte(`{"code":429,"message":"gateway rate limit exceeded"}`)) + return + } + next(w, r) + } +} + +func isLoopback(ip string) bool { + ip = strings.Trim(ip, "[]") + if ip == "127.0.0.1" || ip == "::1" || ip == "localhost" { + return true + } + parsed := net.ParseIP(ip) + return parsed != nil && parsed.IsLoopback() +} + +func (l *Limiter) allow(key string) bool { + l.mu.Lock() + defer l.mu.Unlock() + now := time.Now() + v, ok := l.visitors[key] + if !ok || now.After(v.reset) { + l.visitors[key] = &visitor{count: 1, reset: now.Add(l.window)} + return true + } + if v.count >= l.rate { + return false + } + v.count++ + return true +} diff --git a/nginx/README.md b/nginx/README.md new file mode 100644 index 0000000..5a34e9c --- /dev/null +++ b/nginx/README.md @@ -0,0 +1,53 @@ +# Nginx 与可热重载配置(Linux Docker 部署) + +AI 建站默认用 **Docker web 容器**(`127.0.0.1:5173`)对外提供页面,并在容器内把 `/api/`、`/ai/` 反代到 gateway。 + +若需 **独立域名 + HTTPS**(与同机 yh_web 并存),用 **宿主机 Nginx** 反代到 `5173`,不要与宇恒抢同一 `server_name`。 + +## 配置文件映射(改完可只 reload / 单容器 restart) + +| 用途 | 仓库路径 | 生效方式 | +|------|----------|----------| +| 容器 Web 反代 | `web/nginx.conf` | `./reload-config.sh web` | +| Platform | `platform/etc/platform.docker.yaml` | `./reload-config.sh platform` | +| Gateway | `gateway/etc/gateway.docker.yaml` | `./reload-config.sh gateway` | +| LLM / 视觉 | `.env` | `./reload-config.sh ai` | +| 供应商与模型 | `ai-service/etc/llm.yaml` | `./reload-config.sh ai` | +| 宿主机域名 HTTPS | `nginx/aijz.host.conf` → `/etc/nginx/conf.d/` | `./reload-config.sh host-nginx` | +| SSL 证书 | `nginx/<域名>.pem` + `.key` → `/etc/ssl/aijianzhan/<域名>/` | `./reload-config.sh host-nginx` | +| 域名验证文件 | `verify-root/` | `./reload-config.sh host-nginx` | +| 前端静态 | `web/dist/` | 覆盖文件即可,无需重启 | + +## 启用宿主机 Nginx(可选) + +在 `.env` 中: + +```bash +AIJZ_DOMAIN=aijz.example.com +AIJZ_ENABLE_HOST_NGINX=1 +AIJZ_PUBLIC_BASE_URL=https://aijz.example.com +``` + +证书可放在项目 `nginx/` 下(与 yh_web 相同命名习惯): + +- `nginx/aijz.example.com.pem` + `nginx/aijz.example.com.key` +- 或 `nginx/fullchain.pem` + `nginx/privkey.pem` + +然后: + +```bash +./start.sh # 或 ./restart.sh,会自动同步证书并生成 /etc/nginx/conf.d/<域名>.conf +./reload-config.sh host-nginx # 仅改 Nginx/证书/verify-root 时 +``` + +## 常用命令 + +```bash +./reload-config.sh # 全部轻量重载(不 rebuild、不重建 dist) +./reload-config.sh web # 容器 nginx -s reload +./reload-config.sh platform # 只 restart platform(读新 yaml) +./reload-config.sh gateway # 只 restart gateway +./reload-config.sh host-nginx # nginx -t && systemctl reload nginx +``` + +整栈重建仍用 `./restart.sh` 或 `./pull-and-restart.sh`。 diff --git a/nginx/aijz.host.conf b/nginx/aijz.host.conf new file mode 100644 index 0000000..1598d4e --- /dev/null +++ b/nginx/aijz.host.conf @@ -0,0 +1,52 @@ +# 宿主机 Nginx:443 终止 TLS,反代到本机 Docker web(127.0.0.1:5173) +# web 容器内 nginx 已把 /api/、/ai/、/gateway/ 转发到 gateway。 +# +# 占位符(由 scripts/lib-aijz-deploy.sh 替换): +# __DOMAIN__ 站点域名 +# __WEB_PORT__ 宿主机 web 映射端口(默认 5173) +# __VERIFY_ROOT__ 域名验证文件目录(项目 verify-root/) +# +# 手动部署: +# 1. 证书放到 /etc/ssl/aijianzhan/__DOMAIN__/{fullchain.pem,privkey.pem} +# 2. sudo cp 生成后的 conf 到 /etc/nginx/conf.d/ +# 3. sudo nginx -t && sudo systemctl reload nginx + +server { + listen 80; + listen [::]:80; + server_name __DOMAIN__; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name __DOMAIN__; + client_max_body_size 128m; + + ssl_certificate /etc/ssl/aijianzhan/__DOMAIN__/fullchain.pem; + ssl_certificate_key /etc/ssl/aijianzhan/__DOMAIN__/privkey.pem; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_protocols TLSv1.2 TLSv1.3; + + location ~ ^/[A-Za-z0-9._-]+\.(txt|html|xml)$ { + root __VERIFY_ROOT__; + try_files $uri =404; + default_type text/plain; + add_header Cache-Control "no-store"; + } + + location / { + proxy_pass http://127.0.0.1:__WEB_PORT__; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } +} diff --git a/platform/.dockerignore b/platform/.dockerignore new file mode 100644 index 0000000..609097d --- /dev/null +++ b/platform/.dockerignore @@ -0,0 +1,5 @@ +.git +.runtime +**/*_test.go +platform.exe +*.md diff --git a/platform/Dockerfile b/platform/Dockerfile new file mode 100644 index 0000000..89aa9e4 --- /dev/null +++ b/platform/Dockerfile @@ -0,0 +1,18 @@ +ARG BASE_REGISTRY=docker.m.daocloud.io/library +FROM ${BASE_REGISTRY}/golang:1.25-bookworm AS build +WORKDIR /src +ENV GOPROXY=https://goproxy.cn,direct +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /out/platform . + +FROM ${BASE_REGISTRY}/debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY --from=build /out/platform /app/platform +COPY etc/platform.docker.yaml /app/etc/platform.yaml +RUN mkdir -p /app/data/uploads +EXPOSE 8888 +CMD ["/app/platform", "-f", "/app/etc/platform.yaml"] diff --git a/platform/README.md b/platform/README.md new file mode 100644 index 0000000..fe6869d --- /dev/null +++ b/platform/README.md @@ -0,0 +1,80 @@ +# Platform(go-zero)骨架 + +Publish(校验蓝图 → 分配 schema → 生成/执行 DDL → 注册元数据)+ 动态 CRUD + JWT。 + +## 目录 + +```text +platform/ + platform.go + docker-compose.yml # 本地 Postgres + etc/platform.yaml + internal/ + authx/ # JWT 签发/校验 + Dev 头兜底 + blueprint/ + schema/ # Postgres DDL(数值 PK 用 IDENTITY) + meta/ + crud/ # MemoryEngine + PostgresEngine + logic/applogic/ + handler/ +``` + +## 运行(内存 + JWT) + +```bash +cd platform +go test ./... +go run . -f etc/platform.yaml +# 另开终端 +go run ./scripts/smoke.go +``` + +冒烟会:签发 JWT → publish → create → list → 非法 token 应 401。 + +## 运行(Postgres 行级 Engine) + +```bash +docker compose up -d +``` + +改 `etc/platform.yaml`: + +```yaml +DataSource: "postgres://platform:platform@127.0.0.1:5432/platform?sslmode=disable" +DevAuth: false +``` + +再启动服务。此时: +- publish **真实建 schema/表** +- CRUD 走 `PostgresEngine`(参数化 SQL + `tenant_id` 强制条件) + +## JWT + +```http +POST /api/v1/auth/token +{"tenant_id":1,"user_id":1,"secret":"dev-only-change-me"} + +→ {"access_token":"...","token_type":"Bearer","expires_at":...} +``` + +业务请求: + +```http +Authorization: Bearer +``` + +| 配置 | 行为 | +|------|------| +| `DevAuth: false` | 必须带合法 JWT | +| `DevAuth: true` | 无 JWT 时可用 `X-Tenant-Id` / `X-User-Id`(仅本地) | +| 带了错误 Bearer | 一律 401(即使 DevAuth=true) | + +生产请更换 `Auth.AccessSecret` / `IssueSecret`,并关掉 `DevAuth`。 + +## 元数据落库 + +启用 DataSource 后自动迁移并使用 `platform_meta.tenant_apps`: + +- 存 blueprint / ddl / endpoints / status +- 进程重启后仍可按 slug 解析动态 CRUD +- 无库时回退内存 `MemoryStore` diff --git a/platform/docker-compose.yml b/platform/docker-compose.yml new file mode 100644 index 0000000..50a3f60 --- /dev/null +++ b/platform/docker-compose.yml @@ -0,0 +1,20 @@ +services: + postgres: + image: postgres:16-alpine + container_name: aijianzhan-pg + environment: + POSTGRES_USER: platform + POSTGRES_PASSWORD: platform + POSTGRES_DB: platform + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U platform -d platform"] + interval: 3s + timeout: 3s + retries: 20 + +volumes: + pgdata: diff --git a/platform/etc/platform.docker.yaml b/platform/etc/platform.docker.yaml new file mode 100644 index 0000000..5ba18f0 --- /dev/null +++ b/platform/etc/platform.docker.yaml @@ -0,0 +1,42 @@ +Name: platform +Host: 0.0.0.0 +Port: 8888 + +DataSource: "postgres://platform:platform@postgres:5432/platform?sslmode=disable" +DryRun: false +DevAuth: false +PublicBaseURL: "http://127.0.0.1:8180" +# 生产:改为对外 HTTPS 域名(与 .env 中 AIJZ_PUBLIC_BASE_URL 一致),改后执行 ./reload-config.sh platform +# PublicBaseURL: "https://aijz.example.com" +RateLimitPerMin: 6000 + +Auth: + AccessSecret: "dev-only-change-me" + AccessExpire: 86400 + IssueSecret: "dev-only-change-me" + PhoneLoginOnly: false + RequirePhoneBound: false + +License: + Enabled: false + Customer: "" + LeaseDir: "./data/license/leases" + StateDir: "./data/license/state" + ControlSecret: "dev-license-control-secret" + SeedNotAfter: "" + GraceDays: 0 + Message: "授权已过期,请联系宇信达续费" + +Agent: + CapsuleSecret: "dev-agent-capsule-secret" + RegisterSecret: "dev-only-change-me" + +Storage: + Driver: local + LocalRoot: /app/data/uploads + PublicBase: "http://127.0.0.1:8180/api/v1/storage" + # 生产:与 PublicBaseURL 同域,例如 https://aijz.example.com/api/v1/storage + +DBSync: + Enabled: true + DataDir: /app/data/dbsync diff --git a/platform/etc/platform.yaml b/platform/etc/platform.yaml new file mode 100644 index 0000000..57f73cb --- /dev/null +++ b/platform/etc/platform.yaml @@ -0,0 +1,47 @@ +Name: platform +Host: 0.0.0.0 +Port: 8888 + +DataSource: "postgres://platform:platform@127.0.0.1:5432/platform?sslmode=disable" +DryRun: false +DevAuth: false +PublicBaseURL: "http://127.0.0.1:8180" +RateLimitPerMin: 6000 + +Auth: + AccessSecret: "dev-only-change-me" + AccessExpire: 86400 # 登录 JWT 有效期(秒),默认 24h;过期需重新登录 + IssueSecret: "dev-only-change-me" + # 强制仅手机号登录。有 License 期限控制时运行时会自动忽略,允许用户名登录。 + PhoneLoginOnly: false + RequirePhoneBound: false + +SMS: + Provider: dev + CodeTTLSeconds: 300 # 短信验证码有效期(秒) + ResendSeconds: 60 + # DevFixedCode: "123456" + +# 外公司部署:每次续费/延期生成独立签名文件(目录非固定单文件) +License: + Enabled: false + Customer: "" + LeaseDir: "./data/license/leases" + StateDir: "./data/license/state" # 消费账本(与 leases 分离) + ControlSecret: "dev-license-control-secret" + # RedeemURL: "https://license.yuxinda.example" # 可选:联网核销,防删库后复用旧包 + SeedNotAfter: "" + GraceDays: 0 + Message: "授权已过期,请联系宇信达续费" + +Agent: + CapsuleSecret: "dev-agent-capsule-secret" + RegisterSecret: "dev-only-change-me" + +Storage: + Driver: local + LocalRoot: ./data/uploads + +DBSync: + Enabled: true + DataDir: ./data/dbsync diff --git a/platform/go.mod b/platform/go.mod new file mode 100644 index 0000000..9acabaf --- /dev/null +++ b/platform/go.mod @@ -0,0 +1,66 @@ +module aijianzhan/platform + +go 1.25.0 + +require ( + github.com/go-sql-driver/mysql v1.8.1 + github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/google/uuid v1.6.0 + github.com/lib/pq v1.12.3 + github.com/xuri/excelize/v2 v2.8.1 + github.com/zeromicro/go-zero v1.6.6 + golang.org/x/crypto v0.24.0 + modernc.org/sqlite v1.34.5 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect + github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect +) diff --git a/platform/go.sum b/platform/go.sum new file mode 100644 index 0000000..acf23de --- /dev/null +++ b/platform/go.sum @@ -0,0 +1,186 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= +github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/zeromicro/go-zero v1.6.6 h1:nZTVYObklHiBdYJ/nPoAZ8kGVAplWSDjT7DGE7ur0uk= +github.com/zeromicro/go-zero v1.6.6/go.mod h1:olKf1/hELbSmuIgLgJeoeNVp3tCbLqj6UmO7ATSta4A= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= +modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/platform/internal/agentcap/capsule.go b/platform/internal/agentcap/capsule.go new file mode 100644 index 0000000..7dd5525 --- /dev/null +++ b/platform/internal/agentcap/capsule.go @@ -0,0 +1,121 @@ +package agentcap + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "strings" + + "golang.org/x/crypto/hkdf" +) + +const Prefix = "AJZ1" + +// Descriptor 智能体解密后才能看到的请求契约(前端不展示明文)。 +type Descriptor struct { + Version string `json:"v"` + BaseURL string `json:"base_url"` + AppSlug string `json:"app_slug"` + TenantHint string `json:"tenant_hint,omitempty"` + Auth AuthSpec `json:"auth"` + Resources []ResourceSpec `json:"resources"` + Notes string `json:"notes,omitempty"` +} + +type AuthSpec struct { + Type string `json:"type"` // bearer_jwt + Header string `json:"header"` +} + +type ResourceSpec struct { + Name string `json:"name"` + Path string `json:"path"` + Methods []string `json:"methods"` + Filters []string `json:"filters,omitempty"` + Sorts []string `json:"sorts,omitempty"` + Fields []FieldSpec `json:"fields,omitempty"` + PrimaryKey string `json:"primary_key"` +} + +type FieldSpec struct { + Name string `json:"name"` + Type string `json:"type"` +} + +func DeriveKey(masterSecret string, tenantID int64, userID int64) []byte { + h := hkdf.New(sha256.New, []byte(masterSecret), []byte("aijianzhan-agent-v1"), []byte(fmt.Sprintf("%d:%d", tenantID, userID))) + key := make([]byte, 32) + _, _ = io.ReadFull(h, key) + return key +} + +func Encrypt(key []byte, desc *Descriptor) (string, error) { + raw, err := json.Marshal(desc) + if err != nil { + return "", err + } + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + ciphertext := gcm.Seal(nil, nonce, raw, nil) + return fmt.Sprintf("%s.%s.%s", + Prefix, + base64.RawURLEncoding.EncodeToString(nonce), + base64.RawURLEncoding.EncodeToString(ciphertext), + ), nil +} + +func Decrypt(key []byte, capsule string) (*Descriptor, error) { + parts := strings.Split(capsule, ".") + if len(parts) != 3 || parts[0] != Prefix { + return nil, fmt.Errorf("invalid capsule format") + } + nonce, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, err + } + ciphertext, err := base64.RawURLEncoding.DecodeString(parts[2]) + if err != nil { + return nil, err + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + plain, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, fmt.Errorf("decrypt failed") + } + var desc Descriptor + if err := json.Unmarshal(plain, &desc); err != nil { + return nil, err + } + return &desc, nil +} + +func PublicAgentKey(masterSecret string, tenantID, userID int64) string { + key := DeriveKey(masterSecret, tenantID, userID) + return base64.RawURLEncoding.EncodeToString(key) +} + +func ParseAgentKey(b64 string) ([]byte, error) { + return base64.RawURLEncoding.DecodeString(b64) +} diff --git a/platform/internal/agentcap/capsule_test.go b/platform/internal/agentcap/capsule_test.go new file mode 100644 index 0000000..48ce950 --- /dev/null +++ b/platform/internal/agentcap/capsule_test.go @@ -0,0 +1,36 @@ +package agentcap + +import ( + "testing" +) + +func TestEncryptDecrypt(t *testing.T) { + key := DeriveKey("secret", 1, 2) + cap, err := Encrypt(key, &Descriptor{ + Version: "1", + BaseURL: "http://127.0.0.1:8888", + AppSlug: "demo", + Auth: AuthSpec{Type: "bearer_jwt", Header: "Authorization"}, + Resources: []ResourceSpec{{ + Name: "items", Path: "/api/v1/apps/demo/items", + Methods: []string{"GET", "POST"}, PrimaryKey: "id", + Fields: []FieldSpec{{Name: "title", Type: "string"}}, + }}, + }) + if err != nil { + t.Fatal(err) + } + if cap[:4] != Prefix { + t.Fatalf("prefix: %s", cap) + } + desc, err := Decrypt(key, cap) + if err != nil { + t.Fatal(err) + } + if desc.AppSlug != "demo" || len(desc.Resources) != 1 { + t.Fatalf("%+v", desc) + } + if _, err := Decrypt([]byte("bad-key-bad-key-bad-key-bad!!!!"), cap); err == nil { + t.Fatal("expected decrypt fail") + } +} diff --git a/platform/internal/agentcap/modpath.go b/platform/internal/agentcap/modpath.go new file mode 100644 index 0000000..a911f18 --- /dev/null +++ b/platform/internal/agentcap/modpath.go @@ -0,0 +1,114 @@ +package agentcap + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "strings" + "time" +) + +const ModulePathPrefix = "ajzm1_" + +// ModulePathClaim 写入加密路径 token。根据用户/智能体 ID 生成路径,公开侧可用平台密钥解开。 +type ModulePathClaim struct { + TenantID int64 `json:"t"` + OwnerID int64 `json:"o"` // user_id 或 agent_id(宇恒侧用户) + Slug string `json:"s"` + IssuedAt int64 `json:"iat"` +} + +func modulePathKey(masterSecret string) []byte { + sum := sha256.Sum256([]byte("aijianzhan-module-path-v1|" + masterSecret)) + return sum[:] +} + +// SealModulePath 根据用户 ID(及租户、模块 slug)加密,返回: +// - token:单段密文(可作 URL 段) +// - filePath:逻辑文件路径 m/{token}(不含明文用户 id / slug) +func SealModulePath(masterSecret string, tenantID, ownerID int64, slug string) (token, filePath string, err error) { + slug = strings.TrimSpace(slug) + if slug == "" { + return "", "", fmt.Errorf("slug required") + } + if ownerID <= 0 { + return "", "", fmt.Errorf("owner id required") + } + if tenantID <= 0 { + return "", "", fmt.Errorf("tenant id required") + } + claim := ModulePathClaim{ + TenantID: tenantID, + OwnerID: ownerID, + Slug: slug, + IssuedAt: time.Now().UTC().Unix(), + } + raw, err := json.Marshal(claim) + if err != nil { + return "", "", err + } + key := modulePathKey(masterSecret) + block, err := aes.NewCipher(key) + if err != nil { + return "", "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", "", err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", "", err + } + ciphertext := gcm.Seal(nil, nonce, raw, nil) + packed := append(nonce, ciphertext...) + token = ModulePathPrefix + base64.RawURLEncoding.EncodeToString(packed) + filePath = "m/" + token + return token, filePath, nil +} + +// OpenModulePath 解密路径 token,得到租户、用户、模块 slug。 +func OpenModulePath(masterSecret, token string) (*ModulePathClaim, error) { + token = strings.TrimSpace(token) + token = strings.TrimPrefix(token, "/") + if strings.HasPrefix(token, "m/") { + token = strings.TrimPrefix(token, "m/") + } + if !strings.HasPrefix(token, ModulePathPrefix) { + return nil, fmt.Errorf("invalid module path token") + } + packed, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(token, ModulePathPrefix)) + if err != nil { + return nil, fmt.Errorf("invalid module path token encoding") + } + key := modulePathKey(masterSecret) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + ns := gcm.NonceSize() + if len(packed) < ns+1 { + return nil, fmt.Errorf("invalid module path token length") + } + plain, err := gcm.Open(nil, packed[:ns], packed[ns:], nil) + if err != nil { + return nil, fmt.Errorf("module path decrypt failed") + } + var claim ModulePathClaim + if err := json.Unmarshal(plain, &claim); err != nil { + return nil, err + } + if claim.TenantID <= 0 || claim.OwnerID <= 0 || claim.Slug == "" { + return nil, fmt.Errorf("module path claim incomplete") + } + return &claim, nil +} diff --git a/platform/internal/agentcap/modpath_test.go b/platform/internal/agentcap/modpath_test.go new file mode 100644 index 0000000..2136064 --- /dev/null +++ b/platform/internal/agentcap/modpath_test.go @@ -0,0 +1,28 @@ +package agentcap + +import "testing" + +func TestSealOpenModulePath(t *testing.T) { + secret := "test-secret" + token, filePath, err := SealModulePath(secret, 1, 42, "settlement") + if err != nil { + t.Fatal(err) + } + if filePath != "m/"+token { + t.Fatalf("filePath=%s", filePath) + } + claim, err := OpenModulePath(secret, token) + if err != nil { + t.Fatal(err) + } + if claim.TenantID != 1 || claim.OwnerID != 42 || claim.Slug != "settlement" { + t.Fatalf("%+v", claim) + } + claim2, err := OpenModulePath(secret, filePath) + if err != nil { + t.Fatal(err) + } + if claim2.Slug != "settlement" { + t.Fatalf("%+v", claim2) + } +} diff --git a/platform/internal/agentstore/store.go b/platform/internal/agentstore/store.go new file mode 100644 index 0000000..b4dd558 --- /dev/null +++ b/platform/internal/agentstore/store.go @@ -0,0 +1,688 @@ +package agentstore + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "errors" + "fmt" + "strings" + "sync" + "time" + + "golang.org/x/crypto/bcrypt" + + "aijianzhan/platform/internal/authx" +) + +const ( + StatusActive = "active" + StatusPending = "pending" + StatusDisabled = "disabled" +) + +type Account struct { + AgentID int64 `json:"agent_id"` + TenantID int64 `json:"tenant_id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + HostKey string `json:"host_key,omitempty"` + RoleID int64 `json:"role_id,omitempty"` + RoleCode string `json:"role_code,omitempty"` + RoleName string `json:"role_name,omitempty"` + Status string `json:"status"` + Perms []string `json:"permissions"` + AppSlugs []string `json:"app_slugs"` + CreatedBy int64 `json:"created_by"` + CreatedAt time.Time `json:"created_at"` + LastTokenAt *time.Time `json:"last_token_at,omitempty"` +} + +type CreateInput struct { + Name string + Perms []string + AppSlugs []string + Status string // 空则 active + HostKey string + RoleID int64 +} + +type UpdateInput struct { + Name *string + Status *string + Perms *[]string + AppSlugs *[]string + RoleID *int64 +} + +type Store interface { + List(ctx context.Context, tenantID int64) ([]Account, error) + Get(ctx context.Context, tenantID, agentID int64) (*Account, error) + Create(ctx context.Context, tenantID, createdBy int64, in CreateInput) (acc *Account, clientSecret string, err error) + Register(ctx context.Context, tenantID int64, name, hostKey string) (acc *Account, clientSecret string, reused bool, err error) + Update(ctx context.Context, tenantID, agentID int64, in UpdateInput) (*Account, error) + RotateSecret(ctx context.Context, tenantID, agentID int64) (clientSecret string, err error) + Delete(ctx context.Context, tenantID, agentID int64) error + Authenticate(ctx context.Context, clientID, clientSecret string) (*Account, error) + TouchToken(ctx context.Context, agentID int64) error + HasAppAccess(ctx context.Context, agentID int64, slug string) (bool, error) + // GrantAppSlug 将 slug 写入智能体可访问模块(幂等)。新建发布时自动授权用。 + GrantAppSlug(ctx context.Context, agentID int64, slug string) error +} + +type memAcc struct { + Account + SecretHash string +} + +type MemoryStore struct { + mu sync.Mutex + seq int64 + byID map[int64]*memAcc + byCID map[string]*memAcc +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{byID: map[int64]*memAcc{}, byCID: map[string]*memAcc{}} +} + +func (s *MemoryStore) List(_ context.Context, tenantID int64) ([]Account, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := []Account{} + for _, a := range s.byID { + if a.TenantID == tenantID { + out = append(out, cloneAcc(&a.Account)) + } + } + return out, nil +} + +func (s *MemoryStore) Get(_ context.Context, tenantID, agentID int64) (*Account, error) { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok || a.TenantID != tenantID { + return nil, fmt.Errorf("agent not found") + } + cp := cloneAcc(&a.Account) + return &cp, nil +} + +func (s *MemoryStore) Create(_ context.Context, tenantID, createdBy int64, in CreateInput) (*Account, string, error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.createLocked(tenantID, createdBy, in) +} + +func (s *MemoryStore) Register(_ context.Context, tenantID int64, name, hostKey string) (*Account, string, bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + hostKey = strings.TrimSpace(hostKey) + if hostKey != "" { + for _, a := range s.byID { + if a.TenantID == tenantID && a.HostKey == hostKey { + if a.Status == StatusPending { + secret := randomHex(24) + hash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) + if err != nil { + return nil, "", false, err + } + a.SecretHash = string(hash) + if n := strings.TrimSpace(name); n != "" { + a.Name = n + } + cp := cloneAcc(&a.Account) + return &cp, secret, true, nil + } + return nil, "", false, fmt.Errorf("host already registered as %s (status=%s)", a.ClientID, a.Status) + } + } + } + acc, secret, err := s.createLocked(tenantID, 0, CreateInput{ + Name: name, + Status: StatusPending, + HostKey: hostKey, + }) + return acc, secret, false, err +} + +func (s *MemoryStore) createLocked(tenantID, createdBy int64, in CreateInput) (*Account, string, error) { + cid := "agt_" + randomHex(8) + secret := randomHex(24) + hash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) + if err != nil { + return nil, "", err + } + st := strings.TrimSpace(in.Status) + if st == "" { + st = StatusActive + } + if !validStatus(st) { + return nil, "", fmt.Errorf("invalid status") + } + s.seq++ + a := &memAcc{ + Account: Account{ + AgentID: s.seq, + TenantID: tenantID, + Name: strings.TrimSpace(in.Name), + ClientID: cid, + HostKey: strings.TrimSpace(in.HostKey), + RoleID: in.RoleID, + Status: st, + Perms: authx.NormalizePerms(uniq(in.Perms)), + AppSlugs: uniq(in.AppSlugs), + CreatedBy: createdBy, + CreatedAt: time.Now().UTC(), + }, + SecretHash: string(hash), + } + if a.Name == "" { + a.Name = cid + } + s.byID[a.AgentID] = a + s.byCID[a.ClientID] = a + cp := cloneAcc(&a.Account) + return &cp, secret, nil +} + +func (s *MemoryStore) Update(_ context.Context, tenantID, agentID int64, in UpdateInput) (*Account, error) { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok || a.TenantID != tenantID { + return nil, fmt.Errorf("agent not found") + } + if in.Name != nil { + a.Name = strings.TrimSpace(*in.Name) + } + if in.Status != nil { + st := strings.TrimSpace(*in.Status) + if !validStatus(st) { + return nil, fmt.Errorf("invalid status") + } + a.Status = st + } + if in.Perms != nil { + a.Perms = uniq(*in.Perms) + } + if in.AppSlugs != nil { + a.AppSlugs = uniq(*in.AppSlugs) + } + if in.RoleID != nil { + a.RoleID = *in.RoleID + } + cp := cloneAcc(&a.Account) + return &cp, nil +} + +func (s *MemoryStore) RotateSecret(_ context.Context, tenantID, agentID int64) (string, error) { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok || a.TenantID != tenantID { + return "", fmt.Errorf("agent not found") + } + secret := randomHex(24) + hash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) + if err != nil { + return "", err + } + a.SecretHash = string(hash) + return secret, nil +} + +func (s *MemoryStore) Delete(_ context.Context, tenantID, agentID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok || a.TenantID != tenantID { + return fmt.Errorf("agent not found") + } + delete(s.byCID, a.ClientID) + delete(s.byID, agentID) + return nil +} + +func (s *MemoryStore) Authenticate(_ context.Context, clientID, clientSecret string) (*Account, error) { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byCID[clientID] + if !ok { + return nil, fmt.Errorf("invalid client credentials") + } + if bcrypt.CompareHashAndPassword([]byte(a.SecretHash), []byte(clientSecret)) != nil { + return nil, fmt.Errorf("invalid client credentials") + } + switch a.Status { + case StatusPending: + return nil, fmt.Errorf("agent pending approval") + case StatusDisabled: + return nil, fmt.Errorf("agent disabled") + case StatusActive: + // ok + default: + return nil, fmt.Errorf("agent not active") + } + cp := cloneAcc(&a.Account) + return &cp, nil +} + +func (s *MemoryStore) TouchToken(_ context.Context, agentID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + if a, ok := s.byID[agentID]; ok { + now := time.Now().UTC() + a.LastTokenAt = &now + } + return nil +} + +func (s *MemoryStore) HasAppAccess(_ context.Context, agentID int64, slug string) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok { + return false, fmt.Errorf("agent not found") + } + // 未配置可访问模块 = 不限制,智能体可自由发布/访问自建模块 + if len(a.AppSlugs) == 0 { + return true, nil + } + for _, s0 := range a.AppSlugs { + if s0 == "*" || s0 == slug { + return true, nil + } + } + return false, nil +} + +func (s *MemoryStore) GrantAppSlug(_ context.Context, agentID int64, slug string) error { + slug = strings.TrimSpace(slug) + if slug == "" { + return fmt.Errorf("empty slug") + } + s.mu.Lock() + defer s.mu.Unlock() + a, ok := s.byID[agentID] + if !ok { + return fmt.Errorf("agent not found") + } + for _, s0 := range a.AppSlugs { + if s0 == slug { + return nil + } + } + a.AppSlugs = append(a.AppSlugs, slug) + return nil +} + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db} } + +func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]Account, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at +FROM platform_meta.agent_accounts WHERE tenant_id=$1 +ORDER BY CASE status WHEN 'pending' THEN 0 WHEN 'active' THEN 1 ELSE 2 END, agent_id`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []Account + for rows.Next() { + var a Account + var last sql.NullTime + if err := rows.Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last); err != nil { + return nil, err + } + if last.Valid { + t := last.Time.UTC() + a.LastTokenAt = &t + } + perms, slugs, err := s.loadKids(ctx, a.AgentID) + if err != nil { + return nil, err + } + a.Perms, a.AppSlugs = perms, slugs + out = append(out, a) + } + return out, rows.Err() +} + +func (s *PostgresStore) Get(ctx context.Context, tenantID, agentID int64) (*Account, error) { + var a Account + var last sql.NullTime + err := s.DB.QueryRowContext(ctx, ` +SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at +FROM platform_meta.agent_accounts WHERE agent_id=$1 AND tenant_id=$2`, agentID, tenantID, + ).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("agent not found") + } + if err != nil { + return nil, err + } + if last.Valid { + t := last.Time.UTC() + a.LastTokenAt = &t + } + perms, slugs, err := s.loadKids(ctx, a.AgentID) + if err != nil { + return nil, err + } + a.Perms, a.AppSlugs = perms, slugs + return &a, nil +} + +func (s *PostgresStore) Create(ctx context.Context, tenantID, createdBy int64, in CreateInput) (*Account, string, error) { + return s.insert(ctx, tenantID, createdBy, in) +} + +func (s *PostgresStore) Register(ctx context.Context, tenantID int64, name, hostKey string) (*Account, string, bool, error) { + hostKey = strings.TrimSpace(hostKey) + if hostKey != "" { + var a Account + var last sql.NullTime + err := s.DB.QueryRowContext(ctx, ` +SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at +FROM platform_meta.agent_accounts WHERE tenant_id=$1 AND host_key=$2`, tenantID, hostKey, + ).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last) + if err == nil { + if a.Status == StatusPending { + secret, err := s.RotateSecret(ctx, tenantID, a.AgentID) + if err != nil { + return nil, "", false, err + } + if n := strings.TrimSpace(name); n != "" { + _, _ = s.Update(ctx, tenantID, a.AgentID, UpdateInput{Name: &n}) + } + acc, err := s.Get(ctx, tenantID, a.AgentID) + return acc, secret, true, err + } + return nil, "", false, fmt.Errorf("host already registered as %s (status=%s)", a.ClientID, a.Status) + } + if !errors.Is(err, sql.ErrNoRows) { + return nil, "", false, err + } + } + acc, secret, err := s.insert(ctx, tenantID, 0, CreateInput{ + Name: name, + Status: StatusPending, + HostKey: hostKey, + }) + return acc, secret, false, err +} + +func (s *PostgresStore) insert(ctx context.Context, tenantID, createdBy int64, in CreateInput) (*Account, string, error) { + cid := "agt_" + randomHex(8) + secret := randomHex(24) + hash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) + if err != nil { + return nil, "", err + } + name := strings.TrimSpace(in.Name) + if name == "" { + name = cid + } + st := strings.TrimSpace(in.Status) + if st == "" { + st = StatusActive + } + if !validStatus(st) { + return nil, "", fmt.Errorf("invalid status") + } + var a Account + err = s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.agent_accounts(tenant_id, name, client_id, client_secret_hash, status, created_by, host_key, role_id) +VALUES($1,$2,$3,$4,$5,$6,$7,NULLIF($8,0)) +RETURNING agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at`, + tenantID, name, cid, string(hash), st, createdBy, strings.TrimSpace(in.HostKey), in.RoleID, + ).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt) + if err != nil { + return nil, "", err + } + perms, slugs := uniq(in.Perms), uniq(in.AppSlugs) + if err := s.replaceKids(ctx, a.AgentID, perms, slugs); err != nil { + return nil, "", err + } + a.Perms, a.AppSlugs = perms, slugs + return &a, secret, nil +} + +func (s *PostgresStore) Update(ctx context.Context, tenantID, agentID int64, in UpdateInput) (*Account, error) { + a, err := s.Get(ctx, tenantID, agentID) + if err != nil { + return nil, err + } + name, status := a.Name, a.Status + roleID := a.RoleID + if in.Name != nil { + name = strings.TrimSpace(*in.Name) + } + if in.Status != nil { + status = strings.TrimSpace(*in.Status) + if !validStatus(status) { + return nil, fmt.Errorf("invalid status") + } + } + if in.RoleID != nil { + roleID = *in.RoleID + } + if _, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.agent_accounts SET name=$1, status=$2, role_id=NULLIF($3,0) WHERE agent_id=$4 AND tenant_id=$5`, + name, status, roleID, agentID, tenantID); err != nil { + return nil, err + } + perms, slugs := a.Perms, a.AppSlugs + if in.Perms != nil { + perms = uniq(*in.Perms) + } + if in.AppSlugs != nil { + slugs = uniq(*in.AppSlugs) + } + if err := s.replaceKids(ctx, agentID, perms, slugs); err != nil { + return nil, err + } + return s.Get(ctx, tenantID, agentID) +} + +func (s *PostgresStore) RotateSecret(ctx context.Context, tenantID, agentID int64) (string, error) { + if _, err := s.Get(ctx, tenantID, agentID); err != nil { + return "", err + } + secret := randomHex(24) + hash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) + if err != nil { + return "", err + } + _, err = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.agent_accounts SET client_secret_hash=$1 WHERE agent_id=$2 AND tenant_id=$3`, + string(hash), agentID, tenantID) + return secret, err +} + +func (s *PostgresStore) Delete(ctx context.Context, tenantID, agentID int64) error { + res, err := s.DB.ExecContext(ctx, ` +DELETE FROM platform_meta.agent_accounts WHERE agent_id=$1 AND tenant_id=$2`, agentID, tenantID) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("agent not found") + } + return nil +} + +func (s *PostgresStore) Authenticate(ctx context.Context, clientID, clientSecret string) (*Account, error) { + var a Account + var hash string + var last sql.NullTime + err := s.DB.QueryRowContext(ctx, ` +SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), client_secret_hash, status, created_by, created_at, last_token_at +FROM platform_meta.agent_accounts WHERE client_id=$1`, clientID, + ).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &hash, &a.Status, &a.CreatedBy, &a.CreatedAt, &last) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("invalid client credentials") + } + if err != nil { + return nil, err + } + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(clientSecret)) != nil { + return nil, fmt.Errorf("invalid client credentials") + } + switch a.Status { + case StatusPending: + return nil, fmt.Errorf("agent pending approval") + case StatusDisabled: + return nil, fmt.Errorf("agent disabled") + case StatusActive: + default: + return nil, fmt.Errorf("agent not active") + } + if last.Valid { + t := last.Time.UTC() + a.LastTokenAt = &t + } + perms, slugs, err := s.loadKids(ctx, a.AgentID) + if err != nil { + return nil, err + } + a.Perms, a.AppSlugs = perms, slugs + return &a, nil +} + +func (s *PostgresStore) TouchToken(ctx context.Context, agentID int64) error { + _, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.agent_accounts SET last_token_at=now() WHERE agent_id=$1`, agentID) + return err +} + +func (s *PostgresStore) HasAppAccess(ctx context.Context, agentID int64, slug string) (bool, error) { + var total int + if err := s.DB.QueryRowContext(ctx, ` +SELECT COUNT(1) FROM platform_meta.agent_app_grants WHERE agent_id=$1`, agentID).Scan(&total); err != nil { + return false, err + } + // 未配置可访问模块 = 不限制,可自由发布自己的模块 + if total == 0 { + return true, nil + } + var n int + err := s.DB.QueryRowContext(ctx, ` +SELECT COUNT(1) FROM platform_meta.agent_app_grants +WHERE agent_id=$1 AND (slug=$2 OR slug='*')`, agentID, slug).Scan(&n) + return n > 0, err +} + +func (s *PostgresStore) GrantAppSlug(ctx context.Context, agentID int64, slug string) error { + slug = strings.TrimSpace(slug) + if slug == "" { + return fmt.Errorf("empty slug") + } + ok, err := s.HasAppAccess(ctx, agentID, slug) + if err != nil { + return err + } + if ok { + return nil + } + _, err = s.DB.ExecContext(ctx, ` +INSERT INTO platform_meta.agent_app_grants(agent_id, slug) VALUES($1,$2)`, agentID, slug) + return err +} + +func (s *PostgresStore) loadKids(ctx context.Context, agentID int64) ([]string, []string, error) { + prows, err := s.DB.QueryContext(ctx, `SELECT perm FROM platform_meta.agent_permissions WHERE agent_id=$1`, agentID) + if err != nil { + return nil, nil, err + } + defer prows.Close() + var perms []string + for prows.Next() { + var p string + if err := prows.Scan(&p); err != nil { + return nil, nil, err + } + perms = append(perms, p) + } + srows, err := s.DB.QueryContext(ctx, `SELECT slug FROM platform_meta.agent_app_grants WHERE agent_id=$1`, agentID) + if err != nil { + return nil, nil, err + } + defer srows.Close() + var slugs []string + for srows.Next() { + var slug string + if err := srows.Scan(&slug); err != nil { + return nil, nil, err + } + slugs = append(slugs, slug) + } + return authx.NormalizePerms(perms), slugs, nil +} + +func (s *PostgresStore) replaceKids(ctx context.Context, agentID int64, perms, slugs []string) error { + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.agent_permissions WHERE agent_id=$1`, agentID); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.agent_app_grants WHERE agent_id=$1`, agentID); err != nil { + return err + } + for _, p := range authx.NormalizePerms(perms) { + if _, err := tx.ExecContext(ctx, `INSERT INTO platform_meta.agent_permissions(agent_id, perm) VALUES($1,$2)`, agentID, p); err != nil { + return err + } + } + for _, slug := range slugs { + if _, err := tx.ExecContext(ctx, `INSERT INTO platform_meta.agent_app_grants(agent_id, slug) VALUES($1,$2)`, agentID, slug); err != nil { + return err + } + } + return tx.Commit() +} + +func validStatus(st string) bool { + return st == StatusActive || st == StatusPending || st == StatusDisabled +} + +func cloneAcc(a *Account) Account { + cp := *a + cp.Perms = append([]string{}, a.Perms...) + cp.AppSlugs = append([]string{}, a.AppSlugs...) + return cp +} + +func uniq(in []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(in)) + for _, s := range in { + s = strings.TrimSpace(s) + if s == "" { + continue + } + if _, ok := seen[s]; ok { + continue + } + seen[s] = struct{}{} + out = append(out, s) + } + return out +} + +func randomHex(n int) string { + b := make([]byte, n) + _, _ = rand.Read(b) + return hex.EncodeToString(b) +} diff --git a/platform/internal/apidef/catalog.go b/platform/internal/apidef/catalog.go new file mode 100644 index 0000000..031d0bd --- /dev/null +++ b/platform/internal/apidef/catalog.go @@ -0,0 +1,106 @@ +package apidef + +// 统一 API 目录:代码注册与文档同源,禁止另起一套重复路径。 +// 约定: +// GET 读 +// POST 创建 / 触发无幂等动作(login、import、publish、generate) +// PUT 更新(整资源或按约定部分字段) +// DELETE 删除 +// 动态业务行数据仅通过 /api/v1/apps/{slug}/{resource}[/{id}] 传输,不按行业写死字段。 + +type Entry struct { + Method string `json:"method"` + Path string `json:"path"` + OperationID string `json:"operation_id"` + Summary string `json:"summary"` + Public bool `json:"public"` // 网关可不带 JWT + Group string `json:"group"` +} + +// Catalog 为平台对外唯一路由清单(动态 CRUD 形状固定,不因业务变化而增删行业接口)。 +var Catalog = []Entry{ + {Method: "POST", Path: "/api/v1/auth/register", OperationID: "authRegister", Summary: "注册(默认 pending,无租户)", Public: true, Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/login", OperationID: "authLogin", Summary: "登录:用户名/手机+密码,或手机+短信验证码", Public: true, Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/sms/send", OperationID: "sendLoginSMS", Summary: "发送登录短信验证码(手机须已绑定)", Public: true, Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/password", OperationID: "changePassword", Summary: "登录用户修改自己的密码", Group: "auth"}, + {Method: "GET", Path: "/api/v1/auth/me", OperationID: "authMe", Summary: "当前登录用户资料", Group: "auth"}, + {Method: "PUT", Path: "/api/v1/auth/phone", OperationID: "bindPhone", Summary: "绑定或更换手机号", Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/token", OperationID: "authToken", Summary: "服务/智能体签发 JWT(含 client_credentials)", Public: true, Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/agent/register", OperationID: "agentSelfRegister", Summary: "宿主首次连接自注册(pending)", Public: true, Group: "auth"}, + {Method: "POST", Path: "/api/v1/auth/invites/accept", OperationID: "acceptInvite", Summary: "接受邀请加入租户", Group: "auth"}, + {Method: "POST", Path: "/api/v1/tenants", OperationID: "createTenant", Summary: "pending 用户创建自己的公司", Group: "auth"}, + + {Method: "GET", Path: "/api/v1/meta/apis", OperationID: "listApis", Summary: "API 目录(防重复约定)", Public: true, Group: "meta"}, + {Method: "GET", Path: "/api/v1/meta/openapi.yaml", OperationID: "getOpenAPI", Summary: "OpenAPI 契约原文", Public: true, Group: "meta"}, + + {Method: "GET", Path: "/api/v1/admin/agents", OperationID: "listAgents", Summary: "列出智能体账号", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/agents", OperationID: "createAgent", Summary: "创建智能体账号", Group: "admin"}, + {Method: "GET", Path: "/api/v1/admin/agents/{id}", OperationID: "getAgent", Summary: "智能体详情", Group: "admin"}, + {Method: "PUT", Path: "/api/v1/admin/agents/{id}", OperationID: "updateAgent", Summary: "更新智能体", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/agents/{id}/rotate-secret", OperationID: "rotateAgentSecret", Summary: "轮换 client_secret", Group: "admin"}, + {Method: "DELETE", Path: "/api/v1/admin/agents/{id}", OperationID: "deleteAgent", Summary: "删除智能体", Group: "admin"}, + + {Method: "GET", Path: "/api/v1/admin/roles", OperationID: "listRoles", Summary: "列出角色", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/roles", OperationID: "createRole", Summary: "创建角色", Group: "admin"}, + {Method: "GET", Path: "/api/v1/admin/roles/{id}", OperationID: "getRole", Summary: "角色详情", Group: "admin"}, + {Method: "PUT", Path: "/api/v1/admin/roles/{id}", OperationID: "updateRole", Summary: "更新角色", Group: "admin"}, + {Method: "DELETE", Path: "/api/v1/admin/roles/{id}", OperationID: "deleteRole", Summary: "删除角色", Group: "admin"}, + + {Method: "GET", Path: "/api/v1/admin/invites", OperationID: "listInvites", Summary: "列出租户邀请码", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/invites", OperationID: "createInvite", Summary: "创建租户邀请码", Group: "admin"}, + {Method: "DELETE", Path: "/api/v1/admin/invites/{id}", OperationID: "revokeInvite", Summary: "撤销邀请码", Group: "admin"}, + + {Method: "GET", Path: "/api/v1/admin/org-units", OperationID: "listOrgUnits", Summary: "组织树列表", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/org-units", OperationID: "createOrgUnit", Summary: "创建组织节点", Group: "admin"}, + {Method: "PUT", Path: "/api/v1/admin/org-units/{id}", OperationID: "updateOrgUnit", Summary: "更新组织节点", Group: "admin"}, + {Method: "DELETE", Path: "/api/v1/admin/org-units/{id}", OperationID: "deleteOrgUnit", Summary: "删除组织节点", Group: "admin"}, + + {Method: "GET", Path: "/api/v1/admin/sync/channels", OperationID: "listSyncChannels", Summary: "列出跨库同步通道", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/sync/channels", OperationID: "createSyncChannel", Summary: "创建同步通道(可配线上 DSN)", Group: "admin"}, + {Method: "PUT", Path: "/api/v1/admin/sync/channels/{id}", OperationID: "updateSyncChannel", Summary: "更新同步通道", Group: "admin"}, + {Method: "DELETE", Path: "/api/v1/admin/sync/channels/{id}", OperationID: "deleteSyncChannel", Summary: "删除同步通道", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/sync/test", OperationID: "testSyncEndpoints", Summary: "测试本地/线上库连接", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/sync/channels/{id}/start", OperationID: "startSyncChannel", Summary: "启动近实时同步", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/sync/channels/{id}/stop", OperationID: "stopSyncChannel", Summary: "停止同步", Group: "admin"}, + {Method: "GET", Path: "/api/v1/admin/sync/conflicts", OperationID: "listSyncConflicts", Summary: "冲突队列", Group: "admin"}, + {Method: "POST", Path: "/api/v1/admin/sync/conflicts/{id}/resolve", OperationID: "resolveSyncConflict", Summary: "解决冲突", Group: "admin"}, + + {Method: "GET", Path: "/api/v1/apps", OperationID: "listApps", Summary: "列出模块:管理账号看本租户全部(含在建);智能体仅已授权", Group: "app"}, + {Method: "PUT", Path: "/api/v1/apps/{slug}/draft", OperationID: "saveDraft", Summary: "登记在建模块蓝图(不发布)", Group: "app"}, + {Method: "POST", Path: "/api/v1/apps/{slug}/publish", OperationID: "publishApp", Summary: "向建站平台发布模块;回执含按用户ID加密的访问路径", Group: "app"}, + {Method: "GET", Path: "/api/v1/apps/{slug}/blueprint", OperationID: "getBlueprint", Summary: "读取已发布模块蓝图", Group: "app"}, + {Method: "GET", Path: "/api/v1/apps/{slug}/agent-capsule", OperationID: "getAgentCapsule", Summary: "智能体加密契约", Group: "app"}, + {Method: "GET", Path: "/api/v1/public/m/{token}/blueprint", OperationID: "publicModulePathBlueprint", Summary: "经加密路径公开读取模块蓝图", Public: true, Group: "app"}, + + {Method: "GET", Path: "/api/v1/apps/{slug}/{resource}", OperationID: "listRows", Summary: "列表", Group: "crud"}, + {Method: "POST", Path: "/api/v1/apps/{slug}/{resource}", OperationID: "createRow", Summary: "创建", Group: "crud"}, + {Method: "GET", Path: "/api/v1/apps/{slug}/{resource}/{id}", OperationID: "getRow", Summary: "详情", Group: "crud"}, + {Method: "PUT", Path: "/api/v1/apps/{slug}/{resource}/{id}", OperationID: "updateRow", Summary: "更新", Group: "crud"}, + {Method: "DELETE", Path: "/api/v1/apps/{slug}/{resource}/{id}", OperationID: "deleteRow", Summary: "删除", Group: "crud"}, + + {Method: "POST", Path: "/api/v1/apps/{slug}/{resource}/import", OperationID: "importRows", Summary: "导入 Excel/CSV", Group: "crud"}, + {Method: "GET", Path: "/api/v1/apps/{slug}/{resource}/export", OperationID: "exportRows", Summary: "导出 Excel/CSV", Group: "crud"}, + {Method: "GET", Path: "/api/v1/apps/{slug}/{resource}/aggregate", OperationID: "aggregateRows", Summary: "聚合统计", Group: "crud"}, + + {Method: "GET", Path: "/api/v1/audit/logs", OperationID: "listAuditLogs", Summary: "审计日志", Group: "audit"}, + {Method: "POST", Path: "/api/v1/storage", OperationID: "uploadObject", Summary: "上传对象", Group: "storage"}, + {Method: "GET", Path: "/api/v1/storage/{tenant}/{day}/{name}", OperationID: "downloadObject", Summary: "下载对象", Group: "storage"}, +} + +// AICatalog 由 AI 服务提供,经网关 /ai 前缀访问;不在 platform 重复实现。 +var AICatalog = []Entry{ + {Method: "GET", Path: "/api/v1/llm/providers", OperationID: "listLlmProviders", Summary: "LLM 厂商列表", Public: true, Group: "ai"}, + {Method: "POST", Path: "/api/v1/apps/generate", OperationID: "generateBlueprint", Summary: "生成蓝图草稿", Public: true, Group: "ai"}, +} + +// ReservedResources 不可作为动态 resource 名,避免与固定路由冲突。 +var ReservedResources = map[string]struct{}{ + "blueprint": {}, "publish": {}, "agent-capsule": {}, + "import": {}, "export": {}, "aggregate": {}, + "generate": {}, "meta": {}, +} + +func IsReservedResource(name string) bool { + _, ok := ReservedResources[name] + return ok +} diff --git a/platform/internal/audit/store.go b/platform/internal/audit/store.go new file mode 100644 index 0000000..d2ed0e7 --- /dev/null +++ b/platform/internal/audit/store.go @@ -0,0 +1,122 @@ +package audit + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "sync" + "time" +) + +type Entry struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Action string `json:"action"` + Detail string `json:"detail"` + CreatedAt time.Time `json:"created_at"` +} + +type Store interface { + Log(ctx context.Context, tenantID, userID int64, action, detail string) error + List(ctx context.Context, tenantID int64, limit, offset int) ([]Entry, int, error) +} + +type MemoryStore struct { + mu sync.Mutex + seq int64 + rows []Entry +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{} +} + +func (s *MemoryStore) Log(_ context.Context, tenantID, userID int64, action, detail string) error { + s.mu.Lock() + defer s.mu.Unlock() + s.seq++ + s.rows = append(s.rows, Entry{ + ID: s.seq, TenantID: tenantID, UserID: userID, + Action: action, Detail: detail, CreatedAt: time.Now().UTC(), + }) + return nil +} + +func (s *MemoryStore) List(_ context.Context, tenantID int64, limit, offset int) ([]Entry, int, error) { + s.mu.Lock() + defer s.mu.Unlock() + if limit <= 0 { + limit = 50 + } + filtered := make([]Entry, 0) + for i := len(s.rows) - 1; i >= 0; i-- { + if s.rows[i].TenantID == tenantID { + filtered = append(filtered, s.rows[i]) + } + } + total := len(filtered) + if offset >= total { + return []Entry{}, total, nil + } + end := offset + limit + if end > total { + end = total + } + return filtered[offset:end], total, nil +} + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { + return &PostgresStore{DB: db} +} + +func (s *PostgresStore) Log(ctx context.Context, tenantID, userID int64, action, detail string) error { + _, err := s.DB.ExecContext(ctx, ` +INSERT INTO platform_meta.audit_logs(tenant_id, user_id, action, detail) +VALUES($1,$2,$3,$4)`, tenantID, userID, action, detail) + return err +} + +func (s *PostgresStore) List(ctx context.Context, tenantID int64, limit, offset int) ([]Entry, int, error) { + if limit <= 0 { + limit = 50 + } + var total int + if err := s.DB.QueryRowContext(ctx, + `SELECT COUNT(1) FROM platform_meta.audit_logs WHERE tenant_id=$1`, tenantID, + ).Scan(&total); err != nil { + return nil, 0, err + } + rows, err := s.DB.QueryContext(ctx, ` +SELECT id, tenant_id, user_id, action, detail, created_at +FROM platform_meta.audit_logs +WHERE tenant_id=$1 +ORDER BY id DESC +LIMIT $2 OFFSET $3`, tenantID, limit, offset) + if err != nil { + return nil, 0, err + } + defer rows.Close() + out := make([]Entry, 0) + for rows.Next() { + var e Entry + if err := rows.Scan(&e.ID, &e.TenantID, &e.UserID, &e.Action, &e.Detail, &e.CreatedAt); err != nil { + return nil, 0, err + } + out = append(out, e) + } + return out, total, rows.Err() +} + +func DetailJSON(v any) string { + b, err := json.Marshal(v) + if err != nil { + return fmt.Sprint(v) + } + return string(b) +} diff --git a/platform/internal/authx/authx.go b/platform/internal/authx/authx.go new file mode 100644 index 0000000..bd42ccf --- /dev/null +++ b/platform/internal/authx/authx.go @@ -0,0 +1,396 @@ +package authx + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/zeromicro/go-zero/rest/httpx" +) + +type ctxKey string + +const ( + CtxTenantID ctxKey = "tenant_id" + CtxUserID ctxKey = "user_id" + CtxRole ctxKey = "role" + CtxAgentID ctxKey = "agent_id" + CtxPerms ctxKey = "perms" + CtxOrgUnitID ctxKey = "org_unit_id" +) + +// 平台级角色 → 权限映射(角色、权限一律中文命名) +var RolePermissions = map[string][]string{ + Role超级管理员: { + Perm管理租户, // 只管公司一级:列表/新建等;不介入公司内模块、成员、同步 + }, + Role管理员: { + Perm读取模块, Perm写入模块, Perm发布模块, + Perm新增数据, Perm查询数据, Perm更新数据, Perm删除数据, Perm导出数据, Perm导入数据, + Perm查看审计, Perm上传文件, Perm下载文件, + Perm管理智能体, Perm邀请成员, Perm管理组织, + Perm数据同步, + }, + Role编辑: { + Perm读取模块, Perm写入模块, + Perm新增数据, Perm查询数据, Perm更新数据, Perm导出数据, Perm导入数据, + Perm上传文件, Perm下载文件, + }, + Role只读: { + Perm读取模块, Perm查询数据, Perm导出数据, Perm下载文件, Perm查看审计, + }, + Role待加入: {}, +} + +type Claims struct { + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Role string `json:"role,omitempty"` + OrgUnitID int64 `json:"org_unit_id,omitempty"` + AgentID int64 `json:"agent_id,omitempty"` + Perms []string `json:"perms,omitempty"` + jwt.RegisteredClaims +} + +type JWTConfig struct { + AccessSecret string + AccessExpire int64 +} + +func (c JWTConfig) Expire() time.Duration { + if c.AccessExpire <= 0 { + return 24 * time.Hour + } + return time.Duration(c.AccessExpire) * time.Second +} + +func IssueToken(cfg JWTConfig, tenantID, userID int64, role string, orgUnitID int64) (string, int64, error) { + if cfg.AccessSecret == "" { + return "", 0, errors.New("auth access secret empty") + } + if userID <= 0 { + return "", 0, errors.New("user_id required") + } + if role == "" { + role = Role管理员 + } + role = NormalizeRole(role) + if _, ok := RolePermissions[role]; !ok { + return "", 0, fmt.Errorf("unknown role: %s", role) + } + // 待加入 / 超级管理员可无租户;其他角色必须带 tenant_id + if tenantID <= 0 && role != Role待加入 && role != Role超级管理员 { + return "", 0, errors.New("tenant_id and user_id required") + } + if tenantID < 0 { + tenantID = 0 + } + return signClaims(cfg, Claims{ + TenantID: tenantID, + UserID: userID, + Role: role, + OrgUnitID: orgUnitID, + }) +} + +// IssueAgentToken 签发智能体 JWT(role=agent,权限写在 perms 声明中)。 +func IssueAgentToken(cfg JWTConfig, tenantID, agentID int64, perms []string) (string, int64, error) { + if cfg.AccessSecret == "" { + return "", 0, errors.New("auth access secret empty") + } + if tenantID <= 0 || agentID <= 0 { + return "", 0, errors.New("tenant_id and agent_id required") + } + return signClaims(cfg, Claims{ + TenantID: tenantID, + UserID: agentID, + Role: RoleAgent, + AgentID: agentID, + Perms: NormalizePerms(perms), + }) +} + +func signClaims(cfg JWTConfig, claims Claims) (string, int64, error) { + exp := time.Now().Add(cfg.Expire()) + claims.RegisteredClaims = jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exp), + IssuedAt: jwt.NewNumericDate(time.Now()), + Issuer: "aijianzhan-platform", + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString([]byte(cfg.AccessSecret)) + if err != nil { + return "", 0, err + } + return signed, exp.Unix(), nil +} + +func ParseToken(cfg JWTConfig, tokenStr string) (*Claims, error) { + if cfg.AccessSecret == "" { + return nil, errors.New("auth access secret empty") + } + parser := jwt.NewParser(jwt.WithValidMethods([]string{"HS256"})) + token, err := parser.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) { + return []byte(cfg.AccessSecret), nil + }) + if err != nil { + return nil, err + } + claims, ok := token.Claims.(*Claims) + if !ok || !token.Valid { + return nil, errors.New("invalid token") + } + if claims.UserID <= 0 { + return nil, errors.New("token missing user") + } + if claims.Role == "" { + claims.Role = Role只读 + } + claims.Role = NormalizeRole(claims.Role) + if claims.TenantID <= 0 && claims.Role != Role待加入 && claims.Role != Role超级管理员 { + return nil, errors.New("token missing tenant/user") + } + if claims.TenantID < 0 { + claims.TenantID = 0 + } + if claims.Role == Role智能体 && claims.AgentID <= 0 { + claims.AgentID = claims.UserID + } + return claims, nil +} + +func Middleware(jwtCfg JWTConfig, devAuth bool) func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if strings.HasPrefix(strings.ToLower(auth), "bearer ") { + raw := strings.TrimSpace(auth[7:]) + claims, err := ParseToken(jwtCfg, raw) + if err != nil { + WriteError(w, http.StatusUnauthorized, "invalid token: "+err.Error()) + return + } + ctx := WithFullClaims(r.Context(), claims.TenantID, claims.UserID, claims.Role, claims.AgentID, claims.OrgUnitID, claims.Perms) + next(w, r.WithContext(ctx)) + return + } + + if devAuth { + tenantID, _ := strconv.ParseInt(r.Header.Get("X-Tenant-Id"), 10, 64) + userID, _ := strconv.ParseInt(r.Header.Get("X-User-Id"), 10, 64) + role := r.Header.Get("X-Role") + if tenantID <= 0 { + tenantID = 1 + } + if userID <= 0 { + userID = 1 + } + if role == "" { + role = Role管理员 + } + role = NormalizeRole(role) + ctx := WithClaims(r.Context(), tenantID, userID, role) + next(w, r.WithContext(ctx)) + return + } + + WriteError(w, http.StatusUnauthorized, "missing Authorization Bearer token") + } + } +} + +// RequirePermission 校验平台角色或智能体 JWT 内嵌权限。 +func RequirePermission(perm string) func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !HasPermissionCtx(r.Context(), perm) { + role := Role(r.Context()) + WriteError(w, http.StatusForbidden, "permission denied: "+perm+" (role="+role+")") + return + } + next(w, r) + } + } +} + +// RequireTenant 要求已加入租户;pending / 无 tenant 不可访问业务数据。 +// 超级管理员可在「进入某公司」后携带 tenant_id 访问公司内接口(可看可改,前端会多重确认)。 +func RequireTenant() func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if Role(r.Context()) == Role智能体 { + if TenantID(r.Context()) <= 0 { + WriteError(w, http.StatusForbidden, "agent missing tenant") + return + } + next(w, r) + return + } + if Role(r.Context()) == Role超级管理员 { + if TenantID(r.Context()) <= 0 { + WriteError(w, http.StatusForbidden, "请先在平台管理中进入某公司后再操作公司内部功能") + return + } + next(w, r) + return + } + if TenantID(r.Context()) <= 0 || Role(r.Context()) == Role待加入 { + WriteError(w, http.StatusForbidden, "join a tenant first (pending membership)") + return + } + next(w, r) + } + } +} + +// RequirePlatformAdmin 仅平台超级管理员。 +func RequirePlatformAdmin() func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !IsPlatformAdmin(Role(r.Context())) { + WriteError(w, http.StatusForbidden, "需要超级管理员") + return + } + next(w, r) + } + } +} + +// RequireTenantBound 保留兼容:业务接口要求已绑定租户。 +func RequireTenantBound() func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if IsPlatformAdmin(Role(r.Context())) { + if TenantID(r.Context()) <= 0 { + WriteError(w, http.StatusForbidden, "请先进入某公司") + return + } + next(w, r) + return + } + if TenantID(r.Context()) <= 0 { + WriteError(w, http.StatusForbidden, "请先加入公司") + return + } + next(w, r) + } + } +} + +func HasPermission(role, perm string) bool { + perms, ok := RolePermissions[NormalizeRole(role)] + if !ok { + return false + } + want := NormalizePerm(perm) + for _, p := range perms { + if NormalizePerm(p) == want { + return true + } + } + return false +} + +func HasPermissionCtx(ctx context.Context, perm string) bool { + role := NormalizeRole(Role(ctx)) + want := NormalizePerm(perm) + if role == Role超级管理员 { + // 进入公司后:超管可使用全部公司权限(平台专属「管理租户」仍保留) + if want == Perm管理租户 { + return true + } + for _, p := range CompanyPermCatalog() { + if NormalizePerm(p) == want { + return TenantID(ctx) > 0 + } + } + return HasPermission(role, want) + } + if role == Role智能体 { + ok := false + for _, p := range Perms(ctx) { + if NormalizePerm(p) == want { + ok = true + break + } + } + if !ok { + return false + } + } else if !HasPermission(role, want) { + return false + } + tid := TenantID(ctx) + if tid > 0 && EntitlementChecker != nil { + if !EntitlementChecker(ctx, tid, want) { + return false + } + } + return true +} + +func withClaims(ctx context.Context, tenantID, userID int64, role string) context.Context { + return WithClaims(ctx, tenantID, userID, role) +} + +// WithClaims 写入租户/用户/角色(公开展示等场景用)。 +func WithClaims(ctx context.Context, tenantID, userID int64, role string) context.Context { + return WithFullClaims(ctx, tenantID, userID, role, 0, 0, nil) +} + +func WithFullClaims(ctx context.Context, tenantID, userID int64, role string, agentID, orgUnitID int64, perms []string) context.Context { + ctx = context.WithValue(ctx, CtxTenantID, tenantID) + ctx = context.WithValue(ctx, CtxUserID, userID) + ctx = context.WithValue(ctx, CtxRole, role) + ctx = context.WithValue(ctx, CtxAgentID, agentID) + ctx = context.WithValue(ctx, CtxOrgUnitID, orgUnitID) + if perms != nil { + ctx = context.WithValue(ctx, CtxPerms, append([]string{}, perms...)) + } + return ctx +} + +func TenantID(ctx context.Context) int64 { + v, _ := ctx.Value(CtxTenantID).(int64) + return v +} + +func UserID(ctx context.Context) int64 { + v, _ := ctx.Value(CtxUserID).(int64) + return v +} + +func AgentID(ctx context.Context) int64 { + v, _ := ctx.Value(CtxAgentID).(int64) + return v +} + +func OrgUnitID(ctx context.Context) int64 { + v, _ := ctx.Value(CtxOrgUnitID).(int64) + return v +} + +func Role(ctx context.Context) string { + v, _ := ctx.Value(CtxRole).(string) + if v == "" { + return Role只读 + } + return NormalizeRole(v) +} + +func Perms(ctx context.Context) []string { + v, _ := ctx.Value(CtxPerms).([]string) + return v +} + +func WriteError(w http.ResponseWriter, code int, msg string) { + httpx.WriteJson(w, code, map[string]any{ + "code": code, + "message": msg, + }) +} diff --git a/platform/internal/authx/authx_test.go b/platform/internal/authx/authx_test.go new file mode 100644 index 0000000..3d87f94 --- /dev/null +++ b/platform/internal/authx/authx_test.go @@ -0,0 +1,78 @@ +package authx_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "aijianzhan/platform/internal/authx" +) + +func TestIssueAndParseToken(t *testing.T) { + cfg := authx.JWTConfig{AccessSecret: "test-secret", AccessExpire: 3600} + tok, exp, err := authx.IssueToken(cfg, 9, 42, "owner", 0) + if err != nil { + t.Fatal(err) + } + if tok == "" || exp <= 0 { + t.Fatal("empty token") + } + claims, err := authx.ParseToken(cfg, tok) + if err != nil { + t.Fatal(err) + } + if claims.TenantID != 9 || claims.UserID != 42 { + t.Fatalf("claims mismatch: %+v", claims) + } +} + +func TestMiddlewareRequiresJWT(t *testing.T) { + cfg := authx.JWTConfig{AccessSecret: "test-secret", AccessExpire: 3600} + mw := authx.Middleware(cfg, false) + called := false + h := mw(func(w http.ResponseWriter, r *http.Request) { + called = true + w.WriteHeader(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodGet, "/x", nil) + rr := httptest.NewRecorder() + h(rr, req) + if rr.Code != http.StatusUnauthorized || called { + t.Fatalf("expected 401 without token, got %d called=%v", rr.Code, called) + } + + tok, _, err := authx.IssueToken(cfg, 1, 2, "owner", 0) + if err != nil { + t.Fatal(err) + } + req2 := httptest.NewRequest(http.MethodGet, "/x", nil) + req2.Header.Set("Authorization", "Bearer "+tok) + rr2 := httptest.NewRecorder() + h(rr2, req2) + if rr2.Code != http.StatusOK || !called { + t.Fatalf("expected 200 with token, got %d", rr2.Code) + } + if authx.TenantID(req2.Context()) != 0 { + // context is on the request passed to next; check via handler + } +} + +func TestMiddlewareInjectsClaims(t *testing.T) { + cfg := authx.JWTConfig{AccessSecret: "test-secret", AccessExpire: 3600} + tok, _, _ := authx.IssueToken(cfg, 7, 8, "editor", 0) + mw := authx.Middleware(cfg, false) + var gotTenant, gotUser int64 + h := mw(func(w http.ResponseWriter, r *http.Request) { + gotTenant = authx.TenantID(r.Context()) + gotUser = authx.UserID(r.Context()) + w.WriteHeader(http.StatusOK) + }) + req := httptest.NewRequest(http.MethodGet, "/x", nil) + req.Header.Set("Authorization", "Bearer "+tok) + rr := httptest.NewRecorder() + h(rr, req) + if gotTenant != 7 || gotUser != 8 { + t.Fatalf("got tenant=%d user=%d", gotTenant, gotUser) + } +} diff --git a/platform/internal/authx/catalog.go b/platform/internal/authx/catalog.go new file mode 100644 index 0000000..243ebe6 --- /dev/null +++ b/platform/internal/authx/catalog.go @@ -0,0 +1,117 @@ +package authx + +import ( + "context" + "fmt" + "strings" +) + +// CompanyPermCatalog 公司可被授予的权限模块(不含平台专属「管理租户」)。 +func CompanyPermCatalog() []string { + return []string{ + Perm读取模块, Perm写入模块, Perm发布模块, + Perm查询数据, Perm新增数据, Perm更新数据, Perm删除数据, Perm导入数据, Perm导出数据, + Perm下载文件, Perm上传文件, + Perm查看审计, + Perm管理智能体, Perm邀请成员, Perm管理组织, + Perm数据同步, + } +} + +// PermModule 权限模块分组。 +type PermModule struct { + Title string `json:"title"` + Items []PermItem `json:"items"` +} + +type PermItem struct { + Perm string `json:"perm"` + Desc string `json:"desc"` +} + +// PermDesc 权限说明(超管授权 / 公司分配时展示)。 +func PermDesc(p string) string { + switch NormalizePerm(p) { + case Perm读取模块: + return "查看模块列表、蓝图与智能体胶囊" + case Perm写入模块: + return "保存在建草稿(不发布)" + case Perm发布模块: + return "发布/更新线上模块" + case Perm查询数据: + return "查询业务列表与详情" + case Perm新增数据: + return "新增业务数据行" + case Perm更新数据: + return "更新业务数据行" + case Perm删除数据: + return "删除业务数据行" + case Perm导入数据: + return "导入 Excel/CSV/JSON" + case Perm导出数据: + return "导出业务数据" + case Perm下载文件: + return "下载已上传文件" + case Perm上传文件: + return "上传附件与素材" + case Perm查看审计: + return "查看操作审计日志" + case Perm管理智能体: + return "管理智能体账号与角色权限" + case Perm邀请成员: + return "生成邀请码、管理成员角色" + case Perm管理组织: + return "管理组织单元与成员归属" + case Perm数据同步: + return "配置跨库数据同步通道" + case Perm管理租户: + return "平台级:管理公司与权限额度" + default: + return p + } +} + +func PermModules() []PermModule { + mk := func(title string, perms ...string) PermModule { + items := make([]PermItem, 0, len(perms)) + for _, p := range perms { + items = append(items, PermItem{Perm: p, Desc: PermDesc(p)}) + } + return PermModule{Title: title, Items: items} + } + return []PermModule{ + mk("模块", Perm读取模块, Perm写入模块, Perm发布模块), + mk("数据", Perm查询数据, Perm新增数据, Perm更新数据, Perm删除数据, Perm导入数据, Perm导出数据), + mk("文件", Perm下载文件, Perm上传文件), + mk("审计", Perm查看审计), + mk("公司管理", Perm管理智能体, Perm邀请成员, Perm管理组织), + mk("数据同步", Perm数据同步), + } +} + +// FilterWithinAllowance 只保留额度内的权限。 +func FilterWithinAllowance(want, allowance []string) ([]string, []string) { + allow := map[string]struct{}{} + for _, p := range NormalizePerms(allowance) { + allow[p] = struct{}{} + } + var ok, denied []string + for _, p := range NormalizePerms(want) { + if _, hit := allow[p]; hit { + ok = append(ok, p) + } else { + denied = append(denied, p) + } + } + return ok, denied +} + +var EntitlementChecker func(ctx context.Context, tenantID int64, perm string) bool + +func AssertWithinEntitlement(want, allowance []string) error { + _, denied := FilterWithinAllowance(want, allowance) + if len(denied) == 0 { + return nil + } + return fmt.Errorf("超出公司权限额度: %s", strings.Join(denied, "、")) +} diff --git a/platform/internal/authx/perms.go b/platform/internal/authx/perms.go new file mode 100644 index 0000000..7d8da02 --- /dev/null +++ b/platform/internal/authx/perms.go @@ -0,0 +1,86 @@ +package authx + +// 权限统一用中文命名。英文旧码仍可通过 NormalizePerm 兼容。 +const ( + Perm读取模块 = "读取模块" + Perm写入模块 = "写入模块" + Perm发布模块 = "发布模块" + Perm新增数据 = "新增数据" + Perm查询数据 = "查询数据" + Perm更新数据 = "更新数据" + Perm删除数据 = "删除数据" + Perm导出数据 = "导出数据" + Perm导入数据 = "导入数据" + Perm查看审计 = "查看审计" + Perm上传文件 = "上传文件" + Perm下载文件 = "下载文件" + Perm管理智能体 = "管理智能体" + Perm邀请成员 = "邀请成员" + Perm管理组织 = "管理组织" + Perm数据同步 = "数据同步" + Perm管理租户 = "管理租户" // 平台超级管理员:跨公司 +) + +// permAlias:英文旧码 → 中文规范名;中文自身映射到自身。 +var permAlias = map[string]string{ + "app.read": Perm读取模块, + "app.write": Perm写入模块, + "app.admin": Perm发布模块, + "row.create": Perm新增数据, + "row.read": Perm查询数据, + "row.update": Perm更新数据, + "row.delete": Perm删除数据, + "row.export": Perm导出数据, + "row.import": Perm导入数据, + "audit.read": Perm查看审计, + "storage.write": Perm上传文件, + "storage.read": Perm下载文件, + "agent.admin": Perm管理智能体, + "tenant.invite": Perm邀请成员, + "org.admin": Perm管理组织, + "sync.admin": Perm数据同步, + "tenant.admin": Perm管理租户, + Perm读取模块: Perm读取模块, + Perm写入模块: Perm写入模块, + Perm发布模块: Perm发布模块, + Perm新增数据: Perm新增数据, + Perm查询数据: Perm查询数据, + Perm更新数据: Perm更新数据, + Perm删除数据: Perm删除数据, + Perm导出数据: Perm导出数据, + Perm导入数据: Perm导入数据, + Perm查看审计: Perm查看审计, + Perm上传文件: Perm上传文件, + Perm下载文件: Perm下载文件, + Perm管理智能体: Perm管理智能体, + Perm邀请成员: Perm邀请成员, + Perm管理组织: Perm管理组织, + Perm数据同步: Perm数据同步, + Perm管理租户: Perm管理租户, +} + +// NormalizePerm 将权限统一为中文规范名(未知则原样返回)。 +func NormalizePerm(p string) string { + if c, ok := permAlias[p]; ok { + return c + } + return p +} + +// NormalizePerms 去重并转为中文规范名。 +func NormalizePerms(perms []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(perms)) + for _, p := range perms { + c := NormalizePerm(p) + if c == "" { + continue + } + if _, ok := seen[c]; ok { + continue + } + seen[c] = struct{}{} + out = append(out, c) + } + return out +} diff --git a/platform/internal/authx/roles.go b/platform/internal/authx/roles.go new file mode 100644 index 0000000..8abde39 --- /dev/null +++ b/platform/internal/authx/roles.go @@ -0,0 +1,93 @@ +package authx + +// 平台账号角色(JWT role)——统一中文命名。 +const ( + Role超级管理员 = "超级管理员" // 平台级:可管理所有公司 + Role管理员 = "管理员" // 公司顶级 + Role编辑 = "编辑" + Role只读 = "只读" + Role待加入 = "待加入" + Role智能体 = "智能体" +) + +// RoleAgent 智能体 JWT 角色(与 Role智能体 相同)。 +const RoleAgent = Role智能体 + +// 智能体租户角色编码(roles 表 code)——统一中文。 +const ( + AgentRole生成发布 = "生成发布" + AgentRole只读 = "只读" + AgentRole读写 = "读写" + AgentRole运维 = "运维" +) + +// NormalizeRole 将平台角色统一为中文(兼容英文旧码)。 +func NormalizeRole(role string) string { + switch role { + case "platform_admin", "super_admin", Role超级管理员: + return Role超级管理员 + case "owner", Role管理员: + return Role管理员 + case "editor", Role编辑: + return Role编辑 + case "viewer", Role只读: + return Role只读 + case "pending", Role待加入: + return Role待加入 + case "agent", Role智能体: + return Role智能体 + default: + return role + } +} + +// NormalizeAgentRoleCode 智能体角色编码 → 中文(兼容英文旧码)。 +func NormalizeAgentRoleCode(code string) string { + switch code { + case "publisher", AgentRole生成发布: + return AgentRole生成发布 + case "viewer", AgentRole只读: + return AgentRole只读 + case "editor", AgentRole读写: + return AgentRole读写 + case "operator", AgentRole运维: + return AgentRole运维 + default: + return code + } +} + +// IsPlatformAdmin 是否平台超级管理员(可跨公司)。 +func IsPlatformAdmin(role string) bool { + return NormalizeRole(role) == Role超级管理员 +} + +// IsOwner 是否公司顶级管理员(不含超级管理员)。 +func IsOwner(role string) bool { + return NormalizeRole(role) == Role管理员 +} + +// IsCompanyAdmin 公司内顶级管理员(超级管理员不介入公司内部)。 +func IsCompanyAdmin(role string) bool { + return NormalizeRole(role) == Role管理员 +} + +// IsAgent 是否智能体账号。 +func IsAgent(role string) bool { + return NormalizeRole(role) == Role智能体 +} + +// RoleLabel 平台角色 → 中文显示(已是中文则原样)。 +func RoleLabel(role string) string { + return NormalizeRole(role) +} + +// ValidPlatformRole 是否可赋予人类成员的角色(不含待加入/智能体/超级管理员)。 +func ValidPlatformRole(role string) bool { + switch NormalizeRole(role) { + case Role管理员, Role编辑, Role只读: + return true + default: + return false + } +} diff --git a/platform/internal/blueprint/blueprint.go b/platform/internal/blueprint/blueprint.go new file mode 100644 index 0000000..f0b47d2 --- /dev/null +++ b/platform/internal/blueprint/blueprint.go @@ -0,0 +1,514 @@ +package blueprint + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" +) + +var identRe = regexp.MustCompile(`^[a-z][a-z0-9_]{1,47}$`) +var nonIdentRe = regexp.MustCompile(`[^a-z0-9_]+`) +var multiUnderRe = regexp.MustCompile(`_+`) + +var reserved = map[string]struct{}{ + "select": {}, "insert": {}, "update": {}, "delete": {}, "drop": {}, + "create": {}, "alter": {}, "table": {}, "schema": {}, "user": {}, + "where": {}, "from": {}, "join": {}, "grant": {}, "revoke": {}, +} + +type Blueprint struct { + Version string `json:"version"` + Meta Meta `json:"meta"` + Storage Storage `json:"storage"` + Entities []Entity `json:"entities"` + Apis Apis `json:"apis"` + Pages []Page `json:"pages"` + Security Security `json:"security"` + Seed *Seed `json:"seed,omitempty"` +} + +type Meta struct { + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description,omitempty"` + Locale string `json:"locale"` + Confidence float64 `json:"confidence,omitempty"` + UIPreset string `json:"ui_preset,omitempty"` + PlatformTitle string `json:"platform_title,omitempty"` + ProjectContext string `json:"project_context,omitempty"` + UI json.RawMessage `json:"ui,omitempty"` + Source json.RawMessage `json:"source,omitempty"` +} + +type Storage struct { + Mode string `json:"mode"` + Engine string `json:"engine"` + SchemaName string `json:"schema_name,omitempty"` +} + +type Entity struct { + Name string `json:"name"` + Table string `json:"table"` + Label string `json:"label"` + PrimaryKey string `json:"primary_key"` + Fields []Field `json:"fields"` + Indexes []Index `json:"indexes,omitempty"` +} + +type Field struct { + Name string `json:"name"` + Label string `json:"label"` + Type string `json:"type"` + Nullable *bool `json:"nullable,omitempty"` + Unique bool `json:"unique,omitempty"` + Default any `json:"default,omitempty"` + MaxLength int `json:"max_length,omitempty"` + Precision int `json:"precision,omitempty"` + Scale int `json:"scale,omitempty"` + EnumValues []string `json:"enum_values,omitempty"` + UI json.RawMessage `json:"ui,omitempty"` +} + +type Index struct { + Name string `json:"name"` + Columns []string `json:"columns"` + Unique bool `json:"unique,omitempty"` +} + +type Apis struct { + BasePath string `json:"base_path"` + Resources []APIResource `json:"resources"` +} + +type APIResource struct { + Entity string `json:"entity"` + Path string `json:"path"` + Operations []string `json:"operations"` + List *ListOpt `json:"list,omitempty"` +} + +type ListOpt struct { + DefaultPageSize int `json:"default_page_size,omitempty"` + MaxPageSize int `json:"max_page_size,omitempty"` + AllowedFilters []string `json:"allowed_filters,omitempty"` + AllowedSorts []string `json:"allowed_sorts,omitempty"` +} + +type Page struct { + ID string `json:"id"` + Title string `json:"title"` + Route string `json:"route"` + Type string `json:"type"` + Entity string `json:"entity"` + Layout *PageLayout `json:"layout,omitempty"` +} + +type PageLayout struct { + Preset string `json:"preset,omitempty"` + Columns []string `json:"columns,omitempty"` + Filters []string `json:"filters,omitempty"` + Actions []string `json:"actions,omitempty"` + ActionLabels map[string]string `json:"action_labels,omitempty"` + FilterStyle string `json:"filter_style,omitempty"` + FormFields []string `json:"form_fields,omitempty"` + Widgets []PageWidget `json:"widgets,omitempty"` +} + +type PageWidget struct { + Type string `json:"type"` + Title string `json:"title,omitempty"` + Metric string `json:"metric,omitempty"` + Metrics []string `json:"metrics,omitempty"` + GroupBy string `json:"group_by,omitempty"` + XField string `json:"x_field,omitempty"` + Entity string `json:"entity,omitempty"` + Columns []string `json:"columns,omitempty"` + LabelField string `json:"label_field,omitempty"` + ValueField string `json:"value_field,omitempty"` + SecondaryField string `json:"secondary_field,omitempty"` + WarnField string `json:"warn_field,omitempty"` + FilterField string `json:"filter_field,omitempty"` + FilterOp string `json:"filter_op,omitempty"` + FilterValue any `json:"filter_value,omitempty"` + YUnit string `json:"y_unit,omitempty"` + Variant string `json:"variant,omitempty"` + CycleDays int `json:"cycle_days,omitempty"` +} + +type Security struct { + Visibility string `json:"visibility"` + Roles []Role `json:"roles"` + RowPolicies []RowPolicy `json:"row_policies"` +} + +type Role struct { + Name string `json:"name"` + Permissions []string `json:"permissions"` +} + +type RowPolicy struct { + Entity string `json:"entity"` + Rule string `json:"rule"` +} + +type Seed struct { + ImportExcel bool `json:"import_excel,omitempty"` + MaxRows int `json:"max_rows,omitempty"` +} + +func Parse(raw json.RawMessage) (*Blueprint, error) { + var bp Blueprint + if err := json.Unmarshal(raw, &bp); err != nil { + return nil, fmt.Errorf("invalid blueprint json: %w", err) + } + return &bp, nil +} + +func (bp *Blueprint) Validate(pathSlug string) error { + if bp.Version != "1.0" { + return fmt.Errorf("unsupported version: %s", bp.Version) + } + bp.SanitizeIdentifiers() + // 连字符等非法字符规范为下划线(schema/表名不能含 -) + bp.Meta.Slug = NormalizeIdent(bp.Meta.Slug) + pathSlug = NormalizeIdent(pathSlug) + if err := checkIdent("meta.slug", bp.Meta.Slug); err != nil { + return err + } + if bp.Meta.Slug != pathSlug { + return fmt.Errorf("meta.slug(%s) != path slug(%s)", bp.Meta.Slug, pathSlug) + } + if bp.Meta.Name == "" { + return fmt.Errorf("meta.name required") + } + if bp.Storage.Mode != "schema_per_app" && bp.Storage.Mode != "database_per_app" { + return fmt.Errorf("unsupported storage.mode: %s", bp.Storage.Mode) + } + if bp.Storage.Engine != "postgres" && bp.Storage.Engine != "mysql" { + return fmt.Errorf("unsupported storage.engine: %s", bp.Storage.Engine) + } + if len(bp.Entities) == 0 { + return fmt.Errorf("entities required") + } + + entityNames := map[string]Entity{} + for i, e := range bp.Entities { + prefix := fmt.Sprintf("entities[%d]", i) + if err := checkIdent(prefix+".name", e.Name); err != nil { + return err + } + if err := checkIdent(prefix+".table", e.Table); err != nil { + return err + } + if err := checkIdent(prefix+".primary_key", e.PrimaryKey); err != nil { + return err + } + if len(e.Fields) == 0 { + return fmt.Errorf("%s.fields required", prefix) + } + fields := map[string]struct{}{} + pkFound := false + for j, f := range e.Fields { + fp := fmt.Sprintf("%s.fields[%d]", prefix, j) + if err := checkIdent(fp+".name", f.Name); err != nil { + return err + } + if !validFieldType(f.Type) { + return fmt.Errorf("%s.type invalid: %s", fp, f.Type) + } + f.Type = NormalizeFieldType(f.Type) + if _, ok := fields[f.Name]; ok { + return fmt.Errorf("%s duplicate field %s", prefix, f.Name) + } + fields[f.Name] = struct{}{} + if f.Name == e.PrimaryKey { + pkFound = true + } + } + if !pkFound { + return fmt.Errorf("%s primary_key %s not in fields", prefix, e.PrimaryKey) + } + for j, idx := range e.Indexes { + ip := fmt.Sprintf("%s.indexes[%d]", prefix, j) + if err := checkIdent(ip+".name", idx.Name); err != nil { + return err + } + if !strings.HasPrefix(idx.Name, "idx_") { + return fmt.Errorf("%s.name must start with idx_", ip) + } + for _, col := range idx.Columns { + if _, ok := fields[col]; !ok { + return fmt.Errorf("%s unknown column %s", ip, col) + } + } + } + entityNames[e.Name] = e + } + + if len(bp.Apis.Resources) == 0 { + return fmt.Errorf("apis.resources required") + } + for i, r := range bp.Apis.Resources { + prefix := fmt.Sprintf("apis.resources[%d]", i) + if _, ok := entityNames[r.Entity]; !ok { + return fmt.Errorf("%s unknown entity %s", prefix, r.Entity) + } + path := strings.TrimPrefix(r.Path, "/") + if err := checkIdent(prefix+".path", path); err != nil { + return err + } + if len(r.Operations) == 0 { + return fmt.Errorf("%s.operations required", prefix) + } + } + return nil +} + +// AssignSchemaName 由平台重写 schema;database_per_app 时使用 public。 +func (bp *Blueprint) AssignSchemaName(tenantID int64) string { + if bp.Storage.Mode == "database_per_app" { + bp.Storage.SchemaName = "public" + return "public" + } + name := fmt.Sprintf("app_t%d_%s", tenantID, bp.Meta.Slug) + if len(name) > 48 { + name = name[:48] + } + bp.Storage.SchemaName = name + return name +} + +// AssignDatabaseName database_per_app 时返回独立库名,否则空。 +func (bp *Blueprint) AssignDatabaseName(tenantID int64) string { + if bp.Storage.Mode != "database_per_app" { + return "" + } + name := fmt.Sprintf("appdb_t%d_%s", tenantID, bp.Meta.Slug) + if len(name) > 48 { + name = name[:48] + } + return name +} + +func checkIdent(field, v string) error { + if !identRe.MatchString(v) { + return fmt.Errorf("%s invalid identifier: %s", field, v) + } + if _, bad := reserved[v]; bad { + return fmt.Errorf("%s reserved identifier: %s", field, v) + } + return nil +} + +// SanitizeIdentifiers 将 camelCase / 点号等统一为 snake_case,并同步页面与 API 引用。 +func (bp *Blueprint) SanitizeIdentifiers() { + entityRename := map[string]string{} + for i := range bp.Entities { + e := &bp.Entities[i] + oldName := e.Name + e.Name = NormalizeIdent(e.Name) + e.Table = NormalizeIdent(e.Table) + e.PrimaryKey = NormalizeIdent(e.PrimaryKey) + if oldName != "" { + entityRename[oldName] = e.Name + entityRename[NormalizeIdent(oldName)] = e.Name + } + fieldRename := map[string]string{} + used := map[string]int{} + for j := range e.Fields { + f := &e.Fields[j] + old := f.Name + next := NormalizeIdent(old) + if n, ok := used[next]; ok { + used[next] = n + 1 + next = fmt.Sprintf("%s_%d", next, n+1) + } else { + used[next] = 1 + } + f.Name = next + f.Type = NormalizeFieldType(f.Type) + if old != "" { + fieldRename[old] = next + } + } + e.PrimaryKey = renameOrSelf(fieldRename, e.PrimaryKey) + for j := range e.Indexes { + idx := &e.Indexes[j] + idx.Name = NormalizeIdent(idx.Name) + if !strings.HasPrefix(idx.Name, "idx_") { + idx.Name = "idx_" + idx.Name + } + for k, col := range idx.Columns { + idx.Columns[k] = renameOrSelf(fieldRename, col) + } + } + // pages / apis that reference this entity's fields + for pi := range bp.Pages { + p := &bp.Pages[pi] + entRef := NormalizeIdent(p.Entity) + if p.Entity == oldName || p.Entity == e.Name || entRef == e.Name || entRef == NormalizeIdent(oldName) { + p.Entity = e.Name + if p.Layout != nil { + p.Layout.Columns = renameSlice(fieldRename, p.Layout.Columns) + p.Layout.Filters = renameSlice(fieldRename, p.Layout.Filters) + p.Layout.FormFields = renameSlice(fieldRename, p.Layout.FormFields) + for wi := range p.Layout.Widgets { + w := &p.Layout.Widgets[wi] + w.Metric = renameOrSelf(fieldRename, w.Metric) + w.GroupBy = renameOrSelf(fieldRename, w.GroupBy) + w.XField = renameOrSelf(fieldRename, w.XField) + w.LabelField = renameOrSelf(fieldRename, w.LabelField) + w.ValueField = renameOrSelf(fieldRename, w.ValueField) + w.WarnField = renameOrSelf(fieldRename, w.WarnField) + w.FilterField = renameOrSelf(fieldRename, w.FilterField) + w.Metrics = renameSlice(fieldRename, w.Metrics) + w.Columns = renameSlice(fieldRename, w.Columns) + if w.Entity == oldName || NormalizeIdent(w.Entity) == e.Name { + w.Entity = e.Name + } + } + } + } + } + for ri := range bp.Apis.Resources { + r := &bp.Apis.Resources[ri] + entRef := NormalizeIdent(r.Entity) + if r.Entity == oldName || r.Entity == e.Name || entRef == e.Name || entRef == NormalizeIdent(oldName) { + r.Entity = e.Name + if r.List != nil { + r.List.AllowedFilters = renameSlice(fieldRename, r.List.AllowedFilters) + r.List.AllowedSorts = renameSlice(fieldRename, r.List.AllowedSorts) + } + } + } + } + for i := range bp.Pages { + p := &bp.Pages[i] + p.Entity = renameOrSelf(entityRename, p.Entity) + } + for i := range bp.Apis.Resources { + r := &bp.Apis.Resources[i] + r.Entity = renameOrSelf(entityRename, r.Entity) + path := strings.TrimPrefix(r.Path, "/") + r.Path = "/" + NormalizeIdent(path) + } + for i := range bp.Security.RowPolicies { + rp := &bp.Security.RowPolicies[i] + rp.Entity = renameOrSelf(entityRename, rp.Entity) + } +} + +func renameOrSelf(m map[string]string, v string) string { + if v == "" { + return v + } + if n, ok := m[v]; ok { + return n + } + return NormalizeIdent(v) +} + +func renameSlice(m map[string]string, in []string) []string { + if len(in) == 0 { + return in + } + out := make([]string, len(in)) + for i, v := range in { + out[i] = renameOrSelf(m, v) + } + return out +} + +// NormalizeSlug 兼容旧名。 +func NormalizeSlug(s string) string { return NormalizeIdent(s) } + +// NormalizeIdent 驼峰/点号/连字符 → snake_case,符合 ^[a-z][a-z0-9_]{1,47}$ +func NormalizeIdent(s string) string { + s = strings.TrimSpace(s) + if s == "" { + return "field" + } + var b strings.Builder + b.Grow(len(s) + 8) + prevUnder := false + for i, r := range s { + switch { + case r >= 'A' && r <= 'Z': + if i > 0 && !prevUnder { + b.WriteByte('_') + } + b.WriteByte(byte(r - 'A' + 'a')) + prevUnder = false + case r >= 'a' && r <= 'z' || r >= '0' && r <= '9': + b.WriteByte(byte(r)) + prevUnder = false + default: + // '.', '-', 空格等 + if !prevUnder && b.Len() > 0 { + b.WriteByte('_') + prevUnder = true + } + } + } + s = strings.Trim(b.String(), "_") + s = multiUnderRe.ReplaceAllString(s, "_") + if s == "" { + return "field" + } + if s[0] < 'a' || s[0] > 'z' { + s = "f_" + s + } + if len(s) > 48 { + s = strings.TrimRight(s[:48], "_") + } + if s == "" { + return "field" + } + return s +} + +// NormalizeFieldType 将常见别名收成蓝图允许的 type。 +func NormalizeFieldType(t string) string { + t = strings.ToLower(strings.TrimSpace(t)) + switch t { + case "float", "float32", "float64", "double", "number", "numeric", "real", "money": + return "decimal" + case "integer", "int32", "int64", "long", "serial": + if t == "int64" || t == "long" || t == "serial" { + return "bigint" + } + return "int" + case "bool": + return "boolean" + case "varchar", "str", "char": + return "string" + case "timestamp", "timestamptz", "time": + return "datetime" + case "file", "attachment", "blob": + return "file_ref" + case "string", "text", "int", "bigint", "decimal", "boolean", "date", "datetime", "enum", "json", "file_ref": + return t + default: + if t == "" { + return "string" + } + return t + } +} + +func validFieldType(t string) bool { + switch NormalizeFieldType(t) { + case "string", "text", "int", "bigint", "decimal", "boolean", "date", "datetime", "enum", "json", "file_ref": + return true + default: + return false + } +} + +func BoolOr(p *bool, def bool) bool { + if p == nil { + return def + } + return *p +} diff --git a/platform/internal/blueprint/ident_test.go b/platform/internal/blueprint/ident_test.go new file mode 100644 index 0000000..39ffb8f --- /dev/null +++ b/platform/internal/blueprint/ident_test.go @@ -0,0 +1,52 @@ +package blueprint + +import "testing" + +func TestNormalizeIdent(t *testing.T) { + cases := map[string]string{ + "beforeDay": "before_day", + "cjl.value": "cjl_value", + "nextDay": "next_day", + "settlement-observation": "settlement_observation", + "Already_ok": "already_ok", + } + for in, want := range cases { + got := NormalizeIdent(in) + if got != want { + t.Fatalf("%s => %s, want %s", in, got, want) + } + } +} + +func TestSanitizeFields(t *testing.T) { + bp := &Blueprint{ + Version: "1.0", + Meta: Meta{Name: "t", Slug: "demo_app", Locale: "zh-CN"}, + Storage: Storage{Mode: "schema_per_app", Engine: "postgres"}, + Entities: []Entity{{ + Name: "Point", Table: "Point", PrimaryKey: "id", Label: "p", + Fields: []Field{ + {Name: "id", Label: "ID", Type: "bigint"}, + {Name: "beforeDay", Label: "b", Type: "int"}, + {Name: "cjl.value", Label: "v", Type: "decimal"}, + }, + }}, + Apis: Apis{Resources: []APIResource{{ + Entity: "Point", Path: "/Points", Operations: []string{"list"}, + List: &ListOpt{AllowedFilters: []string{"beforeDay"}}, + }}}, + Pages: []Page{{ + ID: "l", Title: "list", Route: "/x", Type: "list", Entity: "Point", + Layout: &PageLayout{Columns: []string{"beforeDay", "cjl.value"}, Filters: []string{"beforeDay"}}, + }}, + } + if err := bp.Validate("demo_app"); err != nil { + t.Fatal(err) + } + if bp.Entities[0].Fields[1].Name != "before_day" { + t.Fatalf("field=%s", bp.Entities[0].Fields[1].Name) + } + if bp.Pages[0].Layout.Columns[0] != "before_day" { + t.Fatalf("col=%v", bp.Pages[0].Layout.Columns) + } +} diff --git a/platform/internal/blueprint/merge.go b/platform/internal/blueprint/merge.go new file mode 100644 index 0000000..a2b3a32 --- /dev/null +++ b/platform/internal/blueprint/merge.go @@ -0,0 +1,163 @@ +package blueprint + +import ( + "fmt" + "strings" +) + +// MergeResult describes what was added when merging incoming into base. +type MergeResult struct { + AddedPages []string + AddedEntities []string + AddedResources []string +} + +// MergeInto merges incoming pages/entities/apis into base (existing published app). +// Existing entities keep their fields; new fields on known entities are appended. +// Pages with the same id or route are rejected; new pages are appended. +func MergeInto(base, incoming *Blueprint) (*MergeResult, error) { + if base == nil { + return nil, fmt.Errorf("base blueprint is nil") + } + if incoming == nil { + return nil, fmt.Errorf("incoming blueprint is nil") + } + res := &MergeResult{} + + entityByName := map[string]int{} + for i, e := range base.Entities { + entityByName[e.Name] = i + } + for _, e := range incoming.Entities { + if idx, ok := entityByName[e.Name]; ok { + base.Entities[idx] = mergeEntity(base.Entities[idx], e) + continue + } + base.Entities = append(base.Entities, e) + entityByName[e.Name] = len(base.Entities) - 1 + res.AddedEntities = append(res.AddedEntities, e.Name) + } + + pathKey := func(p string) string { + p = strings.TrimPrefix(strings.TrimSpace(p), "/") + return p + } + resByPath := map[string]int{} + for i, r := range base.Apis.Resources { + resByPath[pathKey(r.Path)] = i + } + for _, r := range incoming.Apis.Resources { + k := pathKey(r.Path) + if idx, ok := resByPath[k]; ok { + base.Apis.Resources[idx] = mergeResource(base.Apis.Resources[idx], r) + continue + } + base.Apis.Resources = append(base.Apis.Resources, r) + resByPath[k] = len(base.Apis.Resources) - 1 + res.AddedResources = append(res.AddedResources, k) + } + + pageByID := map[string]struct{}{} + routeBy := map[string]struct{}{} + for _, p := range base.Pages { + pageByID[p.ID] = struct{}{} + routeBy[p.Route] = struct{}{} + } + for _, p := range incoming.Pages { + if _, ok := pageByID[p.ID]; ok { + return nil, fmt.Errorf("page id already exists: %s (use a new page id when adding to an existing app)", p.ID) + } + if _, ok := routeBy[p.Route]; ok { + return nil, fmt.Errorf("page route already exists: %s", p.Route) + } + base.Pages = append(base.Pages, p) + pageByID[p.ID] = struct{}{} + routeBy[p.Route] = struct{}{} + res.AddedPages = append(res.AddedPages, p.ID) + } + + if incoming.Meta.Description != "" && base.Meta.Description == "" { + base.Meta.Description = incoming.Meta.Description + } + if incoming.Meta.UIPreset != "" && base.Meta.UIPreset == "" { + base.Meta.UIPreset = incoming.Meta.UIPreset + } + if len(incoming.Meta.UI) > 0 && len(base.Meta.UI) == 0 { + base.Meta.UI = incoming.Meta.UI + } + if incoming.Seed != nil && base.Seed == nil { + base.Seed = incoming.Seed + } + + if len(res.AddedPages) == 0 && len(res.AddedEntities) == 0 && len(res.AddedResources) == 0 { + return nil, fmt.Errorf("nothing new to publish: provide newly generated pages (and entities/apis if needed) for an existing app") + } + return res, nil +} + +func mergeEntity(base, incoming Entity) Entity { + fieldBy := map[string]struct{}{} + for _, f := range base.Fields { + fieldBy[f.Name] = struct{}{} + } + for _, f := range incoming.Fields { + if _, ok := fieldBy[f.Name]; ok { + continue + } + base.Fields = append(base.Fields, f) + fieldBy[f.Name] = struct{}{} + } + idxBy := map[string]struct{}{} + for _, ix := range base.Indexes { + idxBy[ix.Name] = struct{}{} + } + for _, ix := range incoming.Indexes { + if _, ok := idxBy[ix.Name]; ok { + continue + } + base.Indexes = append(base.Indexes, ix) + } + if base.Label == "" && incoming.Label != "" { + base.Label = incoming.Label + } + return base +} + +func mergeResource(base, incoming APIResource) APIResource { + opSet := map[string]struct{}{} + for _, op := range base.Operations { + opSet[op] = struct{}{} + } + for _, op := range incoming.Operations { + if _, ok := opSet[op]; ok { + continue + } + base.Operations = append(base.Operations, op) + opSet[op] = struct{}{} + } + if base.List == nil && incoming.List != nil { + base.List = incoming.List + } else if base.List != nil && incoming.List != nil { + filt := map[string]struct{}{} + for _, f := range base.List.AllowedFilters { + filt[f] = struct{}{} + } + for _, f := range incoming.List.AllowedFilters { + if _, ok := filt[f]; !ok { + base.List.AllowedFilters = append(base.List.AllowedFilters, f) + filt[f] = struct{}{} + } + } + sorts := map[string]struct{}{} + for _, s := range base.List.AllowedSorts { + sorts[s] = struct{}{} + } + for _, s := range incoming.List.AllowedSorts { + if _, ok := sorts[s]; !ok { + base.List.AllowedSorts = append(base.List.AllowedSorts, s) + sorts[s] = struct{}{} + } + } + } + return base +} diff --git a/platform/internal/blueprint/merge_test.go b/platform/internal/blueprint/merge_test.go new file mode 100644 index 0000000..14e1072 --- /dev/null +++ b/platform/internal/blueprint/merge_test.go @@ -0,0 +1,53 @@ +package blueprint + +import "testing" + +func TestMergeIntoAddsPages(t *testing.T) { + base := &Blueprint{ + Version: "1.0", + Meta: Meta{Name: "app", Slug: "demo"}, + Entities: []Entity{{ + Name: "item", Table: "item", Label: "Item", PrimaryKey: "id", + Fields: []Field{{Name: "id", Label: "ID", Type: "string"}}, + }}, + Apis: Apis{ + BasePath: "/api/v1/apps/demo", + Resources: []APIResource{{ + Entity: "item", Path: "/items", Operations: []string{"list"}, + }}, + }, + Pages: []Page{{ID: "item_list", Title: "列表", Route: "/items", Type: "list", Entity: "item"}}, + } + incoming := &Blueprint{ + Entities: []Entity{{ + Name: "order", Table: "order", Label: "Order", PrimaryKey: "id", + Fields: []Field{{Name: "id", Label: "ID", Type: "string"}}, + }}, + Apis: Apis{Resources: []APIResource{{ + Entity: "order", Path: "/orders", Operations: []string{"list", "import"}, + }}}, + Pages: []Page{{ID: "order_list", Title: "订单", Route: "/orders", Type: "list", Entity: "order"}}, + } + res, err := MergeInto(base, incoming) + if err != nil { + t.Fatal(err) + } + if len(res.AddedPages) != 1 || res.AddedPages[0] != "order_list" { + t.Fatalf("added pages: %+v", res.AddedPages) + } + if len(base.Pages) != 2 || len(base.Entities) != 2 { + t.Fatalf("pages=%d entities=%d", len(base.Pages), len(base.Entities)) + } +} + +func TestMergeIntoRejectsDuplicatePageID(t *testing.T) { + base := &Blueprint{ + Pages: []Page{{ID: "a", Route: "/a", Type: "list", Entity: "x"}}, + } + incoming := &Blueprint{ + Pages: []Page{{ID: "a", Route: "/b", Type: "list", Entity: "x"}}, + } + if _, err := MergeInto(base, incoming); err == nil { + t.Fatal("expected duplicate page id error") + } +} diff --git a/platform/internal/config/config.go b/platform/internal/config/config.go new file mode 100644 index 0000000..239a442 --- /dev/null +++ b/platform/internal/config/config.go @@ -0,0 +1,73 @@ +package config + +import "github.com/zeromicro/go-zero/rest" + +type Config struct { + rest.RestConf + DataSource string `json:",optional"` + DryRun bool `json:",optional"` + DevAuth bool `json:",default=true"` + Auth AuthConf `json:",optional"` + SMS SMSConf `json:",optional"` + License LicenseConf `json:",optional"` + Agent AgentConf `json:",optional"` + PublicBaseURL string `json:",optional"` + RateLimitPerMin int `json:",default=180"` + Storage StorageConf `json:",optional"` + DBSync DBSyncConf `json:",optional"` +} + +type AuthConf struct { + AccessSecret string `json:",optional"` + AccessExpire int64 `json:",default=86400"` // JWT 会话秒数;登录态时效,与授权到期无关 + IssueSecret string `json:",optional"` + // PhoneLoginOnly:仅允许手机号登录。默认 false。 + // 若 License.Enabled(有部署期限控制),运行时会忽略本开关,允许用户名登录。 + PhoneLoginOnly bool `json:",optional"` + // DisableUsernameLoginIfPhoneBound:已绑手机禁止用户名登录(账号级策略仍可单独禁用)。 + DisableUsernameLoginIfPhoneBound bool `json:",optional"` + // RequirePhoneBound:无手机号不可登录。有 License 期限时通常不必开启。 + RequirePhoneBound bool `json:",optional"` +} + +// LicenseConf 客户机授权:每次续费/延期生成独立签名文件;运行时取目录中最新有效租约。 +type LicenseConf struct { + Enabled bool `json:",optional"` + Customer string `json:",optional"` + LeaseDir string `json:",optional"` // 默认 ./data/license/leases;租约文件目录 + StateDir string `json:",optional"` // 默认 ./data/license/state;消费账本(与 leases 分离,防只删租约复用) + LeasePath string `json:",optional"` // 兼容旧字段 + ControlSecret string `json:",optional"` + SignSecret string `json:",optional"` + SeedNotAfter string `json:",optional"` + GraceDays int `json:",optional"` + Message string `json:",optional"` + NotAfter string `json:",optional"` + // RedeemURL 可选:你们的核销服务根地址。配置后导入延期包必须联网核销 id(防删库清盘后复用旧包)。 + // POST {RedeemURL}/v1/license/redeem Body: {"id","customer","action":"redeem"} + RedeemURL string `json:",optional"` +} + +// SMSConf 短信验证码。Provider=dev 时不真实发信,验证码写日志并可在接口返回 debug_code。 +type SMSConf struct { + Provider string `json:",default=dev"` // dev | off + CodeTTLSeconds int `json:",default=300"` + ResendSeconds int `json:",default=60"` + DevFixedCode string `json:",optional"` // 开发可固定如 123456 +} + +type AgentConf struct { + CapsuleSecret string `json:",optional"` + RegisterSecret string `json:",optional"` // 宿主自注册防刷;空则回退 IssueSecret/AccessSecret +} + +type StorageConf struct { + Driver string `json:",default=local"` + LocalRoot string `json:",default=./data/uploads"` + PublicBase string `json:",optional"` // 默认 PublicBaseURL + /api/v1/storage +} + +type DBSyncConf struct { + Enabled bool `json:",default=true"` + DataDir string `json:",default=./data/dbsync"` // 通道/冲突队列 JSON +} diff --git a/platform/internal/crud/common.go b/platform/internal/crud/common.go new file mode 100644 index 0000000..6e50d42 --- /dev/null +++ b/platform/internal/crud/common.go @@ -0,0 +1,188 @@ +package crud + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "aijianzhan/platform/internal/blueprint" + "aijianzhan/platform/internal/meta" +) + +func ensureOp(ref *meta.ResourceRef, op string) error { + for _, o := range ref.Resource.Operations { + if o == op { + return nil + } + } + return fmt.Errorf("operation %s not allowed", op) +} + +func validateFilters(ref *meta.ResourceRef, filters map[string]string) error { + if len(filters) == 0 { + return nil + } + allowed := map[string]struct{}{} + if ref.Resource.List != nil { + for _, f := range ref.Resource.List.AllowedFilters { + allowed[f] = struct{}{} + } + } + for k := range filters { + if _, ok := allowed[k]; !ok { + return fmt.Errorf("filter not allowed: %s", k) + } + } + return nil +} + +func validateSort(ref *meta.ResourceRef, sortBy string) error { + if sortBy == "" { + return nil + } + field := strings.TrimPrefix(sortBy, "-") + allowed := map[string]struct{}{} + if ref.Resource.List != nil { + for _, s := range ref.Resource.List.AllowedSorts { + allowed[s] = struct{}{} + } + } + if _, ok := allowed[field]; !ok { + return fmt.Errorf("sort not allowed: %s", field) + } + return nil +} + +func sanitizeBody(entity blueprint.Entity, body map[string]any, partial bool) (map[string]any, error) { + fields := map[string]blueprint.Field{} + for _, f := range entity.Fields { + fields[f.Name] = f + } + out := map[string]any{} + for k, v := range body { + if k == "tenant_id" || k == "org_unit_id" || k == "created_at" || k == "updated_at" || k == "created_by" { + continue + } + f, ok := fields[k] + if !ok { + return nil, fmt.Errorf("unknown field: %s", k) + } + if f.Name == entity.PrimaryKey && partial { + continue + } + out[k] = v + } + if !partial { + system := map[string]struct{}{ + "tenant_id": {}, "org_unit_id": {}, "created_at": {}, "updated_at": {}, "created_by": {}, + } + for _, f := range entity.Fields { + if f.Name == entity.PrimaryKey { + continue + } + if _, ok := system[f.Name]; ok { + continue + } + if _, ok := out[f.Name]; !ok && !blueprint.BoolOr(f.Nullable, true) && f.Default == nil { + return nil, fmt.Errorf("missing required field: %s", f.Name) + } + } + } + return out, nil +} + +func fieldByName(entity blueprint.Entity, name string) (blueprint.Field, bool) { + for _, f := range entity.Fields { + if f.Name == name { + return f, true + } + } + return blueprint.Field{}, false +} + +func isAutoPK(entity blueprint.Entity) bool { + f, ok := fieldByName(entity, entity.PrimaryKey) + if !ok { + return false + } + return f.Type == "bigint" || f.Type == "int" +} + +func quoteIdent(name string) string { + return `"` + strings.ReplaceAll(name, `"`, ``) + `"` +} + +func qualifiedTable(ref *meta.ResourceRef) string { + return quoteIdent(ref.App.SchemaName) + "." + quoteIdent(ref.Entity.Table) +} + +func pageBounds(ref *meta.ResourceRef, page, pageSize int) (int, int) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + if ref.Resource.List != nil && ref.Resource.List.MaxPageSize > 0 && pageSize > ref.Resource.List.MaxPageSize { + pageSize = ref.Resource.List.MaxPageSize + } + return page, pageSize +} + +func matchFilters(row map[string]any, filters map[string]string) bool { + for k, v := range filters { + if fmt.Sprint(row[k]) != v { + return false + } + } + return true +} + +func sortRows(rows []map[string]any, sortBy string) { + if sortBy == "" { + return + } + desc := strings.HasPrefix(sortBy, "-") + field := strings.TrimPrefix(sortBy, "-") + sort.SliceStable(rows, func(i, j int) bool { + a, b := fmt.Sprint(rows[i][field]), fmt.Sprint(rows[j][field]) + if desc { + return a > b + } + return a < b + }) +} + +func cloneRow(in map[string]any) map[string]any { + out := make(map[string]any, len(in)) + for k, v := range in { + out[k] = v + } + return out +} + +func toInt64(v any) int64 { + switch t := v.(type) { + case int64: + return t + case int: + return int64(t) + case float64: + return int64(t) + case string: + n, _ := strconv.ParseInt(t, 10, 64) + return n + default: + return 0 + } +} + +func normalizeDBValue(v any) any { + switch t := v.(type) { + case []byte: + return string(t) + default: + return t + } +} diff --git a/platform/internal/crud/engine.go b/platform/internal/crud/engine.go new file mode 100644 index 0000000..81d0882 --- /dev/null +++ b/platform/internal/crud/engine.go @@ -0,0 +1,15 @@ +package crud + +import ( + "context" + + "aijianzhan/platform/internal/meta" +) + +type Engine interface { + List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) (items []map[string]any, total int, err error) + Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) + Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) + Update(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) + Delete(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) error +} diff --git a/platform/internal/crud/helpers_test.go b/platform/internal/crud/helpers_test.go new file mode 100644 index 0000000..acc4eeb --- /dev/null +++ b/platform/internal/crud/helpers_test.go @@ -0,0 +1,50 @@ +package crud + +import ( + "strings" + "testing" + + "aijianzhan/platform/internal/blueprint" + "aijianzhan/platform/internal/meta" +) + +func TestValidateFiltersWhitelist(t *testing.T) { + ref := &meta.ResourceRef{ + Resource: blueprint.APIResource{ + List: &blueprint.ListOpt{AllowedFilters: []string{"name"}}, + }, + } + if err := validateFilters(ref, map[string]string{"hack": "1"}); err == nil { + t.Fatal("expected rejection") + } + if err := validateFilters(ref, map[string]string{"name": "a"}); err != nil { + t.Fatal(err) + } +} + +func TestSanitizeRejectsUnknown(t *testing.T) { + n := false + entity := blueprint.Entity{ + Name: "item", Table: "item", PrimaryKey: "id", + Fields: []blueprint.Field{ + {Name: "id", Type: "bigint"}, + {Name: "name", Type: "string", Nullable: &n}, + }, + } + _, err := sanitizeBody(entity, map[string]any{"name": "x", "drop": "1"}, false) + if err == nil || !strings.Contains(err.Error(), "unknown field") { + t.Fatalf("expected unknown field, got %v", err) + } +} + +func TestQualifiedTable(t *testing.T) { + ref := &meta.ResourceRef{ + App: &meta.AppRecord{SchemaName: "app_t1_demo"}, + Entity: blueprint.Entity{Table: "item"}, + } + got := qualifiedTable(ref) + want := `"app_t1_demo"."item"` + if got != want { + t.Fatalf("got %s want %s", got, want) + } +} diff --git a/platform/internal/crud/memory.go b/platform/internal/crud/memory.go new file mode 100644 index 0000000..00bbe8b --- /dev/null +++ b/platform/internal/crud/memory.go @@ -0,0 +1,169 @@ +package crud + +import ( + "context" + "fmt" + "sync" + "time" + + "aijianzhan/platform/internal/meta" + + "github.com/google/uuid" +) + +// MemoryEngine 骨架默认引擎:不依赖外部 DB,方便本地验证动态 CRUD。 +type MemoryEngine struct { + mu sync.RWMutex + rows map[string][]map[string]any // schema.table +} + +func NewMemoryEngine() *MemoryEngine { + return &MemoryEngine{rows: map[string][]map[string]any{}} +} + +func tableKey(ref *meta.ResourceRef) string { + return ref.App.SchemaName + "." + ref.Entity.Table +} + +func (e *MemoryEngine) List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) ([]map[string]any, int, error) { + if err := ensureOp(ref, "list"); err != nil { + return nil, 0, err + } + if err := validateFilters(ref, filters); err != nil { + return nil, 0, err + } + if err := validateSort(ref, sortBy); err != nil { + return nil, 0, err + } + page, pageSize = pageBounds(ref, page, pageSize) + + e.mu.RLock() + defer e.mu.RUnlock() + all := e.rows[tableKey(ref)] + filtered := make([]map[string]any, 0, len(all)) + for _, row := range all { + if toInt64(row["tenant_id"]) != tenantID { + continue + } + if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) { + continue + } + if matchFilters(row, filters) { + filtered = append(filtered, cloneRow(row)) + } + } + sortRows(filtered, sortBy) + total := len(filtered) + start := (page - 1) * pageSize + if start >= total { + return []map[string]any{}, total, nil + } + end := start + pageSize + if end > total { + end = total + } + return filtered[start:end], total, nil +} + +func (e *MemoryEngine) Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) { + if err := ensureOp(ref, "get"); err != nil { + return nil, err + } + e.mu.RLock() + defer e.mu.RUnlock() + pk := ref.Entity.PrimaryKey + for _, row := range e.rows[tableKey(ref)] { + if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { + if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) { + break + } + return cloneRow(row), nil + } + } + return nil, fmt.Errorf("not found") +} + +func (e *MemoryEngine) Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) { + if err := ensureOp(ref, "create"); err != nil { + return nil, err + } + row, err := sanitizeBody(ref.Entity, body, false) + if err != nil { + return nil, err + } + pk := ref.Entity.PrimaryKey + if _, ok := row[pk]; !ok { + if isAutoPK(ref.Entity) { + row[pk] = time.Now().UnixNano() + } else { + row[pk] = uuid.NewString() + } + } + now := time.Now().UTC().Format(time.RFC3339) + row["tenant_id"] = tenantID + row["created_by"] = userID + row["created_at"] = now + row["updated_at"] = now + if scope := RowScopeFrom(ctx); scope.WriteOrgUnit > 0 { + row["org_unit_id"] = scope.WriteOrgUnit + } + + e.mu.Lock() + defer e.mu.Unlock() + k := tableKey(ref) + e.rows[k] = append(e.rows[k], row) + return cloneRow(row), nil +} + +func (e *MemoryEngine) Update(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) { + if err := ensureOp(ref, "update"); err != nil { + return nil, err + } + patch, err := sanitizeBody(ref.Entity, body, true) + if err != nil { + return nil, err + } + e.mu.Lock() + defer e.mu.Unlock() + pk := ref.Entity.PrimaryKey + rows := e.rows[tableKey(ref)] + for i, row := range rows { + if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { + for k, v := range patch { + if k == pk || k == "tenant_id" { + continue + } + row[k] = v + } + row["updated_at"] = time.Now().UTC().Format(time.RFC3339) + rows[i] = row + return cloneRow(row), nil + } + } + return nil, fmt.Errorf("not found") +} + +func (e *MemoryEngine) Delete(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string) error { + if err := ensureOp(ref, "delete"); err != nil { + return err + } + e.mu.Lock() + defer e.mu.Unlock() + pk := ref.Entity.PrimaryKey + k := tableKey(ref) + rows := e.rows[k] + out := rows[:0] + found := false + for _, row := range rows { + if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { + found = true + continue + } + out = append(out, row) + } + if !found { + return fmt.Errorf("not found") + } + e.rows[k] = out + return nil +} diff --git a/platform/internal/crud/pool.go b/platform/internal/crud/pool.go new file mode 100644 index 0000000..a1fe52a --- /dev/null +++ b/platform/internal/crud/pool.go @@ -0,0 +1,50 @@ +package crud + +import ( + "database/sql" + "fmt" + "sync" + + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/schema" +) + +// DBPool 按 database_per_app 缓存连接。 +type DBPool struct { + mu sync.Mutex + baseDSN string + primary *sql.DB + dbs map[string]*sql.DB +} + +func NewDBPool(defaultDB *sql.DB, baseDSN string) *DBPool { + return &DBPool{primary: defaultDB, baseDSN: baseDSN, dbs: map[string]*sql.DB{}} +} + +func (p *DBPool) ForApp(app *meta.AppRecord) (*sql.DB, error) { + if p == nil { + return nil, fmt.Errorf("db pool nil") + } + if app == nil || app.DatabaseName == "" { + return p.primary, nil + } + p.mu.Lock() + defer p.mu.Unlock() + if db, ok := p.dbs[app.DatabaseName]; ok { + return db, nil + } + dsn, err := schema.DSNForDatabase(p.baseDSN, app.DatabaseName) + if err != nil { + return nil, err + } + db, err := sql.Open("postgres", dsn) + if err != nil { + return nil, err + } + if err := db.Ping(); err != nil { + _ = db.Close() + return nil, fmt.Errorf("ping app db %s: %w", app.DatabaseName, err) + } + p.dbs[app.DatabaseName] = db + return db, nil +} diff --git a/platform/internal/crud/postgres.go b/platform/internal/crud/postgres.go new file mode 100644 index 0000000..3a2d905 --- /dev/null +++ b/platform/internal/crud/postgres.go @@ -0,0 +1,301 @@ +package crud + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "aijianzhan/platform/internal/meta" + + "github.com/google/uuid" +) + +// PostgresEngine dynamic row CRUD with tenant_id and optional org_unit_id scope. +type PostgresEngine struct { + DB *sql.DB + Pool *DBPool +} + +func NewPostgresEngine(db *sql.DB, pool *DBPool) *PostgresEngine { + return &PostgresEngine{DB: db, Pool: pool} +} + +func (e *PostgresEngine) conn(ref *meta.ResourceRef) (*sql.DB, error) { + if e.Pool != nil && ref != nil && ref.App != nil { + return e.Pool.ForApp(ref.App) + } + return e.DB, nil +} + +func (e *PostgresEngine) List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) ([]map[string]any, int, error) { + if err := ensureOp(ref, "list"); err != nil { + return nil, 0, err + } + if err := validateFilters(ref, filters); err != nil { + return nil, 0, err + } + if err := validateSort(ref, sortBy); err != nil { + return nil, 0, err + } + page, pageSize = pageBounds(ref, page, pageSize) + db, err := e.conn(ref) + if err != nil { + return nil, 0, err + } + + where := []string{`tenant_id = $1`} + args := []any{tenantID} + argN := 2 + scope := RowScopeFrom(ctx) + where, args, argN = appendOrgFilter(where, args, argN, scope.OrgUnitIDs) + for col, val := range filters { + where = append(where, fmt.Sprintf("%s = $%d", quoteIdent(col), argN)) + args = append(args, val) + argN++ + } + whereSQL := strings.Join(where, " AND ") + table := qualifiedTable(ref) + + var total int + countSQL := fmt.Sprintf("SELECT COUNT(1) FROM %s WHERE %s", table, whereSQL) + if err := db.QueryRowContext(ctx, countSQL, args...).Scan(&total); err != nil { + return nil, 0, fmt.Errorf("count: %w", err) + } + + orderSQL := "" + if sortBy != "" { + desc := strings.HasPrefix(sortBy, "-") + field := strings.TrimPrefix(sortBy, "-") + dir := "ASC" + if desc { + dir = "DESC" + } + orderSQL = " ORDER BY " + quoteIdent(field) + " " + dir + } + + offset := (page - 1) * pageSize + listArgs := append(append([]any{}, args...), pageSize, offset) + listSQL := fmt.Sprintf( + "SELECT * FROM %s WHERE %s%s LIMIT $%d OFFSET $%d", + table, whereSQL, orderSQL, argN, argN+1, + ) + rows, err := db.QueryContext(ctx, listSQL, listArgs...) + if err != nil { + return nil, 0, fmt.Errorf("list: %w", err) + } + defer rows.Close() + + items, err := scanRows(rows) + if err != nil { + return nil, 0, err + } + return items, total, nil +} + +func (e *PostgresEngine) Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) { + if err := ensureOp(ref, "get"); err != nil { + return nil, err + } + db, err := e.conn(ref) + if err != nil { + return nil, err + } + pk := ref.Entity.PrimaryKey + where := []string{`tenant_id = $1`, fmt.Sprintf("%s = $2", quoteIdent(pk))} + args := []any{tenantID, id} + argN := 3 + scope := RowScopeFrom(ctx) + where, args, argN = appendOrgFilter(where, args, argN, scope.OrgUnitIDs) + _ = argN + q := fmt.Sprintf( + "SELECT * FROM %s WHERE %s LIMIT 1", + qualifiedTable(ref), strings.Join(where, " AND "), + ) + rows, err := db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + items, err := scanRows(rows) + if err != nil { + return nil, err + } + if len(items) == 0 { + return nil, fmt.Errorf("not found") + } + return items[0], nil +} + +func (e *PostgresEngine) Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) { + if err := ensureOp(ref, "create"); err != nil { + return nil, err + } + db, err := e.conn(ref) + if err != nil { + return nil, err + } + row, err := sanitizeBody(ref.Entity, body, false) + if err != nil { + return nil, err + } + + pk := ref.Entity.PrimaryKey + autoPK := isAutoPK(ref.Entity) + if !autoPK { + if _, ok := row[pk]; !ok { + row[pk] = uuid.NewString() + } + } else { + delete(row, pk) + } + + now := time.Now().UTC() + row["tenant_id"] = tenantID + row["created_by"] = userID + row["created_at"] = now + row["updated_at"] = now + if scope := RowScopeFrom(ctx); scope.WriteOrgUnit > 0 { + row["org_unit_id"] = scope.WriteOrgUnit + } + + cols := make([]string, 0, len(row)) + placeholders := make([]string, 0, len(row)) + args := make([]any, 0, len(row)) + i := 1 + for k, v := range row { + cols = append(cols, quoteIdent(k)) + placeholders = append(placeholders, fmt.Sprintf("$%d", i)) + args = append(args, v) + i++ + } + + q := fmt.Sprintf( + "INSERT INTO %s (%s) VALUES (%s) RETURNING *", + qualifiedTable(ref), + strings.Join(cols, ", "), + strings.Join(placeholders, ", "), + ) + rows, err := db.QueryContext(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("insert: %w", err) + } + defer rows.Close() + items, err := scanRows(rows) + if err != nil { + return nil, err + } + if len(items) == 0 { + return nil, fmt.Errorf("insert returned no row") + } + return items[0], nil +} + +func (e *PostgresEngine) Update(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) { + if err := ensureOp(ref, "update"); err != nil { + return nil, err + } + db, err := e.conn(ref) + if err != nil { + return nil, err + } + patch, err := sanitizeBody(ref.Entity, body, true) + if err != nil { + return nil, err + } + delete(patch, ref.Entity.PrimaryKey) + delete(patch, "tenant_id") + delete(patch, "org_unit_id") + if len(patch) == 0 { + return e.Get(ctx, ref, tenantID, id) + } + patch["updated_at"] = time.Now().UTC() + + sets := make([]string, 0, len(patch)) + args := make([]any, 0, len(patch)+2) + i := 1 + for k, v := range patch { + sets = append(sets, fmt.Sprintf("%s = $%d", quoteIdent(k), i)) + args = append(args, v) + i++ + } + where := []string{fmt.Sprintf("tenant_id = $%d", i), fmt.Sprintf("%s = $%d", quoteIdent(ref.Entity.PrimaryKey), i+1)} + args = append(args, tenantID, id) + argN := i + 2 + scope := RowScopeFrom(ctx) + where, args, argN = appendOrgFilter(where, args, argN, scope.OrgUnitIDs) + _ = argN + q := fmt.Sprintf( + "UPDATE %s SET %s WHERE %s RETURNING *", + qualifiedTable(ref), + strings.Join(sets, ", "), + strings.Join(where, " AND "), + ) + rows, err := db.QueryContext(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("update: %w", err) + } + defer rows.Close() + items, err := scanRows(rows) + if err != nil { + return nil, err + } + if len(items) == 0 { + return nil, fmt.Errorf("not found") + } + return items[0], nil +} + +func (e *PostgresEngine) Delete(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) error { + if err := ensureOp(ref, "delete"); err != nil { + return err + } + db, err := e.conn(ref) + if err != nil { + return err + } + where := []string{`tenant_id = $1`, fmt.Sprintf("%s = $2", quoteIdent(ref.Entity.PrimaryKey))} + args := []any{tenantID, id} + argN := 3 + scope := RowScopeFrom(ctx) + where, args, argN = appendOrgFilter(where, args, argN, scope.OrgUnitIDs) + _ = argN + q := fmt.Sprintf( + "DELETE FROM %s WHERE %s", + qualifiedTable(ref), strings.Join(where, " AND "), + ) + res, err := db.ExecContext(ctx, q, args...) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("not found") + } + return nil +} + +func scanRows(rows *sql.Rows) ([]map[string]any, error) { + cols, err := rows.Columns() + if err != nil { + return nil, err + } + out := make([]map[string]any, 0) + for rows.Next() { + vals := make([]any, len(cols)) + ptrs := make([]any, len(cols)) + for i := range vals { + ptrs[i] = &vals[i] + } + if err := rows.Scan(ptrs...); err != nil { + return nil, err + } + row := make(map[string]any, len(cols)) + for i, c := range cols { + row[c] = normalizeDBValue(vals[i]) + } + out = append(out, row) + } + return out, rows.Err() +} diff --git a/platform/internal/crud/scope.go b/platform/internal/crud/scope.go new file mode 100644 index 0000000..bfda638 --- /dev/null +++ b/platform/internal/crud/scope.go @@ -0,0 +1,58 @@ +package crud + +import ( + "context" + "fmt" + "strings" +) + +type scopeKey struct{} + +// RowScope 行级组织范围:OrgUnitIDs 非空时限制可读范围;WriteOrgUnit 写入新建行。 +type RowScope struct { + OrgUnitIDs []int64 + WriteOrgUnit int64 +} + +func WithRowScope(ctx context.Context, s RowScope) context.Context { + return context.WithValue(ctx, scopeKey{}, s) +} + +func RowScopeFrom(ctx context.Context) RowScope { + v, _ := ctx.Value(scopeKey{}).(RowScope) + return v +} + +func appendOrgFilter(where []string, args []any, argN int, orgIDs []int64) ([]string, []any, int) { + if len(orgIDs) == 0 { + return where, args, argN + } + ph := make([]string, 0, len(orgIDs)) + for _, id := range orgIDs { + ph = append(ph, fmt.Sprintf("$%d", argN)) + args = append(args, id) + argN++ + } + where = append(where, fmt.Sprintf("(org_unit_id IS NULL OR org_unit_id IN (%s))", strings.Join(ph, ","))) + return where, args, argN +} + +func matchOrgScope(row map[string]any, orgIDs []int64) bool { + if len(orgIDs) == 0 { + return true + } + v, ok := row["org_unit_id"] + if !ok || v == nil { + return true + } + oid := toInt64(v) + if oid == 0 { + return true + } + for _, id := range orgIDs { + if id == oid { + return true + } + } + return false +} diff --git a/platform/internal/dbsync/apply.go b/platform/internal/dbsync/apply.go new file mode 100644 index 0000000..23d38b3 --- /dev/null +++ b/platform/internal/dbsync/apply.go @@ -0,0 +1,292 @@ +package dbsync + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "time" +) + +func PollOutbox(ctx context.Context, db *sql.DB, driver Driver, limit int) ([]OutboxRow, error) { + if limit <= 0 { + limit = 100 + } + q := fmt.Sprintf(`SELECT id, table_name, row_pk, op, payload, version, created_at +FROM %s WHERE synced_at IS NULL ORDER BY id ASC LIMIT %d`, OutboxTable, limit) + rows, err := db.QueryContext(ctx, q) + if err != nil { + return nil, err + } + defer rows.Close() + var out []OutboxRow + for rows.Next() { + var r OutboxRow + var created any + if err := rows.Scan(&r.ID, &r.TableName, &r.RowPK, &r.Op, &r.Payload, &r.Version, &created); err != nil { + return nil, err + } + switch v := created.(type) { + case time.Time: + r.CreatedAt = v + case string: + t, _ := time.Parse("2006-01-02 15:04:05", v) + r.CreatedAt = t + case []byte: + t, _ := time.Parse("2006-01-02 15:04:05", string(v)) + r.CreatedAt = t + } + out = append(out, r) + } + return out, rows.Err() +} + +func MarkSynced(ctx context.Context, db *sql.DB, driver Driver, ids []int64) error { + if len(ids) == 0 { + return nil + } + nowExpr := "CURRENT_TIMESTAMP" + switch driver { + case DriverSQLite: + nowExpr = "datetime('now')" + case DriverMySQL: + nowExpr = "UTC_TIMESTAMP(3)" + case DriverPostgres: + nowExpr = "now()" + } + placeholders := make([]string, len(ids)) + args := make([]any, len(ids)) + for i, id := range ids { + placeholders[i] = "?" + if driver == DriverPostgres { + placeholders[i] = fmt.Sprintf("$%d", i+1) + } + args[i] = id + } + q := fmt.Sprintf(`UPDATE %s SET synced_at = %s WHERE id IN (%s)`, OutboxTable, nowExpr, strings.Join(placeholders, ",")) + _, err := db.ExecContext(ctx, q, args...) + return err +} + +func FetchRowJSON(ctx context.Context, db *sql.DB, driver Driver, table, pkCol, pkVal string) (string, map[string]any, error) { + q := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, quoteIdent(driver, table), quoteIdent(driver, pkCol)) + if driver == DriverPostgres { + q = fmt.Sprintf(`SELECT * FROM %s WHERE %s = $1 LIMIT 1`, quoteIdent(driver, table), quoteIdent(driver, pkCol)) + } + rows, err := db.QueryContext(ctx, q, pkVal) + if err != nil { + return "", nil, err + } + defer rows.Close() + cols, err := rows.Columns() + if err != nil { + return "", nil, err + } + if !rows.Next() { + return "", nil, sql.ErrNoRows + } + raw := make([]any, len(cols)) + ptrs := make([]any, len(cols)) + for i := range raw { + ptrs[i] = &raw[i] + } + if err := rows.Scan(ptrs...); err != nil { + return "", nil, err + } + m := map[string]any{} + for i, c := range cols { + m[c] = normalizeValue(raw[i]) + } + b, err := json.Marshal(m) + if err != nil { + return "", nil, err + } + return string(b), m, nil +} + +func normalizeValue(v any) any { + switch x := v.(type) { + case nil: + return nil + case []byte: + return string(x) + case time.Time: + return x.UTC().Format(time.RFC3339Nano) + default: + return x + } +} + +func GetMetaVersion(ctx context.Context, db *sql.DB, driver Driver, table, pk string) (int64, bool, error) { + q := fmt.Sprintf(`SELECT version FROM %s WHERE table_name = ? AND row_pk = ?`, MetaTable) + if driver == DriverPostgres { + q = fmt.Sprintf(`SELECT version FROM %s WHERE table_name = $1 AND row_pk = $2`, MetaTable) + } + var ver int64 + err := db.QueryRowContext(ctx, q, table, pk).Scan(&ver) + if err == sql.ErrNoRows { + return 0, false, nil + } + if err != nil { + return 0, false, err + } + return ver, true, nil +} + +func UpsertMeta(ctx context.Context, db *sql.DB, driver Driver, table, pk string, version int64) error { + now := time.Now().UTC().Format(time.RFC3339Nano) + switch driver { + case DriverSQLite: + _, err := db.ExecContext(ctx, ` +INSERT INTO `+MetaTable+`(table_name,row_pk,version,updated_at) VALUES(?,?,?,?) +ON CONFLICT(table_name,row_pk) DO UPDATE SET version=excluded.version, updated_at=excluded.updated_at`, + table, pk, version, now) + return err + case DriverMySQL: + _, err := db.ExecContext(ctx, ` +INSERT INTO `+MetaTable+`(table_name,row_pk,version,updated_at) VALUES(?,?,?,?) +ON DUPLICATE KEY UPDATE version=VALUES(version), updated_at=VALUES(updated_at)`, + table, pk, version, now) + return err + case DriverPostgres: + _, err := db.ExecContext(ctx, ` +INSERT INTO `+MetaTable+`(table_name,row_pk,version,updated_at) VALUES($1,$2,$3,$4) +ON CONFLICT(table_name,row_pk) DO UPDATE SET version=EXCLUDED.version, updated_at=EXCLUDED.updated_at`, + table, pk, version, time.Now().UTC()) + return err + default: + return fmt.Errorf("unsupported") + } +} + +func ApplyChange(ctx context.Context, db *sql.DB, driver Driver, table, pkCol, op, payload string, version int64) error { + return WithApplying(ctx, db, driver, func() error { + return applyChangeInner(ctx, db, driver, table, pkCol, op, payload, version) + }) +} + +func applyChangeInner(ctx context.Context, db *sql.DB, driver Driver, table, pkCol, op, payload string, version int64) error { + switch op { + case "delete": + var row map[string]any + _ = json.Unmarshal([]byte(payload), &row) + pkVal := "" + if row != nil { + if v, ok := row[pkCol]; ok { + pkVal = fmt.Sprint(v) + } + } + if pkVal == "" { + return fmt.Errorf("delete missing pk") + } + q := fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, quoteIdent(driver, table), quoteIdent(driver, pkCol)) + if driver == DriverPostgres { + q = fmt.Sprintf(`DELETE FROM %s WHERE %s = $1`, quoteIdent(driver, table), quoteIdent(driver, pkCol)) + } + if _, err := db.ExecContext(ctx, q, pkVal); err != nil { + return err + } + return UpsertMeta(ctx, db, driver, table, pkVal, version) + default: // upsert + var row map[string]any + if err := json.Unmarshal([]byte(payload), &row); err != nil { + return err + } + pkVal := fmt.Sprint(row[pkCol]) + if pkVal == "" || pkVal == "" { + return fmt.Errorf("upsert missing pk %s", pkCol) + } + cols := make([]string, 0, len(row)) + vals := make([]any, 0, len(row)) + for k, v := range row { + cols = append(cols, k) + vals = append(vals, v) + } + return upsertRow(ctx, db, driver, table, pkCol, cols, vals, version) + } +} + +func upsertRow(ctx context.Context, db *sql.DB, driver Driver, table, pkCol string, cols []string, vals []any, version int64) error { + qcols := make([]string, len(cols)) + ph := make([]string, len(cols)) + for i, c := range cols { + qcols[i] = quoteIdent(driver, c) + if driver == DriverPostgres { + ph[i] = fmt.Sprintf("$%d", i+1) + } else { + ph[i] = "?" + } + } + pkVal := "" + for i, c := range cols { + if c == pkCol { + pkVal = fmt.Sprint(vals[i]) + break + } + } + switch driver { + case DriverSQLite: + q := fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s) ON CONFLICT(%s) DO UPDATE SET %s`, + quoteIdent(driver, table), + strings.Join(qcols, ","), + strings.Join(ph, ","), + quoteIdent(driver, pkCol), + sqliteSetClause(cols, pkCol, driver), + ) + if _, err := db.ExecContext(ctx, q, vals...); err != nil { + return err + } + case DriverMySQL: + sets := make([]string, 0, len(cols)) + for _, c := range cols { + if c == pkCol { + continue + } + qi := quoteIdent(driver, c) + sets = append(sets, qi+"=VALUES("+qi+")") + } + if len(sets) == 0 { + sets = append(sets, quoteIdent(driver, pkCol)+"="+quoteIdent(driver, pkCol)) + } + q := fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s`, + quoteIdent(driver, table), strings.Join(qcols, ","), strings.Join(ph, ","), strings.Join(sets, ",")) + if _, err := db.ExecContext(ctx, q, vals...); err != nil { + return err + } + case DriverPostgres: + sets := make([]string, 0, len(cols)) + for _, c := range cols { + if c == pkCol { + continue + } + qi := quoteIdent(driver, c) + sets = append(sets, qi+"=EXCLUDED."+qi) + } + if len(sets) == 0 { + sets = append(sets, quoteIdent(driver, pkCol)+"="+quoteIdent(driver, pkCol)) + } + q := fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s`, + quoteIdent(driver, table), strings.Join(qcols, ","), strings.Join(ph, ","), + quoteIdent(driver, pkCol), strings.Join(sets, ",")) + if _, err := db.ExecContext(ctx, q, vals...); err != nil { + return err + } + } + return UpsertMeta(ctx, db, driver, table, pkVal, version) +} + +func sqliteSetClause(cols []string, pkCol string, driver Driver) string { + parts := make([]string, 0, len(cols)) + for _, c := range cols { + if c == pkCol { + continue + } + qi := quoteIdent(driver, c) + parts = append(parts, qi+"=excluded."+qi) + } + if len(parts) == 0 { + return quoteIdent(driver, pkCol) + "=" + quoteIdent(driver, pkCol) + } + return strings.Join(parts, ",") +} diff --git a/platform/internal/dbsync/dialect.go b/platform/internal/dbsync/dialect.go new file mode 100644 index 0000000..28b5110 --- /dev/null +++ b/platform/internal/dbsync/dialect.go @@ -0,0 +1,281 @@ +package dbsync + +import ( + "context" + "database/sql" + "fmt" + "strings" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + _ "modernc.org/sqlite" +) + +func Open(driver Driver, dsn string) (*sql.DB, error) { + var name string + switch driver { + case DriverSQLite: + name = "sqlite" + if dsn == "" { + return nil, fmt.Errorf("sqlite dsn required") + } + // modernc.org/sqlite 注册名为 sqlite + case DriverMySQL: + name = "mysql" + case DriverPostgres: + name = "postgres" + default: + return nil, fmt.Errorf("unsupported driver: %s", driver) + } + db, err := sql.Open(name, dsn) + if err != nil { + return nil, err + } + db.SetMaxOpenConns(5) + db.SetMaxIdleConns(2) + if err := db.Ping(); err != nil { + _ = db.Close() + return nil, err + } + return db, nil +} + +func quoteIdent(driver Driver, name string) string { + name = strings.ReplaceAll(name, "`", "") + name = strings.ReplaceAll(name, `"`, "") + switch driver { + case DriverMySQL: + return "`" + name + "`" + case DriverPostgres: + return `"` + name + `"` + default: + return `"` + name + `"` + } +} + +func EnsureOutbox(ctx context.Context, db *sql.DB, driver Driver) error { + var ddl string + switch driver { + case DriverSQLite: + ddl = `CREATE TABLE IF NOT EXISTS ` + OutboxTable + ` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + table_name TEXT NOT NULL, + row_pk TEXT NOT NULL, + op TEXT NOT NULL, + payload TEXT NOT NULL DEFAULT '', + version INTEGER NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + synced_at TEXT +);` + case DriverMySQL: + ddl = `CREATE TABLE IF NOT EXISTS ` + OutboxTable + ` ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(128) NOT NULL, + row_pk VARCHAR(255) NOT NULL, + op VARCHAR(16) NOT NULL, + payload LONGTEXT NOT NULL, + version BIGINT NOT NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + synced_at DATETIME(3) NULL, + INDEX idx_outbox_unsynced (synced_at, id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;` + case DriverPostgres: + ddl = `CREATE TABLE IF NOT EXISTS ` + OutboxTable + ` ( + id BIGSERIAL PRIMARY KEY, + table_name TEXT NOT NULL, + row_pk TEXT NOT NULL, + op TEXT NOT NULL, + payload TEXT NOT NULL DEFAULT '', + version BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + synced_at TIMESTAMPTZ +); +CREATE INDEX IF NOT EXISTS idx_ajz_outbox_unsynced ON ` + OutboxTable + ` (synced_at, id);` + default: + return fmt.Errorf("unsupported driver") + } + _, err := db.ExecContext(ctx, ddl) + return err +} + +func EnsureMeta(ctx context.Context, db *sql.DB, driver Driver) error { + var ddl string + switch driver { + case DriverSQLite: + ddl = `CREATE TABLE IF NOT EXISTS ` + MetaTable + ` ( + table_name TEXT NOT NULL, + row_pk TEXT NOT NULL, + version INTEGER NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (table_name, row_pk) +);` + case DriverMySQL: + ddl = `CREATE TABLE IF NOT EXISTS ` + MetaTable + ` ( + table_name VARCHAR(128) NOT NULL, + row_pk VARCHAR(255) NOT NULL, + version BIGINT NOT NULL, + updated_at DATETIME(3) NOT NULL, + PRIMARY KEY (table_name, row_pk) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;` + case DriverPostgres: + ddl = `CREATE TABLE IF NOT EXISTS ` + MetaTable + ` ( + table_name TEXT NOT NULL, + row_pk TEXT NOT NULL, + version BIGINT NOT NULL, + updated_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (table_name, row_pk) +);` + } + _, err := db.ExecContext(ctx, ddl) + return err +} + +// InstallTriggers:在业务表上挂触发器,写入 outbox(实时捕获变更)。 +func InstallTriggers(ctx context.Context, db *sql.DB, driver Driver, table, pkCol string) error { + table = strings.TrimSpace(table) + pkCol = strings.TrimSpace(pkCol) + if table == "" || pkCol == "" { + return fmt.Errorf("table and pk required") + } + switch driver { + case DriverSQLite: + return installSQLiteTriggers(ctx, db, table, pkCol) + case DriverMySQL: + return installMySQLTriggers(ctx, db, table, pkCol) + case DriverPostgres: + return installPostgresTriggers(ctx, db, table, pkCol) + default: + return fmt.Errorf("unsupported driver") + } +} + +func installSQLiteTriggers(ctx context.Context, db *sql.DB, table, pk string) error { + if err := EnsureFlagTable(ctx, db, DriverSQLite); err != nil { + return err + } + guard := `(SELECT IFNULL((SELECT v FROM ` + FlagTable + ` WHERE k='applying'),0))=0` + stmts := []string{ + fmt.Sprintf(`DROP TRIGGER IF EXISTS ajz_sync_ai_%s`, table), + fmt.Sprintf(`DROP TRIGGER IF EXISTS ajz_sync_au_%s`, table), + fmt.Sprintf(`DROP TRIGGER IF EXISTS ajz_sync_ad_%s`, table), + fmt.Sprintf(`CREATE TRIGGER ajz_sync_ai_%s AFTER INSERT ON "%s" WHEN %s BEGIN + INSERT INTO %s(table_name,row_pk,op,payload,version,created_at) + VALUES('%s', CAST(NEW."%s" AS TEXT), 'upsert', '', + CAST(strftime('%%s','now') AS INTEGER)*1000, datetime('now')); +END;`, table, table, guard, OutboxTable, table, pk), + fmt.Sprintf(`CREATE TRIGGER ajz_sync_au_%s AFTER UPDATE ON "%s" WHEN %s BEGIN + INSERT INTO %s(table_name,row_pk,op,payload,version,created_at) + VALUES('%s', CAST(NEW."%s" AS TEXT), 'upsert', '', + CAST(strftime('%%s','now') AS INTEGER)*1000, datetime('now')); +END;`, table, table, guard, OutboxTable, table, pk), + fmt.Sprintf(`CREATE TRIGGER ajz_sync_ad_%s AFTER DELETE ON "%s" WHEN %s BEGIN + INSERT INTO %s(table_name,row_pk,op,payload,version,created_at) + VALUES('%s', CAST(OLD."%s" AS TEXT), 'delete', '', + CAST(strftime('%%s','now') AS INTEGER)*1000, datetime('now')); +END;`, table, table, guard, OutboxTable, table, pk), + } + for _, s := range stmts { + if _, err := db.ExecContext(ctx, s); err != nil { + return fmt.Errorf("sqlite trigger %s: %w", table, err) + } + } + return nil +} + +func installMySQLTriggers(ctx context.Context, db *sql.DB, table, pk string) error { + drops := []string{ + fmt.Sprintf("DROP TRIGGER IF EXISTS ajz_sync_ai_%s", table), + fmt.Sprintf("DROP TRIGGER IF EXISTS ajz_sync_au_%s", table), + fmt.Sprintf("DROP TRIGGER IF EXISTS ajz_sync_ad_%s", table), + } + for _, s := range drops { + _, _ = db.ExecContext(ctx, s) + } + creates := []string{ + fmt.Sprintf(`CREATE TRIGGER ajz_sync_ai_%s AFTER INSERT ON `+"`%s`"+` FOR EACH ROW +BEGIN + IF IFNULL(@ajz_applying,0)=0 THEN + INSERT INTO %s(table_name,row_pk,op,payload,version) + VALUES('%s', CAST(NEW.`+"`%s`"+` AS CHAR), 'upsert', '', UNIX_TIMESTAMP(NOW(3))*1000); + END IF; +END`, table, table, OutboxTable, table, pk), + fmt.Sprintf(`CREATE TRIGGER ajz_sync_au_%s AFTER UPDATE ON `+"`%s`"+` FOR EACH ROW +BEGIN + IF IFNULL(@ajz_applying,0)=0 THEN + INSERT INTO %s(table_name,row_pk,op,payload,version) + VALUES('%s', CAST(NEW.`+"`%s`"+` AS CHAR), 'upsert', '', UNIX_TIMESTAMP(NOW(3))*1000); + END IF; +END`, table, table, OutboxTable, table, pk), + fmt.Sprintf(`CREATE TRIGGER ajz_sync_ad_%s AFTER DELETE ON `+"`%s`"+` FOR EACH ROW +BEGIN + IF IFNULL(@ajz_applying,0)=0 THEN + INSERT INTO %s(table_name,row_pk,op,payload,version) + VALUES('%s', CAST(OLD.`+"`%s`"+` AS CHAR), 'delete', '', UNIX_TIMESTAMP(NOW(3))*1000); + END IF; +END`, table, table, OutboxTable, table, pk), + } + for _, s := range creates { + if _, err := db.ExecContext(ctx, s); err != nil { + return fmt.Errorf("mysql trigger %s: %w", table, err) + } + } + return nil +} + +func installPostgresTriggers(ctx context.Context, db *sql.DB, table, pk string) error { + fn := fmt.Sprintf("ajz_sync_fn_%s", table) + _, err := db.ExecContext(ctx, fmt.Sprintf(` +CREATE OR REPLACE FUNCTION %s() RETURNS trigger AS $$ +BEGIN + IF COALESCE(current_setting('ajz.applying', true), '0') = '1' THEN + IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF; + END IF; + IF TG_OP = 'DELETE' THEN + INSERT INTO %s(table_name,row_pk,op,payload,version) + VALUES('%s', OLD.%s::text, 'delete', '', (EXTRACT(EPOCH FROM clock_timestamp())*1000)::bigint); + RETURN OLD; + ELSE + INSERT INTO %s(table_name,row_pk,op,payload,version) + VALUES('%s', NEW.%s::text, 'upsert', '', (EXTRACT(EPOCH FROM clock_timestamp())*1000)::bigint); + RETURN NEW; + END IF; +END; +$$ LANGUAGE plpgsql;`, fn, OutboxTable, table, quoteIdent(DriverPostgres, pk), OutboxTable, table, quoteIdent(DriverPostgres, pk))) + if err != nil { + return err + } + _, _ = db.ExecContext(ctx, fmt.Sprintf(`DROP TRIGGER IF EXISTS ajz_sync_trg_%s ON %s`, table, quoteIdent(DriverPostgres, table))) + _, err = db.ExecContext(ctx, fmt.Sprintf(` +CREATE TRIGGER ajz_sync_trg_%s +AFTER INSERT OR UPDATE OR DELETE ON %s +FOR EACH ROW EXECUTE PROCEDURE %s();`, table, quoteIdent(DriverPostgres, table), fn)) + return err +} + +func ListTables(ctx context.Context, db *sql.DB, driver Driver) ([]string, error) { + var q string + switch driver { + case DriverSQLite: + q = `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_ajz_%' ORDER BY name` + case DriverMySQL: + q = `SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name NOT LIKE '\_ajz\_%' ORDER BY table_name` + case DriverPostgres: + q = `SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename NOT LIKE '\_ajz\_%' ORDER BY tablename` + default: + return nil, fmt.Errorf("unsupported") + } + rows, err := db.QueryContext(ctx, q) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var n string + if err := rows.Scan(&n); err != nil { + return nil, err + } + out = append(out, n) + } + return out, rows.Err() +} diff --git a/platform/internal/dbsync/echo.go b/platform/internal/dbsync/echo.go new file mode 100644 index 0000000..8fe4f11 --- /dev/null +++ b/platform/internal/dbsync/echo.go @@ -0,0 +1,77 @@ +package dbsync + +import ( + "context" + "database/sql" + "fmt" + "time" +) + +const FlagTable = "_ajz_sync_flag" + +// EnsureFlagTable:SQLite 用表标记「正在应用远端变更」,避免触发器回声。 +func EnsureFlagTable(ctx context.Context, db *sql.DB, driver Driver) error { + if driver != DriverSQLite { + return nil + } + _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS `+FlagTable+` ( + k TEXT PRIMARY KEY, + v INTEGER NOT NULL DEFAULT 0 +);`) + if err != nil { + return err + } + _, _ = db.ExecContext(ctx, `INSERT OR IGNORE INTO `+FlagTable+`(k,v) VALUES('applying',0)`) + return nil +} + +// WithApplying 在写入目标库时置位,使触发器不写 outbox(防 A↔B 回声导致「多」)。 +func WithApplying(ctx context.Context, db *sql.DB, driver Driver, fn func() error) error { + switch driver { + case DriverSQLite: + if err := EnsureFlagTable(ctx, db, driver); err != nil { + return err + } + if _, err := db.ExecContext(ctx, `UPDATE `+FlagTable+` SET v=1 WHERE k='applying'`); err != nil { + return err + } + defer func() { + _, _ = db.ExecContext(context.Background(), `UPDATE `+FlagTable+` SET v=0 WHERE k='applying'`) + }() + return fn() + case DriverMySQL: + if _, err := db.ExecContext(ctx, `SET @ajz_applying = 1`); err != nil { + return err + } + defer func() { _, _ = db.ExecContext(context.Background(), `SET @ajz_applying = 0`) }() + return fn() + case DriverPostgres: + if _, err := db.ExecContext(ctx, `SELECT set_config('ajz.applying', '1', false)`); err != nil { + return err + } + defer func() { + _, _ = db.ExecContext(context.Background(), `SELECT set_config('ajz.applying', '0', false)`) + }() + return fn() + default: + return fn() + } +} + +func EnqueueOutbox(ctx context.Context, db *sql.DB, driver Driver, table, pk, op, payload string, version int64) error { + if version <= 0 { + version = time.Now().UnixMilli() + } + switch driver { + case DriverPostgres: + _, err := db.ExecContext(ctx, fmt.Sprintf(` +INSERT INTO %s(table_name,row_pk,op,payload,version) VALUES($1,$2,$3,$4,$5)`, OutboxTable), + table, pk, op, payload, version) + return err + default: + _, err := db.ExecContext(ctx, fmt.Sprintf(` +INSERT INTO %s(table_name,row_pk,op,payload,version) VALUES(?,?,?,?,?)`, OutboxTable), + table, pk, op, payload, version) + return err + } +} diff --git a/platform/internal/dbsync/manager.go b/platform/internal/dbsync/manager.go new file mode 100644 index 0000000..2a6747f --- /dev/null +++ b/platform/internal/dbsync/manager.go @@ -0,0 +1,306 @@ +package dbsync + +import ( + "context" + "database/sql" + "fmt" + "log" + "sync" + "time" +) + +type Manager struct { + store *FileStore + mu sync.Mutex + runners map[string]context.CancelFunc +} + +func NewManager(store *FileStore) *Manager { + return &Manager{store: store, runners: map[string]context.CancelFunc{}} +} + +func (m *Manager) Store() *FileStore { return m.store } + +func (m *Manager) StartAll(ctx context.Context) { + list, err := m.store.ListChannels() + if err != nil { + log.Printf("dbsync list: %v", err) + return + } + for _, ch := range list { + if ch.Enabled { + _ = m.StartChannel(ch.ID) + } + } + go func() { + <-ctx.Done() + m.StopAll() + }() +} + +func (m *Manager) StartChannel(id string) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, ok := m.runners[id]; ok { + return nil + } + ch, err := m.store.GetChannel(id) + if err != nil { + return err + } + runCtx, cancel := context.WithCancel(context.Background()) + m.runners[id] = cancel + go m.loop(runCtx, ch.ID) + _ = m.store.PatchStats(id, func(c *Channel) { + c.Enabled = true + c.LastError = "" + }) + return nil +} + +func (m *Manager) StopChannel(id string) { + m.mu.Lock() + defer m.mu.Unlock() + if cancel, ok := m.runners[id]; ok { + cancel() + delete(m.runners, id) + } + _ = m.store.PatchStats(id, func(c *Channel) { c.Enabled = false }) +} + +func (m *Manager) StopAll() { + m.mu.Lock() + defer m.mu.Unlock() + for id, cancel := range m.runners { + cancel() + delete(m.runners, id) + } +} + +func (m *Manager) loop(ctx context.Context, id string) { + ticks := 0 + for { + ch, err := m.store.GetChannel(id) + if err != nil { + return + } + iv := time.Duration(ch.PollIntervalMS) * time.Millisecond + if iv < 100*time.Millisecond { + iv = 500 * time.Millisecond + } + if err := m.tick(ctx, ch); err != nil { + _ = m.store.PatchStats(id, func(c *Channel) { c.LastError = err.Error() }) + log.Printf("dbsync channel %s: %v", id, err) + } + ticks++ + // 约每分钟主键对账一次,补漏(漏投递 / 触发器未装时的存量差) + if ticks%120 == 0 && ch.Direction == DirBidirectional { + if _, rerr := ReconcileChannel(ctx, ch); rerr != nil { + log.Printf("dbsync reconcile %s: %v", id, rerr) + } + } + select { + case <-ctx.Done(): + return + case <-time.After(iv): + } + } +} + +func (m *Manager) tick(ctx context.Context, ch *Channel) error { + local, err := Open(ch.Local.Driver, ch.Local.DSN) + if err != nil { + return fmt.Errorf("open local: %w", err) + } + defer local.Close() + remote, err := Open(ch.Remote.Driver, ch.Remote.DSN) + if err != nil { + return fmt.Errorf("open remote: %w", err) + } + defer remote.Close() + + if err := prepareEndpoint(ctx, local, ch.Local, ch.PKColumns); err != nil { + return fmt.Errorf("prepare local: %w", err) + } + if err := prepareEndpoint(ctx, remote, ch.Remote, ch.PKColumns); err != nil { + return fmt.Errorf("prepare remote: %w", err) + } + + var n int + switch ch.Direction { + case DirRemoteToLocal: + n, err = m.drain(ctx, ch, "remote", remote, ch.Remote, local, ch.Local) + case DirBidirectional: + n1, e1 := m.drain(ctx, ch, "local", local, ch.Local, remote, ch.Remote) + n2, e2 := m.drain(ctx, ch, "remote", remote, ch.Remote, local, ch.Local) + n = n1 + n2 + if e1 != nil { + err = e1 + } else { + err = e2 + } + default: // local_to_remote + n, err = m.drain(ctx, ch, "local", local, ch.Local, remote, ch.Remote) + } + now := time.Now().UTC() + _ = m.store.PatchStats(ch.ID, func(c *Channel) { + c.LastSyncAt = &now + c.Stats.LastBatch = n + if err != nil { + c.LastError = err.Error() + } else { + c.LastError = "" + } + }) + return err +} + +func prepareEndpoint(ctx context.Context, db *sql.DB, ep Endpoint, pks map[string]string) error { + if err := EnsureOutbox(ctx, db, ep.Driver); err != nil { + return err + } + if err := EnsureMeta(ctx, db, ep.Driver); err != nil { + return err + } + for _, t := range ep.Tables { + pk := "id" + if pks != nil && pks[t] != "" { + pk = pks[t] + } + if err := InstallTriggers(ctx, db, ep.Driver, t, pk); err != nil { + return err + } + } + return nil +} + +func (m *Manager) drain(ctx context.Context, ch *Channel, sourceName string, src *sql.DB, srcEp Endpoint, dst *sql.DB, dstEp Endpoint) (int, error) { + rows, err := PollOutbox(ctx, src, srcEp.Driver, 100) + if err != nil { + return 0, err + } + if len(rows) == 0 { + return 0, nil + } + var done []int64 + okCount := 0 + for _, r := range rows { + pkCol := ch.PKColumns[r.TableName] + if pkCol == "" { + pkCol = "id" + } + payload := r.Payload + if r.Op != "delete" && (payload == "" || payload == "{}") { + js, _, ferr := FetchRowJSON(ctx, src, srcEp.Driver, r.TableName, pkCol, r.RowPK) + if ferr == sql.ErrNoRows { + // 行已删,改 delete + r.Op = "delete" + payload = fmt.Sprintf(`{%q:%q}`, pkCol, r.RowPK) + } else if ferr != nil { + _ = m.store.PatchStats(ch.ID, func(c *Channel) { c.Stats.Retries++ }) + continue + } else { + payload = js + } + } + if r.Op == "delete" && payload == "" { + payload = fmt.Sprintf(`{%q:%q}`, pkCol, r.RowPK) + } + + tgtVer, has, _ := GetMetaVersion(ctx, dst, dstEp.Driver, r.TableName, r.RowPK) + // 幂等:已同步过相同版本 → 跳过(防「多」) + if has && tgtVer == r.Version { + done = append(done, r.ID) + continue + } + if has && tgtVer > r.Version { + switch ch.ConflictPolicy { + case PolicyLWWTarget: + done = append(done, r.ID) + continue + case PolicyLWWSource: + // fallthrough apply + default: + _ = m.store.AddConflict(Conflict{ + TenantID: ch.TenantID, + ChannelID: ch.ID, + Table: r.TableName, + RowPK: r.RowPK, + Op: r.Op, + Source: sourceName, + Payload: payload, + TargetVer: tgtVer, + SourceVer: r.Version, + Message: "target version newer than source", + }) + _ = m.store.PatchStats(ch.ID, func(c *Channel) { c.Stats.Conflicts++ }) + done = append(done, r.ID) + continue + } + } + + if err := ApplyChange(ctx, dst, dstEp.Driver, r.TableName, pkCol, r.Op, payload, r.Version); err != nil { + _ = m.store.PatchStats(ch.ID, func(c *Channel) { + c.Stats.Retries++ + c.LastError = err.Error() + }) + continue + } + done = append(done, r.ID) + okCount++ + _ = m.store.PatchStats(ch.ID, func(c *Channel) { + if sourceName == "local" { + c.Stats.PushedOK++ + } else { + c.Stats.PulledOK++ + } + }) + } + if err := MarkSynced(ctx, src, srcEp.Driver, done); err != nil { + return okCount, err + } + return okCount, nil +} + +// PrepareChannel 连接两端、建 outbox/触发器,供「测试/启用」调用。 +func PrepareChannel(ctx context.Context, ch *Channel) error { + for _, ep := range []Endpoint{ch.Local, ch.Remote} { + db, err := Open(ep.Driver, ep.DSN) + if err != nil { + return err + } + if err := EnsureOutbox(ctx, db, ep.Driver); err != nil { + _ = db.Close() + return err + } + if err := EnsureMeta(ctx, db, ep.Driver); err != nil { + _ = db.Close() + return err + } + for _, t := range ep.Tables { + pk := "id" + if ch.PKColumns != nil && ch.PKColumns[t] != "" { + pk = ch.PKColumns[t] + } + if err := InstallTriggers(ctx, db, ep.Driver, t, pk); err != nil { + _ = db.Close() + return fmt.Errorf("%s.%s triggers: %w", ep.Driver, t, err) + } + } + _ = db.Close() + } + return nil +} + +func TestEndpoint(ctx context.Context, ep Endpoint) TestResult { + db, err := Open(ep.Driver, ep.DSN) + if err != nil { + return TestResult{OK: false, Driver: string(ep.Driver), Message: err.Error()} + } + defer db.Close() + tables, err := ListTables(ctx, db, ep.Driver) + if err != nil { + return TestResult{OK: false, Driver: string(ep.Driver), Message: err.Error()} + } + return TestResult{OK: true, Driver: string(ep.Driver), Message: "connected", Tables: tables} +} diff --git a/platform/internal/dbsync/reconcile.go b/platform/internal/dbsync/reconcile.go new file mode 100644 index 0000000..aac642d --- /dev/null +++ b/platform/internal/dbsync/reconcile.go @@ -0,0 +1,182 @@ +package dbsync + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "time" +) + +type ReconcileReport struct { + Table string `json:"table"` + OnlyLocal []string `json:"only_local"` // 在 B 有、A 无 → 需推到线上 + OnlyRemote []string `json:"only_remote"` // 在 A 有、B 无 → 需拉到本地 + PatchedPush int `json:"patched_push"` + PatchedPull int `json:"patched_pull"` +} + +type ReconcileResult struct { + ChannelID string `json:"channel_id"` + Reports []ReconcileReport `json:"reports"` + Message string `json:"message"` +} + +// ListPKs 列出表全部主键(用于对账,防漏)。 +func ListPKs(ctx context.Context, db *sql.DB, driver Driver, table, pkCol string) ([]string, error) { + q := fmt.Sprintf(`SELECT %s FROM %s`, quoteIdent(driver, pkCol), quoteIdent(driver, table)) + rows, err := db.QueryContext(ctx, q) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var v any + if err := rows.Scan(&v); err != nil { + return nil, err + } + out = append(out, fmt.Sprint(v)) + } + return out, rows.Err() +} + +// ReconcileChannel 全量主键对账:只在一端的行补投递,保证不漏;已有行靠 outbox/版本,不重复插入。 +func ReconcileChannel(ctx context.Context, ch *Channel) (*ReconcileResult, error) { + local, err := Open(ch.Local.Driver, ch.Local.DSN) + if err != nil { + return nil, fmt.Errorf("open local: %w", err) + } + defer local.Close() + remote, err := Open(ch.Remote.Driver, ch.Remote.DSN) + if err != nil { + return nil, fmt.Errorf("open remote: %w", err) + } + defer remote.Close() + + if err := prepareEndpoint(ctx, local, ch.Local, ch.PKColumns); err != nil { + return nil, err + } + if err := prepareEndpoint(ctx, remote, ch.Remote, ch.PKColumns); err != nil { + return nil, err + } + + tables := uniqueTables(ch.Local.Tables, ch.Remote.Tables) + res := &ReconcileResult{ChannelID: ch.ID, Message: "ok"} + for _, table := range tables { + pkCol := "id" + if ch.PKColumns != nil && ch.PKColumns[table] != "" { + pkCol = ch.PKColumns[table] + } + lpks, err := ListPKs(ctx, local, ch.Local.Driver, table, pkCol) + if err != nil { + return nil, fmt.Errorf("list local %s: %w", table, err) + } + rpks, err := ListPKs(ctx, remote, ch.Remote.Driver, table, pkCol) + if err != nil { + return nil, fmt.Errorf("list remote %s: %w", table, err) + } + lset := toSet(lpks) + rset := toSet(rpks) + rep := ReconcileReport{Table: table} + for pk := range lset { + if !rset[pk] { + rep.OnlyLocal = append(rep.OnlyLocal, pk) + } + } + for pk := range rset { + if !lset[pk] { + rep.OnlyRemote = append(rep.OnlyRemote, pk) + } + } + // 补漏:缺的一端从有的一端取行并 apply(带 WithApplying,不产生回声) + for _, pk := range rep.OnlyLocal { + js, _, ferr := FetchRowJSON(ctx, local, ch.Local.Driver, table, pkCol, pk) + if ferr != nil { + continue + } + ver := time.Now().UnixMilli() + if err := ApplyChange(ctx, remote, ch.Remote.Driver, table, pkCol, "upsert", js, ver); err == nil { + rep.PatchedPush++ + _ = UpsertMeta(ctx, local, ch.Local.Driver, table, pk, ver) + } + } + for _, pk := range rep.OnlyRemote { + js, _, ferr := FetchRowJSON(ctx, remote, ch.Remote.Driver, table, pkCol, pk) + if ferr != nil { + continue + } + ver := time.Now().UnixMilli() + if err := ApplyChange(ctx, local, ch.Local.Driver, table, pkCol, "upsert", js, ver); err == nil { + rep.PatchedPull++ + _ = UpsertMeta(ctx, remote, ch.Remote.Driver, table, pk, ver) + } + } + res.Reports = append(res.Reports, rep) + } + return res, nil +} + +func uniqueTables(a, b []string) []string { + m := map[string]struct{}{} + var out []string + for _, t := range append(append([]string{}, a...), b...) { + if t == "" { + continue + } + if _, ok := m[t]; ok { + continue + } + m[t] = struct{}{} + out = append(out, t) + } + return out +} + +func toSet(xs []string) map[string]bool { + m := make(map[string]bool, len(xs)) + for _, x := range xs { + m[x] = true + } + return m +} + +// IngestRows:外部源 C → 写入本地 B(会触发 outbox,再同步到线上 A)。 +// 按主键 upsert,同一 PK 重复灌入不会「多」出一行。 +func IngestRows(ctx context.Context, ch *Channel, table string, rows []map[string]any, sourceLabel string) (int, error) { + if table == "" { + return 0, fmt.Errorf("table required") + } + pkCol := "id" + if ch.PKColumns != nil && ch.PKColumns[table] != "" { + pkCol = ch.PKColumns[table] + } + db, err := Open(ch.Local.Driver, ch.Local.DSN) + if err != nil { + return 0, err + } + defer db.Close() + if err := prepareEndpoint(ctx, db, ch.Local, ch.PKColumns); err != nil { + return 0, err + } + n := 0 + for _, row := range rows { + if row == nil { + continue + } + pkVal := fmt.Sprint(row[pkCol]) + if pkVal == "" || pkVal == "" { + return n, fmt.Errorf("row missing pk %s", pkCol) + } + // 不置 applying:让触发器写 outbox,随后 worker 推到 A + b, _ := json.Marshal(row) + ver := time.Now().UnixMilli() + // 正常写入本地:触发器入 outbox → worker 推到线上 A;同 PK upsert 不会多行 + if err := applyChangeInner(ctx, db, ch.Local.Driver, table, pkCol, "upsert", string(b), ver); err != nil { + return n, err + } + _ = sourceLabel + n++ + } + return n, nil +} diff --git a/platform/internal/dbsync/store.go b/platform/internal/dbsync/store.go new file mode 100644 index 0000000..018ba54 --- /dev/null +++ b/platform/internal/dbsync/store.go @@ -0,0 +1,327 @@ +package dbsync + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + "github.com/google/uuid" +) + +// FileStore 持久化通道与冲突队列(JSON),不依赖业务库类型。 +type FileStore struct { + mu sync.Mutex + dir string + chPath string + cfPath string +} + +func NewFileStore(dir string) (*FileStore, error) { + if dir == "" { + dir = "./data/dbsync" + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + return &FileStore{ + dir: dir, + chPath: filepath.Join(dir, "channels.json"), + cfPath: filepath.Join(dir, "conflicts.json"), + }, nil +} + +func (s *FileStore) ListChannels() ([]Channel, error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.readChannels() +} + +func (s *FileStore) ListChannelsByTenant(tenantID int64) ([]Channel, error) { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readChannels() + if err != nil { + return nil, err + } + out := make([]Channel, 0) + for _, c := range list { + if c.TenantID == tenantID { + out = append(out, c) + } + } + return out, nil +} + +func (s *FileStore) GetChannel(id string) (*Channel, error) { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readChannels() + if err != nil { + return nil, err + } + for i := range list { + if list[i].ID == id { + cp := list[i] + return &cp, nil + } + } + return nil, fmt.Errorf("channel not found") +} + +// GetChannelForTenant 仅返回属于该租户的通道。 +func (s *FileStore) GetChannelForTenant(id string, tenantID int64) (*Channel, error) { + ch, err := s.GetChannel(id) + if err != nil { + return nil, err + } + if ch.TenantID != tenantID { + return nil, fmt.Errorf("channel not found") + } + return ch, nil +} + +func (s *FileStore) SaveChannel(ch Channel) (Channel, error) { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readChannels() + if err != nil { + return ch, err + } + now := time.Now().UTC() + if ch.ID == "" { + ch.ID = uuid.NewString() + ch.CreatedAt = now + } + ch.UpdatedAt = now + if ch.PollIntervalMS <= 0 { + ch.PollIntervalMS = 500 + } + if ch.Direction == "" { + ch.Direction = DirLocalToRemote + } + if ch.ConflictPolicy == "" { + ch.ConflictPolicy = PolicyQueue + } + if ch.PKColumns == nil { + ch.PKColumns = map[string]string{} + } + found := false + for i := range list { + if list[i].ID == ch.ID { + // 禁止跨租户覆盖;更新时锁定原 tenant_id + if list[i].TenantID != 0 && ch.TenantID != 0 && list[i].TenantID != ch.TenantID { + return ch, fmt.Errorf("channel belongs to another tenant") + } + if ch.TenantID == 0 { + ch.TenantID = list[i].TenantID + } + ch.CreatedAt = list[i].CreatedAt + ch.Stats = list[i].Stats + list[i] = ch + found = true + break + } + } + if !found { + if ch.TenantID <= 0 { + return ch, fmt.Errorf("tenant_id required") + } + list = append(list, ch) + } + if err := s.writeChannels(list); err != nil { + return ch, err + } + return ch, nil +} + +func (s *FileStore) DeleteChannel(id string) error { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readChannels() + if err != nil { + return err + } + next := list[:0] + for _, c := range list { + if c.ID != id { + next = append(next, c) + } + } + return s.writeChannels(next) +} + +func (s *FileStore) DeleteChannelForTenant(id string, tenantID int64) error { + ch, err := s.GetChannelForTenant(id, tenantID) + if err != nil { + return err + } + _ = ch + return s.DeleteChannel(id) +} + +func (s *FileStore) PatchStats(id string, fn func(*Channel)) error { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readChannels() + if err != nil { + return err + } + for i := range list { + if list[i].ID == id { + fn(&list[i]) + list[i].UpdatedAt = time.Now().UTC() + return s.writeChannels(list) + } + } + return fmt.Errorf("channel not found") +} + +func (s *FileStore) AddConflict(c Conflict) error { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readConflicts() + if err != nil { + return err + } + if c.ID == "" { + c.ID = uuid.NewString() + } + if c.CreatedAt.IsZero() { + c.CreatedAt = time.Now().UTC() + } + list = append(list, c) + return s.writeConflicts(list) +} + +func (s *FileStore) ListConflicts(unresolvedOnly bool) ([]Conflict, error) { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readConflicts() + if err != nil { + return nil, err + } + if !unresolvedOnly { + return list, nil + } + out := make([]Conflict, 0) + for _, c := range list { + if !c.Resolved { + out = append(out, c) + } + } + return out, nil +} + +func (s *FileStore) ListConflictsByTenant(tenantID int64, unresolvedOnly bool) ([]Conflict, error) { + list, err := s.ListConflicts(unresolvedOnly) + if err != nil { + return nil, err + } + out := make([]Conflict, 0) + for _, c := range list { + if c.TenantID == tenantID { + out = append(out, c) + } + } + return out, nil +} + +func (s *FileStore) ResolveConflict(id, resolution string) error { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readConflicts() + if err != nil { + return err + } + for i := range list { + if list[i].ID == id { + list[i].Resolved = true + list[i].Resolution = resolution + return s.writeConflicts(list) + } + } + return fmt.Errorf("conflict not found") +} + +func (s *FileStore) ResolveConflictForTenant(id string, tenantID int64, resolution string) error { + s.mu.Lock() + defer s.mu.Unlock() + list, err := s.readConflicts() + if err != nil { + return err + } + for i := range list { + if list[i].ID == id { + if list[i].TenantID != tenantID { + return fmt.Errorf("conflict not found") + } + list[i].Resolved = true + list[i].Resolution = resolution + return s.writeConflicts(list) + } + } + return fmt.Errorf("conflict not found") +} + +func (s *FileStore) readChannels() ([]Channel, error) { + b, err := os.ReadFile(s.chPath) + if err != nil { + if os.IsNotExist(err) { + return []Channel{}, nil + } + return nil, err + } + var list []Channel + if len(b) == 0 { + return []Channel{}, nil + } + if err := json.Unmarshal(b, &list); err != nil { + return nil, err + } + return list, nil +} + +func (s *FileStore) writeChannels(list []Channel) error { + b, err := json.MarshalIndent(list, "", " ") + if err != nil { + return err + } + tmp := s.chPath + ".tmp" + if err := os.WriteFile(tmp, b, 0o600); err != nil { + return err + } + return os.Rename(tmp, s.chPath) +} + +func (s *FileStore) readConflicts() ([]Conflict, error) { + b, err := os.ReadFile(s.cfPath) + if err != nil { + if os.IsNotExist(err) { + return []Conflict{}, nil + } + return nil, err + } + var list []Conflict + if len(b) == 0 { + return []Conflict{}, nil + } + if err := json.Unmarshal(b, &list); err != nil { + return nil, err + } + return list, nil +} + +func (s *FileStore) writeConflicts(list []Conflict) error { + b, err := json.MarshalIndent(list, "", " ") + if err != nil { + return err + } + tmp := s.cfPath + ".tmp" + if err := os.WriteFile(tmp, b, 0o600); err != nil { + return err + } + return os.Rename(tmp, s.cfPath) +} diff --git a/platform/internal/dbsync/types.go b/platform/internal/dbsync/types.go new file mode 100644 index 0000000..6578b52 --- /dev/null +++ b/platform/internal/dbsync/types.go @@ -0,0 +1,98 @@ +// Package dbsync:跨库实时同步中间件(SQLite / MySQL / Postgres)。 +// 变更写入各端 _ajz_sync_outbox,由 worker 轮询投递;冲突进入队列。 +package dbsync + +import "time" + +const OutboxTable = "_ajz_sync_outbox" +const MetaTable = "_ajz_sync_meta" + +type Driver string + +const ( + DriverSQLite Driver = "sqlite" + DriverMySQL Driver = "mysql" + DriverPostgres Driver = "postgres" +) + +type Direction string + +const ( + DirLocalToRemote Direction = "local_to_remote" + DirRemoteToLocal Direction = "remote_to_local" + DirBidirectional Direction = "bidirectional" +) + +type ConflictPolicy string + +const ( + PolicyQueue ConflictPolicy = "queue" // 入冲突队列,人工处理 + PolicyLWWSource ConflictPolicy = "lww_source" // 以源端为准覆盖 + PolicyLWWTarget ConflictPolicy = "lww_target" // 保留目标端,丢弃源变更 +) + +type Endpoint struct { + Driver Driver `json:"driver"` // sqlite | mysql | postgres + DSN string `json:"dsn"` // 连接串;后台可改线上地址 + Tables []string `json:"tables"` // 要同步的表名 +} + +type Channel struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` // 公司/租户隔离;仅该公司顶级权限可配 + Name string `json:"name"` + Enabled bool `json:"enabled"` + Direction Direction `json:"direction"` + ConflictPolicy ConflictPolicy `json:"conflict_policy"` + PollIntervalMS int `json:"poll_interval_ms"` + Local Endpoint `json:"local"` + Remote Endpoint `json:"remote"` + PKColumns map[string]string `json:"pk_columns"` // table -> pk col,默认 id + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LastError string `json:"last_error,omitempty"` + LastSyncAt *time.Time `json:"last_sync_at,omitempty"` + Stats ChannelStats `json:"stats"` +} + +type ChannelStats struct { + PushedOK int64 `json:"pushed_ok"` + PulledOK int64 `json:"pulled_ok"` + Conflicts int64 `json:"conflicts"` + Retries int64 `json:"retries"` + LastBatch int `json:"last_batch"` +} + +type Conflict struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` + ChannelID string `json:"channel_id"` + Table string `json:"table"` + RowPK string `json:"row_pk"` + Op string `json:"op"` + Source string `json:"source"` // local | remote + Payload string `json:"payload"` + TargetVer int64 `json:"target_ver"` + SourceVer int64 `json:"source_ver"` + Message string `json:"message"` + CreatedAt time.Time `json:"created_at"` + Resolved bool `json:"resolved"` + Resolution string `json:"resolution,omitempty"` // apply_source | keep_target | discard +} + +type OutboxRow struct { + ID int64 + TableName string + RowPK string + Op string // upsert | delete + Payload string + Version int64 + CreatedAt time.Time +} + +type TestResult struct { + OK bool `json:"ok"` + Driver string `json:"driver"` + Message string `json:"message"` + Tables []string `json:"tables,omitempty"` +} diff --git a/platform/internal/handler/license.go b/platform/internal/handler/license.go new file mode 100644 index 0000000..f7192ed --- /dev/null +++ b/platform/internal/handler/license.go @@ -0,0 +1,196 @@ +package handler + +import ( + "crypto/subtle" + "encoding/json" + "io" + "net/http" + "strings" + "time" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/license" + "aijianzhan/platform/internal/svc" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func requireLicenseSecret(svcCtx *svc.ServiceContext, r *http.Request) error { + want := strings.TrimSpace(svcCtx.Config.License.ControlSecret) + if want == "" { + want = strings.TrimSpace(svcCtx.Config.Auth.IssueSecret) + } + if want == "" { + return errMsg("未配置 License.ControlSecret") + } + got := strings.TrimSpace(r.Header.Get("X-License-Secret")) + if got == "" { + got = strings.TrimSpace(r.URL.Query().Get("secret")) + } + if subtle.ConstantTimeCompare([]byte(got), []byte(want)) != 1 { + return errMsg("invalid license control secret") + } + return nil +} + +type errMsg string + +func (e errMsg) Error() string { return string(e) } + +func licenseStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if svcCtx.License == nil || !svcCtx.License.Enabled() { + httpx.OkJson(w, map[string]any{"enabled": false, "message": "本实例未启用授权租约"}) + return + } + httpx.OkJson(w, svcCtx.License.Status(time.Now())) + } +} + +func licenseRenewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := requireLicenseSecret(svcCtx, r); err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + if svcCtx.License == nil || !svcCtx.License.Enabled() { + authx.WriteError(w, http.StatusBadRequest, "license 未启用") + return + } + var body struct { + NotAfter string `json:"not_after"` + Note string `json:"note"` + By string `json:"by"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + na, err := license.ParseNotAfter(body.NotAfter) + if err != nil || na.IsZero() { + authx.WriteError(w, http.StatusBadRequest, "not_after 无效") + return + } + by := strings.TrimSpace(body.By) + if by == "" { + by = "remote" + } + lease, err := svcCtx.License.Renew(na, by, body.Note) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) + } +} + +func licenseExtendHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := requireLicenseSecret(svcCtx, r); err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + if svcCtx.License == nil || !svcCtx.License.Enabled() { + authx.WriteError(w, http.StatusBadRequest, "license 未启用") + return + } + var body struct { + Days int `json:"days"` + Note string `json:"note"` + By string `json:"by"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + if body.Days <= 0 { + body.Days = license.DefaultExtensionMaxDays + } + by := strings.TrimSpace(body.By) + if by == "" { + by = "remote" + } + lease, err := svcCtx.License.Extend(body.Days, by, body.Note) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) + } +} + +func licensePutLeaseHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := requireLicenseSecret(svcCtx, r); err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + if svcCtx.License == nil || !svcCtx.License.Enabled() { + authx.WriteError(w, http.StatusBadRequest, "license 未启用") + return + } + var body struct { + Customer string `json:"customer"` + NotAfter string `json:"not_after"` + ExtensionsUsed int `json:"extensions_used"` + ExtensionsMax int `json:"extensions_max"` + ExtensionMaxDays int `json:"extension_max_days"` + Note string `json:"note"` + By string `json:"by"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + na, err := license.ParseNotAfter(body.NotAfter) + if err != nil || na.IsZero() { + authx.WriteError(w, http.StatusBadRequest, "not_after 无效") + return + } + by := strings.TrimSpace(body.By) + if by == "" { + by = "watchdog" + } + lease, err := svcCtx.License.PutLease(&license.Lease{ + Customer: body.Customer, + NotAfter: na, + ExtensionsUsed: body.ExtensionsUsed, + ExtensionsMax: body.ExtensionsMax, + ExtensionMaxDays: body.ExtensionMaxDays, + Note: body.Note, + }, by) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) + } +} + +func licenseImportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := requireLicenseSecret(svcCtx, r); err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + if svcCtx.License == nil || !svcCtx.License.Enabled() { + authx.WriteError(w, http.StatusBadRequest, "license 未启用") + return + } + raw, err := io.ReadAll(r.Body) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + by := strings.TrimSpace(r.URL.Query().Get("by")) + if by == "" { + by = "offline" + } + lease, err := svcCtx.License.ImportSigned(raw, by) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) + } +} diff --git a/platform/internal/handler/openapi.yaml b/platform/internal/handler/openapi.yaml new file mode 100644 index 0000000..a253b2b --- /dev/null +++ b/platform/internal/handler/openapi.yaml @@ -0,0 +1,326 @@ +openapi: 3.0.3 +info: + title: AI建站 Platform API + version: 1.0.0 + description: | + 通用中台契约。业务行数据仅通过 apps/{slug}/{resource} CRUD 传输; + 行业字段由蓝图定义,不在此增加行业专用路由。 + 动词约定:GET 读 / POST 创建或动作 / PUT 更新 / DELETE 删除。 +servers: + - url: http://127.0.0.1:8180 +paths: + /api/v1/meta/apis: + get: + operationId: listApis + summary: API 目录 + responses: + "200": + description: OK + /api/v1/meta/openapi.yaml: + get: + operationId: getOpenAPI + summary: OpenAPI 原文 + responses: + "200": + description: YAML + /api/v1/auth/register: + post: + operationId: authRegister + summary: 注册 + responses: + "201": { description: Created } + /api/v1/auth/login: + post: + operationId: authLogin + summary: 登录 + responses: + "200": { description: OK } + /api/v1/auth/token: + post: + operationId: authToken + summary: 服务签发 JWT + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/publish: + post: + operationId: publishApp + summary: 发布蓝图 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + /api/v1/apps/{slug}/blueprint: + get: + operationId: getBlueprint + summary: 读取蓝图 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + /api/v1/apps/{slug}/agent-capsule: + get: + operationId: getAgentCapsule + summary: 智能体胶囊 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/{resource}: + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + get: + operationId: listRows + summary: 列表 + parameters: + - name: page + in: query + schema: { type: integer, minimum: 1, default: 1 } + - name: page_size + in: query + schema: { type: integer, minimum: 1, maximum: 100, default: 20 } + - name: sort + in: query + schema: { type: string } + - name: filter.* + in: query + description: 如 filter.status=在售,键须在蓝图 allowed_filters + schema: { type: string } + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/PageResult" + post: + operationId: createRow + summary: 创建 + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + + /api/v1/apps/{slug}/{resource}/{id}: + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Id" + - $ref: "#/components/parameters/Authorization" + get: + operationId: getRow + summary: 详情 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + "404": + $ref: "#/components/responses/NotFound" + put: + operationId: updateRow + summary: 更新 + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Row" + delete: + operationId: deleteRow + summary: 删除 + responses: + "204": { description: No Content } + + /api/v1/apps/{slug}/{resource}/import: + post: + operationId: importRows + summary: 导入 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/apps/{slug}/{resource}/export: + get: + operationId: exportRows + summary: 导出 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + - name: format + in: query + schema: { type: string, enum: [xlsx, csv], default: xlsx } + responses: + "200": { description: 文件流 } + + /api/v1/apps/{slug}/{resource}/aggregate: + get: + operationId: aggregateRows + summary: 聚合 + parameters: + - $ref: "#/components/parameters/Slug" + - $ref: "#/components/parameters/Resource" + - $ref: "#/components/parameters/Authorization" + - name: group_by + in: query + schema: { type: string } + - name: sum + in: query + schema: { type: string } + responses: + "200": { description: OK } + + /api/v1/audit/logs: + get: + operationId: listAuditLogs + summary: 审计日志 + parameters: + - $ref: "#/components/parameters/Authorization" + responses: + "200": { description: OK } + + /api/v1/storage: + post: + operationId: uploadObject + summary: 上传 + parameters: + - $ref: "#/components/parameters/Authorization" + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/storage/{tenant}/{day}/{name}: + get: + operationId: downloadObject + summary: 下载 + parameters: + - $ref: "#/components/parameters/Authorization" + - name: tenant + in: path + required: true + schema: { type: string } + - name: day + in: path + required: true + schema: { type: string } + - name: name + in: path + required: true + schema: { type: string } + responses: + "200": { description: 文件流 } + + /api/v1/apps/generate: + post: + operationId: generateBlueprint + summary: 生成蓝图(AI 服务,经网关 /ai 前缀) + description: 实际请求 /ai/api/v1/apps/generate;勿在 platform 重复实现。 + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + prompt: { type: string } + excel: { type: string, format: binary } + images: { type: string, format: binary } + responses: + "200": { description: OK } + + /api/v1/llm/providers: + get: + operationId: listLlmProviders + summary: LLM 厂商(AI 服务) + responses: + "200": { description: OK } + +components: + parameters: + Authorization: + name: Authorization + in: header + required: true + schema: { type: string } + description: Bearer JWT + Slug: + name: slug + in: path + required: true + schema: { type: string, pattern: "^[a-z][a-z0-9_]{1,47}$" } + Resource: + name: resource + in: path + required: true + schema: { type: string } + description: 蓝图 apis.resources.path(无前导 /);不可为保留名 + Id: + name: id + in: path + required: true + schema: { type: string } + schemas: + PageResult: + type: object + properties: + items: { type: array, items: { $ref: "#/components/schemas/Row" } } + total: { type: integer } + Row: + type: object + additionalProperties: true + responses: + NotFound: + description: Not Found + content: + application/json: + schema: + type: object + properties: + code: { type: integer } + message: { type: string } diff --git a/platform/internal/handler/openapi_serve.go b/platform/internal/handler/openapi_serve.go new file mode 100644 index 0000000..c05d487 --- /dev/null +++ b/platform/internal/handler/openapi_serve.go @@ -0,0 +1,18 @@ +package handler + +import ( + _ "embed" + "net/http" +) + +//go:embed openapi.yaml +var openapiYAML []byte + +func openapiHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/yaml; charset=utf-8") + w.Header().Set("Cache-Control", "public, max-age=60") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(openapiYAML) + } +} diff --git a/platform/internal/handler/platform.go b/platform/internal/handler/platform.go new file mode 100644 index 0000000..9f11a95 --- /dev/null +++ b/platform/internal/handler/platform.go @@ -0,0 +1,319 @@ +package handler + +import ( + "encoding/json" + "net/http" + "strconv" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/logic/applogic" + "aijianzhan/platform/internal/svc" + + "github.com/zeromicro/go-zero/rest/httpx" + "github.com/zeromicro/go-zero/rest/pathvar" +) + +func platformListTenantsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + list, err := l.ListTenants() + if err != nil { + authx.WriteError(w, http.StatusForbidden, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": list}) + } +} + +func platformCreateTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var body struct { + Name string `json:"name"` + Slug string `json:"slug"` + AdminPhone string `json:"admin_phone"` + WithAdminInvite *bool `json:"with_admin_invite"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + withInvite := false + if body.WithAdminInvite != nil { + withInvite = *body.WithAdminInvite + } + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + res, err := l.CreateTenant(body.Name, body.Slug, withInvite, body.AdminPhone) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, res) + } +} + +func platformUpdateTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var body struct { + Name string `json:"name"` + Slug string `json:"slug"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + t, err := l.UpdateTenant(id, body.Name, body.Slug) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, t) + } +} + +func platformAdminInviteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + inv, err := l.IssueAdminInvite(id) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, inv) + } +} + +func platformAdminAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var body struct { + Phone string `json:"phone"` + } + _ = json.NewDecoder(r.Body).Decode(&body) + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + acc, err := l.IssueAdminAccount(id, body.Phone) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), id, authx.UserID(r.Context()), "platform.admin_account", acc.Username) + httpx.OkJson(w, map[string]any{"admin_account": acc}) + } +} + +func platformListAdminsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + items, err := l.ListCompanyAdmins(id) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items}) + } +} + +func platformUpdateAdminHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tid, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + uid, _ := strconv.ParseInt(pathvar.Vars(r)["uid"], 10, 64) + var body struct { + ResetPassword *bool `json:"reset_password"` + Password string `json:"password"` + Phone *string `json:"phone"` + DisableUsernameLogin *bool `json:"disable_username_login"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + reset := body.ResetPassword != nil && *body.ResetPassword + if !reset && body.Phone == nil && body.DisableUsernameLogin == nil { + authx.WriteError(w, http.StatusBadRequest, "请指定重置密码、更新手机号或禁用用户名登录") + return + } + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + acc, err := l.UpdateCompanyAdmin(tid, uid, reset, body.Password, body.Phone, body.DisableUsernameLogin) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), tid, authx.UserID(r.Context()), "platform.update_admin", acc.Username) + httpx.OkJson(w, map[string]any{"admin_account": acc}) + } +} + +func platformEnterTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + resp, err := l.EnterTenant(id) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), id, resp.UserID, "platform.enter_tenant", resp.Message) + httpx.OkJson(w, resp) + } +} + +func platformExitTenantHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + resp, err := l.ExitTenant() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), 0, resp.UserID, "platform.exit_tenant", resp.Message) + httpx.OkJson(w, resp) + } +} + +func platformPermModulesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !authx.IsPlatformAdmin(authx.Role(r.Context())) { + authx.WriteError(w, http.StatusForbidden, "需要超级管理员") + return + } + httpx.OkJson(w, map[string]any{ + "modules": authx.PermModules(), + "catalog": authx.CompanyPermCatalog(), + "platform": []string{authx.Perm管理租户}, + }) + } +} + +func platformGetTenantPermsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + perms, err := l.GetTenantPerms(id) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"tenant_id": id, "permissions": perms, "total": len(authx.CompanyPermCatalog())}) + } +} + +func platformSetTenantPermsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var body struct { + Permissions []string `json:"permissions"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + l := applogic.NewPlatformLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + perms, err := l.SetTenantPerms(id, body.Permissions) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"tenant_id": id, "permissions": perms, "total": len(authx.CompanyPermCatalog())}) + } +} + +func companyEntitlementsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := applogic.NewAuthLogic(r.Context(), svcCtx) + perms, modules, err := l.CompanyEntitlements() + if err != nil { + authx.WriteError(w, http.StatusForbidden, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"permissions": perms, "modules": modules}) + } +} + +func memberListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + list, err := l.List() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + items := make([]map[string]any, 0, len(list)) + for _, u := range list { + items = append(items, map[string]any{ + "user_id": u.UserID, + "username": u.Username, + "phone": u.Phone, + "display_name": u.DisplayName, + "role": u.Role, + "org_unit_id": u.OrgUnitID, + "status": u.Status, + "created_at": u.CreatedAt, + }) + } + httpx.OkJson(w, map[string]any{"items": items}) + } +} + +func memberCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var body struct { + Password string `json:"password"` + DisplayName string `json:"display_name"` + Role string `json:"role"` + OrgUnitID int64 `json:"org_unit_id"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + u, plain, err := l.Create(body.Password, body.DisplayName, body.Role, body.OrgUnitID) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()), + "member.create", "创建成员 "+u.Username) + httpx.OkJson(w, map[string]any{ + "user_id": u.UserID, + "username": u.Username, + "display_name": u.DisplayName, + "role": u.Role, + "org_unit_id": u.OrgUnitID, + "status": u.Status, + "password": plain, + }) + } +} + +func memberUpdateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var body struct { + Role string `json:"role"` + OrgUnitID int64 `json:"org_unit_id"` + Status string `json:"status"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + l := applogic.NewMemberLogic(applogic.NewAuthLogic(r.Context(), svcCtx)) + u, err := l.Update(id, body.Role, body.OrgUnitID, body.Status) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{ + "user_id": u.UserID, + "username": u.Username, + "display_name": u.DisplayName, + "role": u.Role, + "org_unit_id": u.OrgUnitID, + "status": u.Status, + }) + } +} diff --git a/platform/internal/handler/routes.go b/platform/internal/handler/routes.go new file mode 100644 index 0000000..1a41f66 --- /dev/null +++ b/platform/internal/handler/routes.go @@ -0,0 +1,1035 @@ +package handler + +import ( + "encoding/json" + "io" + "net/http" + "strconv" + "strings" + + "aijianzhan/platform/internal/apidef" + "aijianzhan/platform/internal/audit" + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/agentcap" + "aijianzhan/platform/internal/logic/applogic" + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" + + "github.com/zeromicro/go-zero/rest" + "github.com/zeromicro/go-zero/rest/httpx" + "github.com/zeromicro/go-zero/rest/pathvar" +) + +func chain(h http.HandlerFunc, mws ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc { + for i := len(mws) - 1; i >= 0; i-- { + h = mws[i](h) + } + return h +} + +func RegisterHandlers(server *rest.Server, svcCtx *svc.ServiceContext) { + authMW := authx.Middleware(svcCtx.JWT, svcCtx.Config.DevAuth) + rl := svcCtx.Limiter.Middleware + perm := authx.RequirePermission + tenant := authx.RequireTenant() + platformAdmin := authx.RequirePlatformAdmin() + appGrant := requireAgentAppGrant(svcCtx) + + // —— 公开 —— + server.AddRoutes([]rest.Route{ + {Method: http.MethodPost, Path: "/api/v1/auth/token", Handler: rl(tokenHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/auth/agent/register", Handler: rl(agentSelfRegisterHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/auth/register", Handler: rl(registerHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/auth/login", Handler: rl(loginHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/auth/sms/send", Handler: rl(sendLoginSMSHandler(svcCtx))}, + {Method: http.MethodGet, Path: "/api/v1/meta/apis", Handler: rl(apiCatalogHandler())}, + {Method: http.MethodGet, Path: "/api/v1/meta/openapi.yaml", Handler: rl(openapiHandler())}, + // 授权租约:过期后仍可调用(中间件放行),供远端续费 / 安全狗延期 + {Method: http.MethodGet, Path: "/api/v1/license/status", Handler: rl(licenseStatusHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/license/renew", Handler: rl(licenseRenewHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/license/extend", Handler: rl(licenseExtendHandler(svcCtx))}, + {Method: http.MethodPut, Path: "/api/v1/license/lease", Handler: rl(licensePutLeaseHandler(svcCtx))}, + {Method: http.MethodPost, Path: "/api/v1/license/import", Handler: rl(licenseImportHandler(svcCtx))}, + // 已发布模块公开展示(看板预览 / 真页面截图,无需登录) + {Method: http.MethodGet, Path: "/api/v1/public/apps/:slug/blueprint", Handler: rl(publicBlueprintHandler(svcCtx))}, + {Method: http.MethodGet, Path: "/api/v1/public/apps/:slug/:resource", Handler: rl(publicListHandler(svcCtx))}, + // 按用户 ID 加密后的模块路径访问(宿主「访问地址」) + {Method: http.MethodGet, Path: "/api/v1/public/m/:token/blueprint", Handler: rl(publicModulePathBlueprintHandler(svcCtx))}, + }) + + // —— 鉴权:pending 可调用(入驻) —— + server.AddRoutes([]rest.Route{ + {Method: http.MethodPost, Path: "/api/v1/auth/invites/accept", Handler: chain(inviteAcceptHandler(svcCtx), rl, authMW)}, + {Method: http.MethodPost, Path: "/api/v1/auth/password", Handler: chain(changePasswordHandler(svcCtx), rl, authMW)}, + {Method: http.MethodGet, Path: "/api/v1/auth/me", Handler: chain(meHandler(svcCtx), rl, authMW)}, + {Method: http.MethodPut, Path: "/api/v1/auth/phone", Handler: chain(bindPhoneHandler(svcCtx), rl, authMW)}, + {Method: http.MethodPost, Path: "/api/v1/tenants", Handler: chain(tenantCreateHandler(svcCtx), rl, authMW)}, + + // —— 平台超级管理员(仅公司一级:列表/新建)—— + // —— 平台超级管理员(公司一级 + 权限额度)—— + {Method: http.MethodGet, Path: "/api/v1/platform/tenants", Handler: chain(platformListTenantsHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPost, Path: "/api/v1/platform/tenants", Handler: chain(platformCreateTenantHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPut, Path: "/api/v1/platform/tenants/:id", Handler: chain(platformUpdateTenantHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPost, Path: "/api/v1/platform/tenants/:id/enter", Handler: chain(platformEnterTenantHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPost, Path: "/api/v1/platform/exit", Handler: chain(platformExitTenantHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPost, Path: "/api/v1/platform/tenants/:id/admin-invite", Handler: chain(platformAdminInviteHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPost, Path: "/api/v1/platform/tenants/:id/admin-account", Handler: chain(platformAdminAccountHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodGet, Path: "/api/v1/platform/tenants/:id/admins", Handler: chain(platformListAdminsHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPut, Path: "/api/v1/platform/tenants/:id/admins/:uid", Handler: chain(platformUpdateAdminHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodGet, Path: "/api/v1/platform/perm-modules", Handler: chain(platformPermModulesHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodGet, Path: "/api/v1/platform/tenants/:id/permissions", Handler: chain(platformGetTenantPermsHandler(svcCtx), rl, authMW, platformAdmin)}, + {Method: http.MethodPut, Path: "/api/v1/platform/tenants/:id/permissions", Handler: chain(platformSetTenantPermsHandler(svcCtx), rl, authMW, platformAdmin)}, + }) + + // —— 鉴权:需已加入租户 —— + server.AddRoutes([]rest.Route{ + {Method: http.MethodGet, Path: "/api/v1/apps", Handler: chain(listAppsHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm读取模块))}, + {Method: http.MethodPut, Path: "/api/v1/apps/:slug/draft", Handler: chain(saveDraftHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm写入模块), appGrant)}, + {Method: http.MethodPost, Path: "/api/v1/apps/:slug/publish", Handler: chain(publishHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm发布模块), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/blueprint", Handler: chain(getBlueprintHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm读取模块), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/agent-capsule", Handler: chain(capsuleHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm读取模块), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/audit/logs", Handler: chain(auditListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm查看审计))}, + + {Method: http.MethodGet, Path: "/api/v1/admin/agents", Handler: chain(agentListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodPost, Path: "/api/v1/admin/agents", Handler: chain(agentCreateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodGet, Path: "/api/v1/admin/agents/:id", Handler: chain(agentGetHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodPut, Path: "/api/v1/admin/agents/:id", Handler: chain(agentUpdateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodPost, Path: "/api/v1/admin/agents/:id/rotate-secret", Handler: chain(agentRotateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodDelete, Path: "/api/v1/admin/agents/:id", Handler: chain(agentDeleteHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + + {Method: http.MethodGet, Path: "/api/v1/admin/roles", Handler: chain(roleListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodGet, Path: "/api/v1/admin/entitlements", Handler: chain(companyEntitlementsHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm读取模块))}, + {Method: http.MethodGet, Path: "/api/v1/admin/members", Handler: chain(memberListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + {Method: http.MethodPost, Path: "/api/v1/admin/members", Handler: chain(memberCreateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + {Method: http.MethodPut, Path: "/api/v1/admin/members/:id", Handler: chain(memberUpdateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + {Method: http.MethodPost, Path: "/api/v1/admin/roles", Handler: chain(roleCreateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodGet, Path: "/api/v1/admin/roles/:id", Handler: chain(roleGetHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodPut, Path: "/api/v1/admin/roles/:id", Handler: chain(roleUpdateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + {Method: http.MethodDelete, Path: "/api/v1/admin/roles/:id", Handler: chain(roleDeleteHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理智能体))}, + + {Method: http.MethodGet, Path: "/api/v1/admin/invites", Handler: chain(inviteListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + {Method: http.MethodPost, Path: "/api/v1/admin/invites", Handler: chain(inviteCreateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + {Method: http.MethodDelete, Path: "/api/v1/admin/invites/:id", Handler: chain(inviteRevokeHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm邀请成员))}, + + {Method: http.MethodGet, Path: "/api/v1/admin/org-units", Handler: chain(orgUnitListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理组织))}, + {Method: http.MethodPost, Path: "/api/v1/admin/org-units", Handler: chain(orgUnitCreateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理组织))}, + {Method: http.MethodPut, Path: "/api/v1/admin/org-units/:id", Handler: chain(orgUnitUpdateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理组织))}, + {Method: http.MethodDelete, Path: "/api/v1/admin/org-units/:id", Handler: chain(orgUnitDeleteHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm管理组织))}, + + // 跨库同步中间件:本地 SQLite ↔ 线上 MySQL/Postgres,后台可配线上地址 + {Method: http.MethodGet, Path: "/api/v1/admin/sync/channels", Handler: chain(syncListHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels", Handler: chain(syncSaveHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodGet, Path: "/api/v1/admin/sync/channels/:id", Handler: chain(syncGetHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPut, Path: "/api/v1/admin/sync/channels/:id", Handler: chain(syncSaveHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodDelete, Path: "/api/v1/admin/sync/channels/:id", Handler: chain(syncDeleteHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/test", Handler: chain(syncTestHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/prepare", Handler: chain(syncPrepareHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/start", Handler: chain(syncStartHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/stop", Handler: chain(syncStopHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodGet, Path: "/api/v1/admin/sync/conflicts", Handler: chain(syncConflictsHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/conflicts/:id/resolve", Handler: chain(syncResolveConflictHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/reconcile", Handler: chain(syncReconcileHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + {Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/ingest", Handler: chain(syncIngestHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))}, + + // 存储:POST 创建对象,GET 读取(无 /upload 动词路径;旧路径保留别名防断裂) + {Method: http.MethodPost, Path: "/api/v1/storage", Handler: chain(uploadHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm上传文件))}, + {Method: http.MethodPost, Path: "/api/v1/storage/upload", Handler: chain(uploadHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm上传文件))}, + {Method: http.MethodGet, Path: "/api/v1/storage/:1/:2/:3", Handler: chain(downloadHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm下载文件))}, + + // 动态 CRUD:仅通用行数据传输 + {Method: http.MethodPost, Path: "/api/v1/apps/:slug/:resource/import", Handler: chain(importHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm导入数据), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/:resource/export", Handler: chain(exportHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm导出数据), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/:resource/aggregate", Handler: chain(aggregateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm查询数据), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/:resource", Handler: chain(listHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm查询数据), appGrant)}, + {Method: http.MethodPost, Path: "/api/v1/apps/:slug/:resource", Handler: chain(createHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm新增数据), appGrant)}, + {Method: http.MethodGet, Path: "/api/v1/apps/:slug/:resource/:id", Handler: chain(getHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm查询数据), appGrant)}, + {Method: http.MethodPut, Path: "/api/v1/apps/:slug/:resource/:id", Handler: chain(updateHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm更新数据), appGrant)}, + {Method: http.MethodDelete, Path: "/api/v1/apps/:slug/:resource/:id", Handler: chain(deleteHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm删除数据), appGrant)}, + }) +} + +func requireAgentAppGrant(svcCtx *svc.ServiceContext) func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if authx.Role(r.Context()) != authx.RoleAgent { + next(w, r) + return + } + vars := pathvar.Vars(r) + slug := vars["slug"] + if slug == "" { + next(w, r) + return + } + if svcCtx.Agents == nil { + authx.WriteError(w, http.StatusForbidden, "agent store unavailable") + return + } + ok, err := svcCtx.Agents.HasAppAccess(r.Context(), authx.AgentID(r.Context()), slug) + if err != nil { + authx.WriteError(w, http.StatusForbidden, err.Error()) + return + } + if ok { + next(w, r) + return + } + // 新建:目标模块尚不存在时,允许已启用智能体直接 publish/draft(发布成功后自动写入 app_slugs) + if svcCtx.Meta != nil && (r.Method == http.MethodPost || r.Method == http.MethodPut) { + existing, gerr := svcCtx.Meta.GetBySlug(r.Context(), authx.TenantID(r.Context()), slug) + if gerr != nil || existing == nil { + next(w, r) + return + } + } + authx.WriteError(w, http.StatusForbidden, "app not granted to agent: "+slug) + } + } +} + +func agentListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + items, err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).List() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items}) + } +} + +func agentGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + acc, err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).Get(id) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + httpx.OkJson(w, acc) + } +} + +func agentCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentCreateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).Create(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func agentUpdateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var req types.AgentUpdateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + acc, err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).Update(id, &req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, acc) + } +} + +func agentRotateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + resp, err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).Rotate(id) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func agentDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + if err := applogic.NewAgentAdminLogic(r.Context(), svcCtx).Delete(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true}) + } +} + +func roleListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + items, err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).List() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items}) + } +} + +func roleGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + role, err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).Get(id) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + httpx.OkJson(w, role) + } +} + +func roleCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.RoleCreateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + role, err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).Create(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, role) + } +} + +func roleUpdateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var req types.RoleUpdateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + role, err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).Update(id, &req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, role) + } +} + +func roleDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + if err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).Delete(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true}) + } +} + +func apiCatalogHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + httpx.OkJson(w, map[string]any{ + "platform": apidef.Catalog, + "ai": apidef.AICatalog, + "verbs": []string{"GET", "POST", "PUT", "DELETE"}, + "note": "业务数据只走 apps/{slug}/{resource} CRUD;行业字段由蓝图定义,不在此增删路由。", + }) + } +} + +func tokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.TokenReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).IssueToken(&req) + if err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func agentSelfRegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.AgentSelfRegisterReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).SelfRegisterAgent(&req) + if err != nil { + msg := err.Error() + code := http.StatusBadRequest + if strings.Contains(msg, "invalid register secret") { + code = http.StatusUnauthorized + } + if strings.Contains(msg, "already registered") { + code = http.StatusConflict + } + authx.WriteError(w, code, msg) + return + } + httpx.OkJson(w, resp) + } +} + +func registerHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.RegisterReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).Register(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), resp.TenantID, resp.UserID, "auth.register", req.Username) + httpx.WriteJson(w, http.StatusCreated, resp) + } +} + +func loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.LoginReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).Login(&req) + if err != nil { + msg := err.Error() + code := http.StatusUnauthorized + if strings.Contains(msg, "请填写") || strings.Contains(msg, "格式") || strings.Contains(msg, "未开启") || strings.Contains(msg, "未启用") { + code = http.StatusBadRequest + } + authx.WriteError(w, code, msg) + return + } + label := req.Username + if label == "" { + label = req.Phone + } + _ = svcCtx.Audit.Log(r.Context(), resp.TenantID, resp.UserID, "auth.login", label) + httpx.OkJson(w, resp) + } +} + +func sendLoginSMSHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.SMSSendReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + res, err := applogic.NewAuthLogic(r.Context(), svcCtx).SendLoginSMS(req.Phone) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, types.SMSSendResp{ + OK: true, + ExpiresIn: res.ExpiresIn, + RetryAfter: res.RetryAfter, + Message: res.Message, + DebugCode: res.DebugCode, + }) + } +} + +func changePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var body struct { + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + if err := applogic.NewAuthLogic(r.Context(), svcCtx).ChangePassword(body.OldPassword, body.NewPassword); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()), "auth.change_password", "ok") + httpx.OkJson(w, map[string]any{"ok": true}) + } +} + +func meHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + u, err := applogic.NewAuthLogic(r.Context(), svcCtx).Me() + if err != nil { + authx.WriteError(w, http.StatusUnauthorized, err.Error()) + return + } + httpx.OkJson(w, map[string]any{ + "user_id": u.UserID, + "username": u.Username, + "phone": u.Phone, + "username_login_disabled": u.UsernameLoginDisabled, + "display_name": u.DisplayName, + "role": u.Role, + "tenant_id": u.TenantID, + "org_unit_id": u.OrgUnitID, + "status": u.Status, + "policy": map[string]any{ + "phone_login_only": applogic.NewAuthLogic(r.Context(), svcCtx).PhoneLoginOnlyPolicy(), + "license_enabled": svcCtx.Config.License.Enabled, + "disable_username_login_if_phone_bound": svcCtx.Config.Auth.DisableUsernameLoginIfPhoneBound, + "require_phone_bound": svcCtx.Config.Auth.RequirePhoneBound, + }, + }) + } +} + +func bindPhoneHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var body struct { + Phone string `json:"phone"` + DisableUsernameLogin *bool `json:"disable_username_login"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + l := applogic.NewAuthLogic(r.Context(), svcCtx) + u, err := l.BindPhone(body.Phone) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + if body.DisableUsernameLogin != nil { + u, err = l.SetUsernameLoginDisabled(*body.DisableUsernameLogin) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + } + _ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()), + "auth.bind_phone", u.Phone) + httpx.OkJson(w, map[string]any{ + "user_id": u.UserID, + "username": u.Username, + "phone": u.Phone, + "username_login_disabled": u.UsernameLoginDisabled, + "display_name": u.DisplayName, + }) + } +} + +func inviteAcceptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.InviteAcceptReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).AcceptInvite(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), resp.TenantID, resp.UserID, "auth.invite.accept", req.Code) + httpx.OkJson(w, resp) + } +} + +func tenantCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.TenantCreateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).CreateTenant(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), resp.TenantID, resp.UserID, "tenant.create", req.Name) + httpx.WriteJson(w, http.StatusCreated, resp) + } +} + +func inviteListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + items, err := applogic.NewInviteAdminLogic(r.Context(), svcCtx).List() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items}) + } +} + +func inviteCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.InviteCreateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + inv, err := applogic.NewInviteAdminLogic(r.Context(), svcCtx).Create(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.WriteJson(w, http.StatusCreated, inv) + } +} + +func inviteRevokeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + if err := applogic.NewInviteAdminLogic(r.Context(), svcCtx).Revoke(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]string{"status": "ok"}) + } +} + +func orgUnitListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + items, err := applogic.NewOrgUnitLogic(r.Context(), svcCtx).List() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items, "max_depth": 5}) + } +} + +func orgUnitCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.OrgUnitCreateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + ou, err := applogic.NewOrgUnitLogic(r.Context(), svcCtx).Create(&req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.WriteJson(w, http.StatusCreated, ou) + } +} + +func orgUnitUpdateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + var req types.OrgUnitUpdateReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + ou, err := applogic.NewOrgUnitLogic(r.Context(), svcCtx).Update(id, &req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, ou) + } +} + +func orgUnitDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.ParseInt(pathvar.Vars(r)["id"], 10, 64) + if err := applogic.NewOrgUnitLogic(r.Context(), svcCtx).Delete(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]string{"status": "ok"}) + } +} + +func auditListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + size, _ := strconv.Atoi(r.URL.Query().Get("page_size")) + if page <= 0 { + page = 1 + } + if size <= 0 { + size = 50 + } + items, total, err := svcCtx.Audit.List(r.Context(), authx.TenantID(r.Context()), size, (page-1)*size) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": items, "total": total, "page": page, "page_size": size}) + } +} + +func uploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + file, hdr, err := r.FormFile("file") + if err != nil { + authx.WriteError(w, http.StatusBadRequest, "file required") + return + } + defer file.Close() + ct := hdr.Header.Get("Content-Type") + if ct == "" { + ct = "application/octet-stream" + } + meta, err := svcCtx.Objects.Put(r.Context(), authx.TenantID(r.Context()), hdr.Filename, ct, file, hdr.Size) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + _ = svcCtx.Audit.Log(r.Context(), authx.TenantID(r.Context()), authx.UserID(r.Context()), "storage.upload", audit.DetailJSON(meta)) + httpx.OkJson(w, types.UploadResp{ + Key: meta.Key, URL: meta.URL, Filename: meta.Filename, ContentType: meta.ContentType, Size: meta.Size, + }) + } +} + +func downloadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // path: /api/v1/storage/t1/20260101/xxx.png → key from URL after /storage/ + idx := strings.Index(r.URL.Path, "/storage/") + if idx < 0 { + authx.WriteError(w, http.StatusNotFound, "not found") + return + } + key := r.URL.Path[idx+len("/storage/"):] + rc, meta, err := svcCtx.Objects.Open(r.Context(), key) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + defer rc.Close() + if meta.ContentType != "" { + w.Header().Set("Content-Type", meta.ContentType) + } + if meta.Filename != "" { + w.Header().Set("Content-Disposition", "inline; filename="+meta.Filename) + } + _, _ = io.Copy(w, rc) + } +} + +func capsuleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + slug := pathvar.Vars(r)["slug"] + resp, err := applogic.NewCapsuleLogic(r.Context(), svcCtx).Build(slug) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func importHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + if err := r.ParseMultipartForm(8 << 20); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + file, hdr, err := r.FormFile("file") + if err != nil { + authx.WriteError(w, http.StatusBadRequest, "file required") + return + } + defer file.Close() + filename := "import.xlsx" + if hdr != nil && hdr.Filename != "" { + filename = hdr.Filename + } + resp, err := applogic.NewCrudLogic(r.Context(), svcCtx).ImportRows(vars["slug"], vars["resource"], filename, file) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func exportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + format := strings.ToLower(r.URL.Query().Get("format")) + if format == "csv" { + raw, err := applogic.NewCrudLogic(r.Context(), svcCtx).ExportCSV(vars["slug"], vars["resource"]) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + w.Header().Set("Content-Type", "text/csv; charset=utf-8") + w.Header().Set("Content-Disposition", "attachment; filename="+vars["resource"]+".csv") + _, _ = w.Write(raw) + return + } + raw, name, err := applogic.NewCrudLogic(r.Context(), svcCtx).ExportExcel(vars["slug"], vars["resource"]) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + w.Header().Set("Content-Disposition", "attachment; filename="+name) + _, _ = w.Write(raw) + } +} + +func aggregateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + q := r.URL.Query() + resp, err := applogic.NewCrudLogic(r.Context(), svcCtx).Aggregate( + vars["slug"], vars["resource"], q.Get("group_by"), q.Get("sum"), + ) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func listAppsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + resp, err := applogic.NewPublishLogic(r.Context(), svcCtx).ListApps() + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func saveDraftHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + slug := pathvar.Vars(r)["slug"] + var req types.DraftReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewPublishLogic(r.Context(), svcCtx).SaveDraft(slug, &req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func publishHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + slug := pathvar.Vars(r)["slug"] + var req types.PublishReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + resp, err := applogic.NewPublishLogic(r.Context(), svcCtx).Publish(slug, &req) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func getBlueprintHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + slug := pathvar.Vars(r)["slug"] + bp, err := applogic.NewCrudLogic(r.Context(), svcCtx).GetBlueprint(slug) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + httpx.OkJson(w, bp) + } +} + +func publicBlueprintHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + slug := pathvar.Vars(r)["slug"] + app, err := svcCtx.Meta.FindPublishedBySlug(r.Context(), slug) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + if app.Blueprint == nil { + authx.WriteError(w, http.StatusNotFound, "blueprint missing") + return + } + httpx.OkJson(w, app.Blueprint) + } +} + +func publicModulePathBlueprintHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := pathvar.Vars(r)["token"] + secret := svcCtx.Config.Agent.CapsuleSecret + if secret == "" { + secret = svcCtx.JWT.AccessSecret + } + claim, err := agentcap.OpenModulePath(secret, token) + if err != nil { + authx.WriteError(w, http.StatusNotFound, "invalid access path") + return + } + app, err := svcCtx.Meta.GetBySlug(r.Context(), claim.TenantID, claim.Slug) + if err != nil || app == nil || app.Blueprint == nil || app.Status != meta.StatusPublished { + authx.WriteError(w, http.StatusNotFound, "module not found") + return + } + httpx.OkJson(w, app.Blueprint) + } +} + +func publicListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + slug, resource := vars["slug"], vars["resource"] + if apidef.IsReservedResource(resource) { + authx.WriteError(w, http.StatusNotFound, "not found") + return + } + app, err := svcCtx.Meta.FindPublishedBySlug(r.Context(), slug) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + q := r.URL.Query() + page, size := applogic.ParsePage(q.Get("page"), q.Get("page_size")) + filters := map[string]string{} + for k, vs := range q { + if strings.HasPrefix(k, "filter.") && len(vs) > 0 { + filters[strings.TrimPrefix(k, "filter.")] = vs[0] + } + } + // 以应用所属租户读取数据,无需登录态 + ctx := authx.WithClaims(r.Context(), app.TenantID, 0, authx.Role只读) + resp, err := applogic.NewCrudLogic(ctx, svcCtx).List(slug, resource, page, size, filters, q.Get("sort")) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func listHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + slug, resource := vars["slug"], vars["resource"] + if apidef.IsReservedResource(resource) { + authx.WriteError(w, http.StatusNotFound, "not found") + return + } + q := r.URL.Query() + page, size := applogic.ParsePage(q.Get("page"), q.Get("page_size")) + filters := map[string]string{} + for k, vs := range q { + if strings.HasPrefix(k, "filter.") && len(vs) > 0 { + filters[strings.TrimPrefix(k, "filter.")] = vs[0] + } + } + resp, err := applogic.NewCrudLogic(r.Context(), svcCtx).List(slug, resource, page, size, filters, q.Get("sort")) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, resp) + } +} + +func createHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + if apidef.IsReservedResource(vars["resource"]) { + authx.WriteError(w, http.StatusBadRequest, "reserved resource name") + return + } + body, err := readJSONObject(r) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + row, err := applogic.NewCrudLogic(r.Context(), svcCtx).Create(vars["slug"], vars["resource"], body) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.WriteJson(w, http.StatusCreated, row) + } +} + +func getHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + row, err := applogic.NewCrudLogic(r.Context(), svcCtx).Get(vars["slug"], vars["resource"], vars["id"]) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + httpx.OkJson(w, row) + } +} + +func updateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + body, err := readJSONObject(r) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + row, err := applogic.NewCrudLogic(r.Context(), svcCtx).Update(vars["slug"], vars["resource"], vars["id"], body) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, row) + } +} + +func deleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := pathvar.Vars(r) + if err := applogic.NewCrudLogic(r.Context(), svcCtx).Delete(vars["slug"], vars["resource"], vars["id"]); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + } +} + +func readJSONObject(r *http.Request) (map[string]any, error) { + defer r.Body.Close() + raw, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) + if err != nil { + return nil, err + } + var body map[string]any + if err := json.Unmarshal(raw, &body); err != nil { + return nil, err + } + return body, nil +} diff --git a/platform/internal/handler/sync.go b/platform/internal/handler/sync.go new file mode 100644 index 0000000..b7ab08c --- /dev/null +++ b/platform/internal/handler/sync.go @@ -0,0 +1,271 @@ +package handler + +import ( + "encoding/json" + "net/http" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/dbsync" + "aijianzhan/platform/internal/svc" + + "github.com/zeromicro/go-zero/rest/httpx" + "github.com/zeromicro/go-zero/rest/pathvar" +) + +func requireDBSync(svcCtx *svc.ServiceContext, w http.ResponseWriter) bool { + if svcCtx.DBSync == nil { + authx.WriteError(w, http.StatusServiceUnavailable, "dbsync not enabled") + return false + } + return true +} + +// sync 仅公司顶级权限(管理员 /「数据同步」);智能体与编辑不可配。 +func syncTenantID(r *http.Request) int64 { + return authx.TenantID(r.Context()) +} + +func syncListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + list, err := svcCtx.DBSync.Store().ListChannelsByTenant(syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": list}) + } +} + +func syncGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + httpx.OkJson(w, ch) + } +} + +func syncSaveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + var ch dbsync.Channel + if err := json.NewDecoder(r.Body).Decode(&ch); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + if id := pathvar.Vars(r)["id"]; id != "" { + ch.ID = id + existing, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + _ = existing + } + // 强制归属当前公司,禁止客户端伪造 tenant_id + ch.TenantID = syncTenantID(r) + saved, err := svcCtx.DBSync.Store().SaveChannel(ch) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, saved) + } +} + +func syncDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + id := pathvar.Vars(r)["id"] + if _, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)); err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + svcCtx.DBSync.StopChannel(id) + if err := svcCtx.DBSync.Store().DeleteChannel(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true}) + } +} + +func syncTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + var body struct { + Local *dbsync.Endpoint `json:"local"` + Remote *dbsync.Endpoint `json:"remote"` + Side string `json:"side"` // local|remote|both + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + out := map[string]any{} + side := body.Side + if side == "" { + side = "both" + } + if (side == "local" || side == "both") && body.Local != nil { + out["local"] = dbsync.TestEndpoint(r.Context(), *body.Local) + } + if (side == "remote" || side == "both") && body.Remote != nil { + out["remote"] = dbsync.TestEndpoint(r.Context(), *body.Remote) + } + httpx.OkJson(w, out) + } +} + +func syncPrepareHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + if err := dbsync.PrepareChannel(r.Context(), ch); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "message": "outbox + triggers ready"}) + } +} + +func syncStartHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + id := pathvar.Vars(r)["id"] + ch, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + if err := dbsync.PrepareChannel(r.Context(), ch); err != nil { + authx.WriteError(w, http.StatusBadRequest, "prepare: "+err.Error()) + return + } + if err := svcCtx.DBSync.StartChannel(id); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "running": true}) + } +} + +func syncStopHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + id := pathvar.Vars(r)["id"] + if _, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)); err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + svcCtx.DBSync.StopChannel(id) + httpx.OkJson(w, map[string]any{"ok": true, "running": false}) + } +} + +func syncConflictsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + only := r.URL.Query().Get("unresolved") != "0" + list, err := svcCtx.DBSync.Store().ListConflictsByTenant(syncTenantID(r), only) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"items": list}) + } +} + +func syncResolveConflictHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + var body struct { + Resolution string `json:"resolution"` // apply_source | keep_target | discard + } + _ = json.NewDecoder(r.Body).Decode(&body) + if body.Resolution == "" { + body.Resolution = "discard" + } + id := pathvar.Vars(r)["id"] + if err := svcCtx.DBSync.Store().ResolveConflictForTenant(id, syncTenantID(r), body.Resolution); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "resolution": body.Resolution}) + } +} + +func syncReconcileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + res, err := dbsync.ReconcileChannel(r.Context(), ch) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, res) + } +} + +func syncIngestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !requireDBSync(svcCtx, w) { + return + } + ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r)) + if err != nil { + authx.WriteError(w, http.StatusNotFound, err.Error()) + return + } + var body struct { + Table string `json:"table"` + Source string `json:"source"` // 如 c / excel / api + Rows []map[string]any `json:"rows"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + n, err := dbsync.IngestRows(r.Context(), ch, body.Table, body.Rows, body.Source) + if err != nil { + authx.WriteError(w, http.StatusBadRequest, err.Error()) + return + } + httpx.OkJson(w, map[string]any{"ok": true, "ingested": n, "hint": "已写入本地并进入 outbox,将同步到线上"}) + } +} diff --git a/platform/internal/invitestore/store.go b/platform/internal/invitestore/store.go new file mode 100644 index 0000000..8ca500f --- /dev/null +++ b/platform/internal/invitestore/store.go @@ -0,0 +1,297 @@ +package invitestore + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "errors" + "fmt" + "strings" + "sync" + "time" + + "aijianzhan/platform/internal/authx" +) + +type Invite struct { + InviteID int64 `json:"invite_id"` + TenantID int64 `json:"tenant_id"` + Code string `json:"code"` + Role string `json:"role"` + OrgUnitID int64 `json:"org_unit_id,omitempty"` + CreatedBy int64 `json:"created_by"` + MaxUses int `json:"max_uses"` + UsedCount int `json:"used_count"` + Status string `json:"status"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +type CreateInput struct { + Role string + OrgUnitID int64 + MaxUses int + ExpiresIn time.Duration // 0 = 不过期 +} + +type Store interface { + Create(ctx context.Context, tenantID, createdBy int64, in CreateInput) (*Invite, error) + List(ctx context.Context, tenantID int64) ([]Invite, error) + GetByCode(ctx context.Context, code string) (*Invite, error) + Consume(ctx context.Context, inviteID int64) error + Revoke(ctx context.Context, tenantID, inviteID int64) error +} + +type MemoryStore struct { + mu sync.Mutex + byID map[int64]*Invite + code map[string]int64 + seq int64 +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{byID: map[int64]*Invite{}, code: map[string]int64{}} +} + +func (s *MemoryStore) Create(_ context.Context, tenantID, createdBy int64, in CreateInput) (*Invite, error) { + s.mu.Lock() + defer s.mu.Unlock() + code, err := randomCode() + if err != nil { + return nil, err + } + s.seq++ + inv := &Invite{ + InviteID: s.seq, + TenantID: tenantID, + Code: code, + Role: normalizeRole(in.Role), + OrgUnitID: in.OrgUnitID, + CreatedBy: createdBy, + MaxUses: maxUses(in.MaxUses), + Status: "active", + CreatedAt: time.Now().UTC(), + } + if in.ExpiresIn > 0 { + t := time.Now().UTC().Add(in.ExpiresIn) + inv.ExpiresAt = &t + } + s.byID[inv.InviteID] = inv + s.code[inv.Code] = inv.InviteID + cp := *inv + return &cp, nil +} + +func (s *MemoryStore) List(_ context.Context, tenantID int64) ([]Invite, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := make([]Invite, 0) + for _, inv := range s.byID { + if inv.TenantID == tenantID { + out = append(out, *inv) + } + } + return out, nil +} + +func (s *MemoryStore) GetByCode(_ context.Context, code string) (*Invite, error) { + s.mu.Lock() + defer s.mu.Unlock() + id, ok := s.code[strings.TrimSpace(code)] + if !ok { + return nil, fmt.Errorf("invite not found") + } + inv := s.byID[id] + cp := *inv + return &cp, nil +} + +func (s *MemoryStore) Consume(_ context.Context, inviteID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + inv, ok := s.byID[inviteID] + if !ok { + return fmt.Errorf("invite not found") + } + if err := usable(inv); err != nil { + return err + } + inv.UsedCount++ + if inv.UsedCount >= inv.MaxUses { + inv.Status = "exhausted" + } + return nil +} + +func (s *MemoryStore) Revoke(_ context.Context, tenantID, inviteID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + inv, ok := s.byID[inviteID] + if !ok || inv.TenantID != tenantID { + return fmt.Errorf("invite not found") + } + inv.Status = "revoked" + return nil +} + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { + return &PostgresStore{DB: db} +} + +func (s *PostgresStore) Create(ctx context.Context, tenantID, createdBy int64, in CreateInput) (*Invite, error) { + code, err := randomCode() + if err != nil { + return nil, err + } + var expires any + var expPtr *time.Time + if in.ExpiresIn > 0 { + t := time.Now().UTC().Add(in.ExpiresIn) + expPtr = &t + expires = t + } + inv := &Invite{ + TenantID: tenantID, + Code: code, + Role: normalizeRole(in.Role), + OrgUnitID: in.OrgUnitID, + CreatedBy: createdBy, + MaxUses: maxUses(in.MaxUses), + Status: "active", + ExpiresAt: expPtr, + } + err = s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.tenant_invites(tenant_id, code, role, created_by, max_uses, expires_at, org_unit_id) +VALUES($1,$2,$3,$4,$5,$6,$7) +RETURNING invite_id, created_at`, + tenantID, code, inv.Role, createdBy, inv.MaxUses, expires, in.OrgUnitID, + ).Scan(&inv.InviteID, &inv.CreatedAt) + return inv, err +} + +func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]Invite, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT invite_id, tenant_id, code, role, created_by, max_uses, used_count, status, expires_at, created_at, COALESCE(org_unit_id,0) +FROM platform_meta.tenant_invites WHERE tenant_id=$1 ORDER BY invite_id DESC`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + out := make([]Invite, 0) + for rows.Next() { + var inv Invite + var exp sql.NullTime + if err := rows.Scan(&inv.InviteID, &inv.TenantID, &inv.Code, &inv.Role, &inv.CreatedBy, + &inv.MaxUses, &inv.UsedCount, &inv.Status, &exp, &inv.CreatedAt, &inv.OrgUnitID); err != nil { + return nil, err + } + if exp.Valid { + t := exp.Time.UTC() + inv.ExpiresAt = &t + } + out = append(out, inv) + } + return out, rows.Err() +} + +func (s *PostgresStore) GetByCode(ctx context.Context, code string) (*Invite, error) { + var inv Invite + var exp sql.NullTime + err := s.DB.QueryRowContext(ctx, ` +SELECT invite_id, tenant_id, code, role, created_by, max_uses, used_count, status, expires_at, created_at, COALESCE(org_unit_id,0) +FROM platform_meta.tenant_invites WHERE code=$1`, strings.TrimSpace(code), + ).Scan(&inv.InviteID, &inv.TenantID, &inv.Code, &inv.Role, &inv.CreatedBy, + &inv.MaxUses, &inv.UsedCount, &inv.Status, &exp, &inv.CreatedAt, &inv.OrgUnitID) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("invite not found") + } + if err != nil { + return nil, err + } + if exp.Valid { + t := exp.Time.UTC() + inv.ExpiresAt = &t + } + return &inv, nil +} + +func (s *PostgresStore) Consume(ctx context.Context, inviteID int64) error { + res, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.tenant_invites +SET used_count = used_count + 1, + status = CASE WHEN used_count + 1 >= max_uses THEN 'exhausted' ELSE status END +WHERE invite_id=$1 AND status='active' + AND (expires_at IS NULL OR expires_at > now()) + AND used_count < max_uses`, inviteID) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("invite not usable") + } + return nil +} + +func (s *PostgresStore) Revoke(ctx context.Context, tenantID, inviteID int64) error { + res, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.tenant_invites SET status='revoked' +WHERE invite_id=$1 AND tenant_id=$2`, inviteID, tenantID) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("invite not found") + } + return nil +} + +func randomCode() (string, error) { + b := make([]byte, 12) + if _, err := rand.Read(b); err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + +func normalizeRole(role string) string { + n := authx.NormalizeRole(role) + if authx.ValidPlatformRole(n) { + return n + } + return authx.Role编辑 +} + +func maxUses(n int) int { + if n <= 0 { + return 1 + } + if n > 1000 { + return 1000 + } + return n +} + +func usable(inv *Invite) error { + if inv.Status != "active" { + return fmt.Errorf("invite not usable") + } + if inv.ExpiresAt != nil && time.Now().UTC().After(*inv.ExpiresAt) { + return fmt.Errorf("invite expired") + } + if inv.UsedCount >= inv.MaxUses { + return fmt.Errorf("invite exhausted") + } + return nil +} + +// ValidateUsable 供业务层在 Consume 前检查。 +func ValidateUsable(inv *Invite) error { + return usable(inv) +} diff --git a/platform/internal/license/license_test.go b/platform/internal/license/license_test.go new file mode 100644 index 0000000..0d2972e --- /dev/null +++ b/platform/internal/license/license_test.go @@ -0,0 +1,251 @@ +package license + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "aijianzhan/platform/internal/config" +) + +func testCfg(dir, seed string) config.LicenseConf { + return config.LicenseConf{ + Enabled: true, + LeaseDir: dir, + SeedNotAfter: seed, + Customer: "t", + ControlSecret: "test-license-secret", + SignSecret: "test-license-secret", + } +} + +func countLeaseFiles(dir string) int { + ents, err := os.ReadDir(dir) + if err != nil { + return 0 + } + n := 0 + for _, e := range ents { + if !e.IsDir() && strings.HasPrefix(e.Name(), "lease-") && strings.HasSuffix(e.Name(), ".json") { + n++ + } + } + return n +} + +func TestEachOpWritesNewFile(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 10).Format("2006-01-02"))) + if err != nil { + t.Fatal(err) + } + if n := countLeaseFiles(dir); n != 1 { + t.Fatalf("bootstrap files=%d", n) + } + if _, err := m.Extend(7, "support", "e1"); err != nil { + t.Fatal(err) + } + if _, err := m.Renew(time.Now().UTC().AddDate(1, 0, 0), "billing", "y1"); err != nil { + t.Fatal(err) + } + if n := countLeaseFiles(dir); n != 3 { + t.Fatalf("want 3 lease files, got %d", n) + } + st := m.Status(time.Now()) + if st.LeaseCount != 3 || st.ActiveFile == "" || st.Expired { + t.Fatalf("%+v", st) + } +} + +func TestExtendLimits(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 10).Format("2006-01-02"))) + if err != nil { + t.Fatal(err) + } + for i := 0; i < DefaultMaxExtensions; i++ { + if _, err := m.Extend(DefaultExtensionMaxDays, "test", ""); err != nil { + t.Fatalf("extend %d: %v", i+1, err) + } + } + if _, err := m.Extend(1, "test", ""); err == nil { + t.Fatal("expected max extensions exceeded") + } +} + +func TestTamperDoesNotOverrideValidHistory(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 30).Format("2006-01-02"))) + if err != nil { + t.Fatal(err) + } + good := m.Status(time.Now()).ActiveFile + // 写入一份篡改文件 + bad := `{ + "id": "deadbeef", + "customer": "hack", + "not_after": "2099-12-31T00:00:00Z", + "extensions_used": 0, + "extensions_max": 5, + "extension_max_days": 30, + "updated_at": "2099-01-01T00:00:00Z", + "updated_by": "hacker", + "note": "", + "signature": "00" +}` + if err := os.WriteFile(filepath.Join(dir, "lease-20990101T000000Z-hacker-deadbeef.json"), []byte(bad), 0o644); err != nil { + t.Fatal(err) + } + st := m.Status(time.Now()) + if st.Expired || !st.SignatureOK || st.ActiveFile != good { + t.Fatalf("tampered file must be ignored: %+v want active=%s", st, good) + } +} + +func TestTamperActiveRejectedWhenAlone(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 30).Format("2006-01-02"))) + if err != nil { + t.Fatal(err) + } + active := m.Status(time.Now()).ActiveFile + b, _ := os.ReadFile(filepath.Join(dir, active)) + var raw map[string]any + _ = json.Unmarshal(b, &raw) + raw["not_after"] = "2099-12-31T00:00:00Z" + out, _ := json.MarshalIndent(raw, "", " ") + _ = os.WriteFile(filepath.Join(dir, active), out, 0o644) + st := m.Status(time.Now()) + if !st.Expired || st.SignatureOK { + t.Fatalf("tampered sole lease must fail: %+v", st) + } +} + +func TestWatchdogViaAPI(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, "2020-01-01")) + if err != nil { + t.Fatal(err) + } + before := countLeaseFiles(dir) + lease, err := m.PutLease(&Lease{ + Customer: "A", + NotAfter: time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC), + }, "watchdog") + if err != nil { + t.Fatal(err) + } + if lease.FileName == "" || lease.ID == "" { + t.Fatalf("%+v", lease) + } + if countLeaseFiles(dir) != before+1 { + t.Fatal("put should add a new file") + } +} + +func TestOfflineSameFileOnce(t *testing.T) { + dir := t.TempDir() + m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 5).Format("2006-01-02"))) + if err != nil { + t.Fatal(err) + } + _ = m.Status(time.Now()) // 初始化消费账本 + cur := m.Status(time.Now()) + pkg, err := m.MintSignedJSON(&Lease{ + Customer: "t", + NotAfter: time.Now().UTC().AddDate(0, 0, 35), + ExtensionsUsed: 1, + ExtensionsMax: 1, + ExtensionMaxDays: 30, + UpdatedAt: time.Now().UTC(), + UpdatedBy: "offline-pack", + Note: "usb", + }) + if err != nil { + t.Fatal(err) + } + if _, err := m.ImportSigned(pkg, "usb"); err != nil { + t.Fatal(err) + } + if m.Status(time.Now()).Expired { + t.Fatal("after import should work") + } + // 同一份再导入 → 拒绝 + if _, err := m.ImportSigned(pkg, "usb"); err == nil { + t.Fatal("same file must not import twice") + } + // 正式续费后,再丢回这份旧包仍应拒绝 + if _, err := m.Renew(time.Now().UTC().AddDate(1, 0, 0), "billing", ""); err != nil { + t.Fatal(err) + } + if _, err := m.ImportSigned(pkg, "usb"); err == nil { + t.Fatal("consumed id must stay rejected after renew") + } + _ = cur +} + +func TestClearConsumedFailsClosed(t *testing.T) { + dir := t.TempDir() + leaseDir := filepath.Join(dir, "leases") + stateDir := filepath.Join(dir, "state") + cfg := testCfg(leaseDir, time.Now().UTC().AddDate(0, 0, 20).Format("2006-01-02")) + cfg.StateDir = stateDir + m, err := NewManager(cfg) + if err != nil { + t.Fatal(err) + } + _ = m.Status(time.Now()) + if err := os.WriteFile(filepath.Join(stateDir, "_consumed.json"), []byte(`{"active_id":"x","ids":[],"updated_at":"2020-01-01T00:00:00Z","signature":"00"}`), 0o644); err != nil { + t.Fatal(err) + } + st := m.Status(time.Now()) + if !st.Expired { + t.Fatalf("tampered consumed ledger must fail closed: %+v", st) + } +} + +func TestWipeLeasesKeepsState(t *testing.T) { + dir := t.TempDir() + leaseDir := filepath.Join(dir, "leases") + stateDir := filepath.Join(dir, "state") + cfg := testCfg(leaseDir, time.Now().UTC().AddDate(0, 0, 5).Format("2006-01-02")) + cfg.StateDir = stateDir + m, err := NewManager(cfg) + if err != nil { + t.Fatal(err) + } + _ = m.Status(time.Now()) + pkg, err := m.MintSignedJSON(&Lease{ + Customer: "t", NotAfter: time.Now().UTC().AddDate(0, 0, 40), + ExtensionsUsed: 1, ExtensionsMax: 1, ExtensionMaxDays: 30, + UpdatedAt: time.Now().UTC(), UpdatedBy: "pack", + }) + if err != nil { + t.Fatal(err) + } + if _, err := m.ImportSigned(pkg, "usb"); err != nil { + t.Fatal(err) + } + // 删光租约文件,保留 state + ents, _ := os.ReadDir(leaseDir) + for _, e := range ents { + _ = os.Remove(filepath.Join(leaseDir, e.Name())) + } + if _, err := m.ImportSigned(pkg, "usb"); err == nil { + t.Fatal("after wiping leases, same pack must still be rejected via state") + } +} + +func TestDisabled(t *testing.T) { + m, err := NewManager(config.LicenseConf{Enabled: false}) + if err != nil { + t.Fatal(err) + } + st := m.Status(time.Now()) + if st.Expired || st.Enabled { + t.Fatalf("%+v", st) + } +} diff --git a/platform/internal/license/manager.go b/platform/internal/license/manager.go new file mode 100644 index 0000000..5829034 --- /dev/null +++ b/platform/internal/license/manager.go @@ -0,0 +1,1045 @@ +package license + +import ( + "context" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "time" + + "aijianzhan/platform/internal/config" +) + +const ( + DefaultMaxExtensions = 1 // 临时延期总共只能用 1 次 + DefaultExtensionMaxDays = 30 + signVersion = "v1" + leaseFilePrefix = "lease-" + consumedFileName = "_consumed.json" // 本机已消费 id 账本(签名),保证离线一文件一用 +) + +// Lease 单次签发的授权记录。每次续费/延期/写入都会生成独立文件,不覆盖历史。 +type Lease struct { + ID string `json:"id"` // 本条租约唯一 ID + Customer string `json:"customer"` + NotAfter time.Time `json:"not_after"` + ExtensionsUsed int `json:"extensions_used"` + ExtensionsMax int `json:"extensions_max"` + ExtensionMaxDays int `json:"extension_max_days"` + UpdatedAt time.Time `json:"updated_at"` + UpdatedBy string `json:"updated_by,omitempty"` + Note string `json:"note,omitempty"` + Signature string `json:"signature,omitempty"` + FileName string `json:"file_name,omitempty"` // 落盘文件名(不参与签名) +} + +// Status 对外状态(取目录中「签名有效且最新」的那一份)。 +type Status struct { + Enabled bool `json:"enabled"` + Customer string `json:"customer,omitempty"` + NotAfter time.Time `json:"not_after,omitempty"` + GraceDays int `json:"grace_days"` + Expired bool `json:"expired"` + Message string `json:"message,omitempty"` + ExtensionsUsed int `json:"extensions_used"` + ExtensionsRemaining int `json:"extensions_remaining"` + ExtensionMaxDays int `json:"extension_max_days"` + LeaseDir string `json:"lease_dir,omitempty"` + ActiveFile string `json:"active_file,omitempty"` + LeaseID string `json:"lease_id,omitempty"` + LeaseCount int `json:"lease_count"` // 目录内签名有效且仍可作为候选的文件数 + ConsumedCount int `json:"consumed_count"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + SignatureOK bool `json:"signature_ok"` +} + +type Manager struct { + mu sync.RWMutex + cfg config.LicenseConf + dir string + state string + cur *Lease + mirror ConsumedMirror +} + +func resolveLeaseDir(cfg config.LicenseConf) string { + if d := strings.TrimSpace(cfg.LeaseDir); d != "" { + return d + } + p := strings.TrimSpace(cfg.LeasePath) + if p == "" { + return "./data/license/leases" + } + if strings.HasSuffix(strings.ToLower(p), ".json") { + return filepath.Join(filepath.Dir(p), "leases") + } + return p +} + +func resolveStateDir(cfg config.LicenseConf, leaseDir string) string { + if d := strings.TrimSpace(cfg.StateDir); d != "" { + return d + } + // 与 leases 同级的 state,删租约目录不会清掉消费记录 + return filepath.Join(filepath.Dir(leaseDir), "state") +} + +func NewManager(cfg config.LicenseConf) (*Manager, error) { + dir := resolveLeaseDir(cfg) + state := resolveStateDir(cfg, dir) + m := &Manager{cfg: cfg, dir: dir, state: state} + if !cfg.Enabled { + return m, nil + } + if _, err := m.signKey(); err != nil { + return nil, err + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + if err := os.MkdirAll(state, 0o755); err != nil { + return nil, err + } + m.migrateLegacyConsumed() + if err := m.reload(); err != nil { + if !os.IsNotExist(err) { + if isSigErr(err) { + m.cur = nil + return m, nil + } + return nil, err + } + seed, _ := ParseNotAfter(cfg.SeedNotAfter) + if seed.IsZero() { + seed, _ = ParseNotAfter(cfg.NotAfter) + } + if seed.IsZero() { + seed = time.Now().UTC().AddDate(0, 0, -1) + } + lease := &Lease{ + Customer: strings.TrimSpace(cfg.Customer), + NotAfter: seed, + ExtensionsUsed: 0, + ExtensionsMax: DefaultMaxExtensions, + ExtensionMaxDays: DefaultExtensionMaxDays, + UpdatedAt: time.Now().UTC(), + UpdatedBy: "bootstrap", + Note: "signed lease file; each renew/extend writes a new file", + } + if err := m.persistLocked(lease); err != nil { + return nil, err + } + if err := m.activateLocked(lease); err != nil { + return nil, err + } + m.cur = lease + } + return m, nil +} + +func (m *Manager) AttachMirror(store ConsumedMirror) { + if m == nil || store == nil { + return + } + m.mu.Lock() + m.mirror = store + m.mu.Unlock() + // 启动时用 DB 补全本地账本 + key, err := m.signKey() + if err != nil { + return + } + m.mu.Lock() + defer m.mu.Unlock() + led, _ := m.loadConsumed(key) + if led == nil { + led = &consumedLedger{IDs: map[string]struct{}{}} + } + _ = m.mergeMirrorLocked(led) + _ = m.saveConsumed(led, key) +} + +func (m *Manager) migrateLegacyConsumed() { + legacy := filepath.Join(m.dir, consumedFileName) + dest := m.consumedPath() + if _, err := os.Stat(dest); err == nil { + return + } + b, err := os.ReadFile(legacy) + if err != nil { + return + } + _ = os.WriteFile(dest, b, 0o644) +} + +func (m *Manager) Path() string { return m.dir } + +func (m *Manager) StatePath() string { return m.state } + +func (m *Manager) Enabled() bool { + return m != nil && m.cfg.Enabled +} + +func (m *Manager) signKey() ([]byte, error) { + s := strings.TrimSpace(m.cfg.SignSecret) + if s == "" { + s = strings.TrimSpace(m.cfg.ControlSecret) + } + if s == "" { + return nil, fmt.Errorf("License.ControlSecret(或 SignSecret)未配置,无法签发/校验租约") + } + return []byte(s), nil +} + +func (m *Manager) reload() error { + key, err := m.signKey() + if err != nil { + return err + } + ledger, ledErr := m.loadConsumed(key) + if ledErr != nil && !os.IsNotExist(ledErr) { + return ledErr // 账本被篡改:拒绝生效(防清账复用) + } + if ledger == nil { + ledger = &consumedLedger{IDs: map[string]struct{}{}} + } + + entries, err := os.ReadDir(m.dir) + if err != nil { + return err + } + var valid []*Lease + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasPrefix(name, leaseFilePrefix) || !strings.HasSuffix(strings.ToLower(name), ".json") { + continue + } + lease, lerr := m.readFile(filepath.Join(m.dir, name), key) + if lerr != nil { + continue + } + lease.FileName = name + valid = append(valid, lease) + } + if len(valid) == 0 { + return os.ErrNotExist + } + + // 首次无账本:把已有全部 id 记为已消费,仅最新一份可作 active(防把旧文件再当「新延期」用) + if ledErr != nil && os.IsNotExist(ledErr) { + var newest *Lease + for _, l := range valid { + ledger.IDs[l.ID] = struct{}{} + if newest == nil || leaseNewer(l, newest) { + newest = l + } + } + ledger.ActiveID = newest.ID + if err := m.saveConsumed(ledger, key); err != nil { + return err + } + m.cur = newest + return nil + } + + var best *Lease + for _, l := range valid { + if !ledger.usable(l.ID) { + continue // 已消费且非当前 active → 同一份文件不能再次生效 + } + if best == nil || leaseNewer(l, best) { + best = l + } + } + if best == nil { + return sigErr("无可用租约:有效文件均已消费,请使用新的延期包") + } + if best.ID != ledger.ActiveID { + ledger.IDs[best.ID] = struct{}{} + ledger.ActiveID = best.ID + if err := m.saveConsumed(ledger, key); err != nil { + return err + } + } + m.cur = best + return nil +} + +func (m *Manager) consumedPath() string { + return filepath.Join(m.state, consumedFileName) +} + +func (m *Manager) mergeMirrorLocked(led *consumedLedger) error { + if m.mirror == nil || led == nil { + return nil + } + active, ids, err := m.mirror.Load(context.Background()) + if err != nil { + return nil // DB 暂不可用时不阻断,靠本地账本 + } + for _, id := range ids { + id = strings.TrimSpace(id) + if id != "" { + led.IDs[id] = struct{}{} + } + } + if active != "" && led.ActiveID == "" { + led.ActiveID = active + } + if led.ActiveID != "" { + led.IDs[led.ActiveID] = struct{}{} + } + return nil +} + +type consumedLedger struct { + ActiveID string + IDs map[string]struct{} +} + +func (c *consumedLedger) usable(id string) bool { + if id == "" { + return false + } + if c.ActiveID == id { + return true + } + _, used := c.IDs[id] + return !used +} + +type consumedFile struct { + ActiveID string `json:"active_id"` + IDs []string `json:"ids"` + UpdatedAt string `json:"updated_at"` + Signature string `json:"signature"` +} + +func (m *Manager) loadConsumed(key []byte) (*consumedLedger, error) { + b, err := os.ReadFile(m.consumedPath()) + if err != nil { + // 本地无账本:尝试从 DB 恢复(防只删 state 文件) + led := &consumedLedger{IDs: map[string]struct{}{}} + if m.mirror != nil { + _ = m.mergeMirrorLocked(led) + if len(led.IDs) > 0 { + return led, nil + } + } + return nil, err + } + var raw consumedFile + if err := json.Unmarshal(b, &raw); err != nil { + return nil, sigErr("消费账本损坏") + } + payload := consumedCanonical(raw.ActiveID, raw.IDs, raw.UpdatedAt) + want := signPayload(payload, key) + if !hmac.Equal([]byte(strings.ToLower(raw.Signature)), []byte(strings.ToLower(want))) { + return nil, sigErr("消费账本签名校验失败(疑似清账复用)") + } + out := &consumedLedger{ + ActiveID: raw.ActiveID, + IDs: map[string]struct{}{}, + } + for _, id := range raw.IDs { + id = strings.TrimSpace(id) + if id != "" { + out.IDs[id] = struct{}{} + } + } + if out.ActiveID != "" { + out.IDs[out.ActiveID] = struct{}{} + } + _ = m.mergeMirrorLocked(out) + return out, nil +} + +func (m *Manager) saveConsumed(led *consumedLedger, key []byte) error { + ids := make([]string, 0, len(led.IDs)) + for id := range led.IDs { + ids = append(ids, id) + } + sortStrings(ids) + ua := time.Now().UTC().Format(time.RFC3339) + raw := consumedFile{ + ActiveID: led.ActiveID, + IDs: ids, + UpdatedAt: ua, + Signature: signPayload(consumedCanonical(led.ActiveID, ids, ua), key), + } + b, err := json.MarshalIndent(raw, "", " ") + if err != nil { + return err + } + if err := os.MkdirAll(m.state, 0o755); err != nil { + return err + } + path := m.consumedPath() + tmp := path + ".tmp" + if err := os.WriteFile(tmp, b, 0o644); err != nil { + return err + } + if err := os.Rename(tmp, path); err != nil { + return err + } + if m.mirror != nil { + _ = m.mirror.Save(context.Background(), led.ActiveID, ids) + } + return nil +} + +func consumedCanonical(active string, ids []string, updatedAt string) string { + cp := append([]string(nil), ids...) + sortStrings(cp) + return strings.Join([]string{ + signVersion, + "consumed", + active, + strings.Join(cp, ","), + updatedAt, + }, "|") +} + +func sortStrings(a []string) { + sort.Strings(a) +} + +// ImportSigned 离线导入延期软件签发的租约文件:验签后落盘;同一 id 只能生效一次。 +func (m *Manager) ImportSigned(rawJSON []byte, by string) (*Lease, error) { + if !m.Enabled() { + return nil, fmt.Errorf("license 未启用") + } + key, err := m.signKey() + if err != nil { + return nil, err + } + var file leaseFile + if err := json.Unmarshal(rawJSON, &file); err != nil { + return nil, fmt.Errorf("租约 JSON 无效: %w", err) + } + lease, err := file.toLease() + if err != nil { + return nil, err + } + if err := verifyLease(lease, key); err != nil { + return nil, err + } + m.mu.Lock() + defer m.mu.Unlock() + + ledger, ledErr := m.loadConsumed(key) + if ledErr != nil && !os.IsNotExist(ledErr) { + return nil, ledErr + } + if ledger == nil { + ledger = &consumedLedger{IDs: map[string]struct{}{}} + _ = m.mergeMirrorLocked(ledger) + } + if _, used := ledger.IDs[lease.ID]; used { + return nil, fmt.Errorf("该延期文件已使用过(id=%s),不可重复导入", lease.ID) + } + // 配置了核销地址:必须联网把 id 核销掉;删光本地后旧包也会被服务端拒绝 + if strings.TrimSpace(m.cfg.RedeemURL) != "" { + if err := m.redeemOnline(lease.ID, "redeem"); err != nil { + return nil, err + } + } + + by = sanitizeFilePart(nonempty(by, "import")) + name := fmt.Sprintf("%s%s-%s-%s.json", + leaseFilePrefix, + lease.UpdatedAt.UTC().Format("20060102T150405Z"), + by, + lease.ID, + ) + full := filepath.Join(m.dir, name) + // 原样写入(保留延期软件签名) + pretty, err := json.MarshalIndent(file, "", " ") + if err != nil { + return nil, err + } + tmp := full + ".tmp" + if err := os.WriteFile(tmp, pretty, 0o644); err != nil { + return nil, err + } + if err := os.Rename(tmp, full); err != nil { + _ = os.Remove(tmp) + return nil, err + } + lease.FileName = name + ledger.IDs[lease.ID] = struct{}{} + ledger.ActiveID = lease.ID + if err := m.saveConsumed(ledger, key); err != nil { + return nil, err + } + m.cur = lease + return cloneLease(lease), nil +} + +// MintSignedJSON 延期软件离线签发:生成带签名的 JSON(不落盘、不消费);客户机 ImportSigned 后才会生效且仅一次。 +func (m *Manager) MintSignedJSON(lease *Lease) ([]byte, error) { + if !m.Enabled() { + return nil, fmt.Errorf("license 未启用") + } + key, err := m.signKey() + if err != nil { + return nil, err + } + if lease == nil { + return nil, fmt.Errorf("lease 为空") + } + if lease.ID == "" { + lease.ID = newLeaseID() + } + if lease.UpdatedAt.IsZero() { + lease.UpdatedAt = time.Now().UTC() + } + if lease.ExtensionsMax <= 0 { + lease.ExtensionsMax = DefaultMaxExtensions + } + if lease.ExtensionMaxDays <= 0 { + lease.ExtensionMaxDays = DefaultExtensionMaxDays + } + lease.Signature = signPayload(canonical(lease), key) + raw := leaseFile{ + ID: lease.ID, + Customer: lease.Customer, + NotAfter: lease.NotAfter.UTC().Format(time.RFC3339), + ExtensionsUsed: lease.ExtensionsUsed, + ExtensionsMax: lease.ExtensionsMax, + ExtensionMaxDays: lease.ExtensionMaxDays, + UpdatedAt: lease.UpdatedAt.UTC().Format(time.RFC3339), + UpdatedBy: lease.UpdatedBy, + Note: lease.Note, + Signature: lease.Signature, + } + return json.MarshalIndent(raw, "", " ") +} + +func leaseNewer(a, b *Lease) bool { + if a.UpdatedAt.After(b.UpdatedAt) { + return true + } + if a.UpdatedAt.Equal(b.UpdatedAt) && a.NotAfter.After(b.NotAfter) { + return true + } + if a.UpdatedAt.Equal(b.UpdatedAt) && a.NotAfter.Equal(b.NotAfter) && a.ID > b.ID { + return true + } + return false +} + +func (m *Manager) readFile(path string, key []byte) (*Lease, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var raw leaseFile + if err := json.Unmarshal(b, &raw); err != nil { + return nil, fmt.Errorf("parse lease: %w", err) + } + lease, err := raw.toLease() + if err != nil { + return nil, err + } + if err := verifyLease(lease, key); err != nil { + return nil, err + } + return lease, nil +} + +func (m *Manager) countValidLocked(key []byte) int { + entries, err := os.ReadDir(m.dir) + if err != nil { + return 0 + } + n := 0 + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasPrefix(name, leaseFilePrefix) || !strings.HasSuffix(strings.ToLower(name), ".json") { + continue + } + if _, err := m.readFile(filepath.Join(m.dir, name), key); err == nil { + n++ + } + } + return n +} + +func (m *Manager) load() (*Lease, error) { + m.mu.Lock() + defer m.mu.Unlock() + if err := m.reload(); err != nil { + return nil, err + } + return cloneLease(m.cur), nil +} + +func (m *Manager) Status(now time.Time) Status { + st := Status{ + Enabled: m.cfg.Enabled, + GraceDays: m.cfg.GraceDays, + LeaseDir: m.dir, + ExtensionMaxDays: DefaultExtensionMaxDays, + ExtensionsRemaining: DefaultMaxExtensions, + } + if !m.cfg.Enabled { + return st + } + lease, err := m.load() + key, _ := m.signKey() + m.mu.Lock() + if key != nil { + st.LeaseCount = m.countValidLocked(key) + if led, e := m.loadConsumed(key); e == nil && led != nil { + st.ConsumedCount = len(led.IDs) + } + } + m.mu.Unlock() + if err != nil || lease == nil { + st.Expired = true + st.SignatureOK = false + msg := "授权租约无效或签名校验失败(禁止外部篡改文件)" + if err != nil && isSigErr(err) { + msg = err.Error() + } else if err != nil { + msg = "授权租约不可用,请用延期软件签发新文件后导入" + } + if mcfg := strings.TrimSpace(m.cfg.Message); mcfg != "" && !isSigErr(err) { + msg = mcfg + } + st.Message = msg + return st + } + st.SignatureOK = true + st.ActiveFile = lease.FileName + st.LeaseID = lease.ID + st.Customer = lease.Customer + if st.Customer == "" { + st.Customer = strings.TrimSpace(m.cfg.Customer) + } + st.NotAfter = lease.NotAfter + st.UpdatedAt = lease.UpdatedAt + st.UpdatedBy = lease.UpdatedBy + st.ExtensionsUsed = lease.ExtensionsUsed + maxExt := lease.ExtensionsMax + if maxExt <= 0 || maxExt > DefaultMaxExtensions { + maxExt = DefaultMaxExtensions + } + maxDays := lease.ExtensionMaxDays + if maxDays <= 0 || maxDays > DefaultExtensionMaxDays { + maxDays = DefaultExtensionMaxDays + } + st.ExtensionMaxDays = maxDays + remain := maxExt - lease.ExtensionsUsed + if remain < 0 { + remain = 0 + } + st.ExtensionsRemaining = remain + + deadline := lease.NotAfter + if m.cfg.GraceDays > 0 { + deadline = lease.NotAfter.AddDate(0, 0, m.cfg.GraceDays) + } + if now.UTC().After(deadline) { + st.Expired = true + msg := strings.TrimSpace(m.cfg.Message) + if msg == "" { + msg = "授权已过期,请联系宇信达续费或使用临时延期" + } + st.Message = msg + } + return st +} + +func (m *Manager) Renew(notAfter time.Time, by, note string) (*Lease, error) { + if !m.Enabled() { + return nil, fmt.Errorf("license 未启用") + } + if notAfter.IsZero() { + return nil, fmt.Errorf("not_after 无效") + } + if notAfter.UTC().Before(time.Now().UTC()) { + return nil, fmt.Errorf("续费到期日不能早于当前时间") + } + m.mu.Lock() + defer m.mu.Unlock() + _ = m.reload() + cur := m.cur + if cur == nil { + cur = &Lease{ + Customer: strings.TrimSpace(m.cfg.Customer), + ExtensionsMax: DefaultMaxExtensions, + ExtensionMaxDays: DefaultExtensionMaxDays, + } + } + next := &Lease{ + Customer: cur.Customer, + NotAfter: notAfter.UTC(), + ExtensionsUsed: 0, + ExtensionsMax: nonzero(cur.ExtensionsMax, DefaultMaxExtensions), + ExtensionMaxDays: nonzero(cur.ExtensionMaxDays, DefaultExtensionMaxDays), + UpdatedAt: time.Now().UTC(), + UpdatedBy: nonempty(by, "renew"), + Note: note, + } + if next.Customer == "" { + next.Customer = strings.TrimSpace(m.cfg.Customer) + } + if err := m.persistLocked(next); err != nil { + return nil, err + } + if err := m.activateLocked(next); err != nil { + return nil, err + } + m.cur = next + return cloneLease(next), nil +} + +func (m *Manager) Extend(days int, by, note string) (*Lease, error) { + if !m.Enabled() { + return nil, fmt.Errorf("license 未启用") + } + m.mu.Lock() + defer m.mu.Unlock() + if err := m.reload(); err != nil { + return nil, fmt.Errorf("当前租约无效,无法延期: %w", err) + } + cur := m.cur + if cur == nil { + return nil, fmt.Errorf("租约不存在") + } + maxExt := nonzero(cur.ExtensionsMax, DefaultMaxExtensions) + if maxExt > DefaultMaxExtensions { + maxExt = DefaultMaxExtensions + } + maxDays := nonzero(cur.ExtensionMaxDays, DefaultExtensionMaxDays) + if maxDays > DefaultExtensionMaxDays { + maxDays = DefaultExtensionMaxDays + } + if days <= 0 { + return nil, fmt.Errorf("延期天数须大于 0") + } + if days > maxDays { + return nil, fmt.Errorf("每次临时延期最多 %d 天", maxDays) + } + if cur.ExtensionsUsed >= maxExt { + return nil, fmt.Errorf("临时延期已使用过(仅允许 1 次),请正式续费") + } + base := cur.NotAfter + now := time.Now().UTC() + if base.Before(now) { + base = now + } + next := &Lease{ + Customer: cur.Customer, + NotAfter: base.AddDate(0, 0, days), + ExtensionsUsed: cur.ExtensionsUsed + 1, + ExtensionsMax: maxExt, + ExtensionMaxDays: maxDays, + UpdatedAt: now, + UpdatedBy: nonempty(by, "extend"), + Note: note, + } + if err := m.persistLocked(next); err != nil { + return nil, err + } + if err := m.activateLocked(next); err != nil { + return nil, err + } + m.cur = next + return cloneLease(next), nil +} + +func (m *Manager) PutLease(in *Lease, by string) (*Lease, error) { + if !m.Enabled() { + return nil, fmt.Errorf("license 未启用") + } + if in == nil || in.NotAfter.IsZero() { + return nil, fmt.Errorf("not_after 必填") + } + m.mu.Lock() + defer m.mu.Unlock() + _ = m.reload() + next := &Lease{ + Customer: strings.TrimSpace(in.Customer), + NotAfter: in.NotAfter.UTC(), + ExtensionsUsed: in.ExtensionsUsed, + ExtensionsMax: in.ExtensionsMax, + ExtensionMaxDays: in.ExtensionMaxDays, + UpdatedAt: time.Now().UTC(), + UpdatedBy: nonempty(by, "watchdog"), + Note: in.Note, + } + if next.Customer == "" { + next.Customer = strings.TrimSpace(m.cfg.Customer) + } + if next.ExtensionsMax <= 0 { + next.ExtensionsMax = DefaultMaxExtensions + } + if next.ExtensionMaxDays <= 0 { + next.ExtensionMaxDays = DefaultExtensionMaxDays + } + if next.ExtensionMaxDays > DefaultExtensionMaxDays { + next.ExtensionMaxDays = DefaultExtensionMaxDays + } + if next.ExtensionsMax > DefaultMaxExtensions { + next.ExtensionsMax = DefaultMaxExtensions + } + if next.ExtensionsUsed < 0 { + next.ExtensionsUsed = 0 + } + if next.ExtensionsUsed > next.ExtensionsMax { + next.ExtensionsUsed = next.ExtensionsMax + } + if err := m.persistLocked(next); err != nil { + return nil, err + } + if err := m.activateLocked(next); err != nil { + return nil, err + } + m.cur = next + return cloneLease(next), nil +} + +// activateLocked 将租约 id 记入本机消费账本(离线防同一文件二次生效)。 +func (m *Manager) activateLocked(lease *Lease) error { + key, err := m.signKey() + if err != nil { + return err + } + ledger, ledErr := m.loadConsumed(key) + if ledErr != nil && !os.IsNotExist(ledErr) { + return ledErr + } + if ledger == nil { + ledger = &consumedLedger{IDs: map[string]struct{}{}} + } + ledger.IDs[lease.ID] = struct{}{} + ledger.ActiveID = lease.ID + return m.saveConsumed(ledger, key) +} + +// persistLocked 每次写入新文件,不覆盖历史租约。 +func (m *Manager) persistLocked(lease *Lease) error { + key, err := m.signKey() + if err != nil { + return err + } + if lease.ID == "" { + lease.ID = newLeaseID() + } + if lease.UpdatedAt.IsZero() { + lease.UpdatedAt = time.Now().UTC() + } + lease.Signature = signPayload(canonical(lease), key) + raw := leaseFile{ + ID: lease.ID, + Customer: lease.Customer, + NotAfter: lease.NotAfter.UTC().Format(time.RFC3339), + ExtensionsUsed: lease.ExtensionsUsed, + ExtensionsMax: lease.ExtensionsMax, + ExtensionMaxDays: lease.ExtensionMaxDays, + UpdatedAt: lease.UpdatedAt.UTC().Format(time.RFC3339), + UpdatedBy: lease.UpdatedBy, + Note: lease.Note, + Signature: lease.Signature, + } + b, err := json.MarshalIndent(raw, "", " ") + if err != nil { + return err + } + by := sanitizeFilePart(lease.UpdatedBy) + if by == "" { + by = "op" + } + name := fmt.Sprintf("%s%s-%s-%s.json", + leaseFilePrefix, + lease.UpdatedAt.UTC().Format("20060102T150405Z"), + by, + lease.ID, + ) + full := filepath.Join(m.dir, name) + tmp := full + ".tmp" + if err := os.WriteFile(tmp, b, 0o644); err != nil { + return err + } + if err := os.Rename(tmp, full); err != nil { + _ = os.Remove(tmp) + return err + } + lease.FileName = name + return nil +} + +func newLeaseID() string { + var b [8]byte + _, _ = rand.Read(b[:]) + return hex.EncodeToString(b[:]) +} + +func sanitizeFilePart(s string) string { + s = strings.TrimSpace(strings.ToLower(s)) + var b strings.Builder + for _, r := range s { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' { + b.WriteRune(r) + } + } + out := b.String() + if len(out) > 24 { + out = out[:24] + } + return out +} + +type leaseFile struct { + ID string `json:"id"` + Customer string `json:"customer"` + NotAfter string `json:"not_after"` + ExtensionsUsed int `json:"extensions_used"` + ExtensionsMax int `json:"extensions_max"` + ExtensionMaxDays int `json:"extension_max_days"` + UpdatedAt string `json:"updated_at"` + UpdatedBy string `json:"updated_by"` + Note string `json:"note"` + Signature string `json:"signature"` +} + +func (f leaseFile) toLease() (*Lease, error) { + na, err := ParseNotAfter(f.NotAfter) + if err != nil || na.IsZero() { + return nil, fmt.Errorf("lease.not_after 无效") + } + ua, _ := ParseNotAfter(f.UpdatedAt) + if ua.IsZero() { + ua = time.Now().UTC() + } + l := &Lease{ + ID: strings.TrimSpace(f.ID), + Customer: f.Customer, + NotAfter: na, + ExtensionsUsed: f.ExtensionsUsed, + ExtensionsMax: f.ExtensionsMax, + ExtensionMaxDays: f.ExtensionMaxDays, + UpdatedAt: ua, + UpdatedBy: f.UpdatedBy, + Note: f.Note, + Signature: strings.TrimSpace(f.Signature), + } + if l.ExtensionsMax <= 0 { + l.ExtensionsMax = DefaultMaxExtensions + } + if l.ExtensionMaxDays <= 0 { + l.ExtensionMaxDays = DefaultExtensionMaxDays + } + return l, nil +} + +func canonical(l *Lease) string { + return strings.Join([]string{ + signVersion, + l.ID, + l.Customer, + l.NotAfter.UTC().Format(time.RFC3339), + strconv.Itoa(l.ExtensionsUsed), + strconv.Itoa(l.ExtensionsMax), + strconv.Itoa(l.ExtensionMaxDays), + l.UpdatedAt.UTC().Format(time.RFC3339), + l.UpdatedBy, + l.Note, + }, "|") +} + +func signPayload(payload string, key []byte) string { + mac := hmac.New(sha256.New, key) + _, _ = mac.Write([]byte(payload)) + return hex.EncodeToString(mac.Sum(nil)) +} + +func verifyLease(l *Lease, key []byte) error { + if l == nil { + return sigErr("租约为空") + } + if strings.TrimSpace(l.ID) == "" { + return sigErr("租约缺少 id") + } + if strings.TrimSpace(l.Signature) == "" { + return sigErr("租约缺少签名") + } + want := signPayload(canonical(l), key) + if !hmac.Equal([]byte(strings.ToLower(l.Signature)), []byte(strings.ToLower(want))) { + return sigErr("租约签名校验失败(疑似外部篡改)") + } + return nil +} + +type sigError string + +func (e sigError) Error() string { return string(e) } + +func sigErr(msg string) error { return sigError(msg) } + +func isSigErr(err error) bool { + if _, ok := err.(sigError); ok { + return true + } + if err == nil { + return false + } + s := err.Error() + return strings.Contains(s, "签名") || strings.Contains(s, "篡改") +} + +func ParseNotAfter(s string) (time.Time, error) { + s = strings.TrimSpace(s) + if s == "" { + return time.Time{}, nil + } + if t, err := time.Parse(time.RFC3339, s); err == nil { + return t.UTC(), nil + } + if t, err := time.Parse("2006-01-02", s); err == nil { + return t.UTC(), nil + } + return time.Time{}, fmt.Errorf("时间格式应为 RFC3339 或 YYYY-MM-DD") +} + +func cloneLease(l *Lease) *Lease { + if l == nil { + return nil + } + cp := *l + return &cp +} + +func nonempty(s, def string) string { + s = strings.TrimSpace(s) + if s == "" { + return def + } + return s +} + +func nonzero(v, def int) int { + if v <= 0 { + return def + } + return v +} diff --git a/platform/internal/license/middleware.go b/platform/internal/license/middleware.go new file mode 100644 index 0000000..ae4db3b --- /dev/null +++ b/platform/internal/license/middleware.go @@ -0,0 +1,44 @@ +package license + +import ( + "fmt" + "net/http" + "strings" + "time" +) + +// Middleware 按租约文件判断是否过期。授权控制接口始终放行,便于过期后远端续费/延期。 +func Middleware(m *Manager) func(http.HandlerFunc) http.HandlerFunc { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if isLicenseControlPath(path) || isHealthPath(path) { + next(w, r) + return + } + if m == nil || !m.Enabled() { + next(w, r) + return + } + st := m.Status(time.Now()) + if !st.Expired { + next(w, r) + return + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusPaymentRequired) + _, _ = w.Write([]byte(fmt.Sprintf( + `{"error":%q,"code":"license_expired","extensions_remaining":%d}`, + st.Message, st.ExtensionsRemaining, + ))) + } + } +} + +func isHealthPath(path string) bool { + return path == "/ping" || path == "/healthz" || strings.HasSuffix(path, "/healthz") +} + +func isLicenseControlPath(path string) bool { + return strings.HasPrefix(path, "/api/v1/license/") +} diff --git a/platform/internal/license/mirror.go b/platform/internal/license/mirror.go new file mode 100644 index 0000000..6bc96ed --- /dev/null +++ b/platform/internal/license/mirror.go @@ -0,0 +1,87 @@ +package license + +import ( + "context" + "database/sql" + "fmt" + "strings" +) + +// ConsumedMirror 第二持久化(如 Postgres):leases 目录被删后仍能识别已用过的 id。 +type ConsumedMirror interface { + Load(ctx context.Context) (activeID string, ids []string, err error) + Save(ctx context.Context, activeID string, ids []string) error +} + +type pgMirror struct { + db *sql.DB +} + +func NewPGMirror(db *sql.DB) ConsumedMirror { + if db == nil { + return nil + } + return &pgMirror{db: db} +} + +func (p *pgMirror) Load(ctx context.Context) (string, []string, error) { + rows, err := p.db.QueryContext(ctx, ` +SELECT lease_id, active FROM platform_meta.license_consumed`) + if err != nil { + return "", nil, err + } + defer rows.Close() + var active string + var ids []string + for rows.Next() { + var id string + var isActive bool + if err := rows.Scan(&id, &isActive); err != nil { + return "", nil, err + } + ids = append(ids, id) + if isActive { + active = id + } + } + return active, ids, rows.Err() +} + +func (p *pgMirror) Save(ctx context.Context, activeID string, ids []string) error { + tx, err := p.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.license_consumed`); err != nil { + return err + } + for _, id := range ids { + id = trimID(id) + if id == "" { + continue + } + _, err := tx.ExecContext(ctx, ` +INSERT INTO platform_meta.license_consumed(lease_id, active, updated_at) +VALUES ($1, $2, now()) +ON CONFLICT (lease_id) DO UPDATE SET active=EXCLUDED.active, updated_at=now()`, + id, id == activeID) + if err != nil { + return fmt.Errorf("license_consumed upsert: %w", err) + } + } + if activeID != "" { + _, err := tx.ExecContext(ctx, ` +INSERT INTO platform_meta.license_consumed(lease_id, active, updated_at) +VALUES ($1, true, now()) +ON CONFLICT (lease_id) DO UPDATE SET active=true, updated_at=now()`, activeID) + if err != nil { + return err + } + } + return tx.Commit() +} + +func trimID(s string) string { + return strings.TrimSpace(s) +} diff --git a/platform/internal/license/redeem.go b/platform/internal/license/redeem.go new file mode 100644 index 0000000..f6d0bdf --- /dev/null +++ b/platform/internal/license/redeem.go @@ -0,0 +1,53 @@ +package license + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" +) + +// redeemOnline 向你们的核销服务登记/校验延期包 id(删光本地数据后仍能拦截旧包)。 +func (m *Manager) redeemOnline(leaseID, action string) error { + base := strings.TrimRight(strings.TrimSpace(m.cfg.RedeemURL), "/") + if base == "" { + return nil + } + leaseID = strings.TrimSpace(leaseID) + if leaseID == "" { + return fmt.Errorf("lease id 为空") + } + body, _ := json.Marshal(map[string]string{ + "id": leaseID, + "customer": strings.TrimSpace(m.cfg.Customer), + "action": action, // check | redeem + "secret": strings.TrimSpace(m.cfg.ControlSecret), + }) + client := &http.Client{Timeout: 15 * time.Second} + req, err := http.NewRequest(http.MethodPost, base+"/v1/license/redeem", bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-License-Secret", strings.TrimSpace(m.cfg.ControlSecret)) + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("核销服务不可达(已配置 RedeemURL,导入需联网): %w", err) + } + defer resp.Body.Close() + var out struct { + OK bool `json:"ok"` + Error string `json:"error"` + } + _ = json.NewDecoder(resp.Body).Decode(&out) + if resp.StatusCode >= 300 || !out.OK { + msg := out.Error + if msg == "" { + msg = fmt.Sprintf("核销失败 HTTP %d", resp.StatusCode) + } + return fmt.Errorf("%s", msg) + } + return nil +} diff --git a/platform/internal/logic/applogic/agents.go b/platform/internal/logic/applogic/agents.go new file mode 100644 index 0000000..41353da --- /dev/null +++ b/platform/internal/logic/applogic/agents.go @@ -0,0 +1,244 @@ +package applogic + +import ( + "context" + "fmt" + "strings" + + "aijianzhan/platform/internal/agentcap" + "aijianzhan/platform/internal/agentstore" + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/tenantperm" + "aijianzhan/platform/internal/types" +) + +type AgentAdminLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAgentAdminLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentAdminLogic { + return &AgentAdminLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *AgentAdminLogic) store() (agentstore.Store, error) { + if l.svcCtx.Agents == nil { + return nil, fmt.Errorf("agent store unavailable") + } + return l.svcCtx.Agents, nil +} + +func (l *AgentAdminLogic) List() ([]agentstore.Account, error) { + st, err := l.store() + if err != nil { + return nil, err + } + items, err := st.List(l.ctx, authx.TenantID(l.ctx)) + if err != nil { + return nil, err + } + for i := range items { + l.attachRole(&items[i]) + } + return items, nil +} + +func (l *AgentAdminLogic) Get(agentID int64) (*agentstore.Account, error) { + st, err := l.store() + if err != nil { + return nil, err + } + acc, err := st.Get(l.ctx, authx.TenantID(l.ctx), agentID) + if err != nil { + return nil, err + } + l.attachRole(acc) + return acc, nil +} + +func (l *AgentAdminLogic) attachRole(acc *agentstore.Account) { + if acc == nil || acc.RoleID <= 0 || l.svcCtx.Roles == nil { + return + } + role, err := l.svcCtx.Roles.Get(l.ctx, authx.TenantID(l.ctx), acc.RoleID) + if err != nil { + return + } + acc.RoleCode = role.Code + acc.RoleName = role.Name +} + +func (l *AgentAdminLogic) resolvePerms(roleID int64, fallback []string) (int64, []string, error) { + if roleID <= 0 { + return 0, authx.NormalizePerms(fallback), nil + } + if l.svcCtx.Roles == nil { + return 0, nil, fmt.Errorf("role store unavailable") + } + role, err := l.svcCtx.Roles.Get(l.ctx, authx.TenantID(l.ctx), roleID) + if err != nil { + return 0, nil, err + } + return role.RoleID, authx.NormalizePerms(append([]string{}, role.Permissions...)), nil +} + +func (l *AgentAdminLogic) Create(req *types.AgentCreateReq) (*types.AgentCreateResp, error) { + st, err := l.store() + if err != nil { + return nil, err + } + roleID, perms, err := l.resolvePerms(req.RoleID, req.Permissions) + if err != nil { + return nil, err + } + if roleID <= 0 && len(perms) == 0 { + return nil, fmt.Errorf("role_id or permissions required") + } + if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { + return nil, err + } + acc, secret, err := st.Create(l.ctx, authx.TenantID(l.ctx), authx.UserID(l.ctx), agentstore.CreateInput{ + Name: req.Name, + Perms: perms, + AppSlugs: req.AppSlugs, + Status: agentstore.StatusActive, + RoleID: roleID, + }) + if err != nil { + return nil, err + } + l.attachRole(acc) + return &types.AgentCreateResp{Account: *acc, ClientSecret: secret}, nil +} + +func (l *AuthLogic) SelfRegisterAgent(req *types.AgentSelfRegisterReq) (*types.AgentSelfRegisterResp, error) { + if l.svcCtx.Agents == nil { + return nil, fmt.Errorf("agent store unavailable") + } + want := l.svcCtx.Config.Agent.RegisterSecret + if want == "" { + want = l.svcCtx.Config.Auth.IssueSecret + } + if want == "" { + want = l.svcCtx.JWT.AccessSecret + } + if req.RegisterSecret == "" || req.RegisterSecret != want { + return nil, fmt.Errorf("invalid register secret") + } + tenantID := req.TenantID + if tenantID <= 0 { + tenantID = 1 + } + acc, secret, reused, err := l.svcCtx.Agents.Register(l.ctx, tenantID, req.Name, req.HostKey) + if err != nil { + return nil, err + } + msg := "已登记为 pending,请管理员在控制台分配角色并启用后再换票" + if reused { + msg = "已存在 pending 登记,已轮换 client_secret;仍须管理员分配角色并启用" + } + return &types.AgentSelfRegisterResp{ + Account: *acc, + ClientSecret: secret, + Reused: reused, + Message: msg, + }, nil +} + +func (l *AgentAdminLogic) Update(agentID int64, req *types.AgentUpdateReq) (*agentstore.Account, error) { + st, err := l.store() + if err != nil { + return nil, err + } + in := agentstore.UpdateInput{} + if req.Name != nil { + in.Name = req.Name + } + if req.Status != nil { + in.Status = req.Status + } + if req.AppSlugs != nil { + in.AppSlugs = req.AppSlugs + } + if req.RoleID != nil { + roleID, perms, err := l.resolvePerms(*req.RoleID, nil) + if err != nil { + return nil, err + } + if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { + return nil, err + } + in.RoleID = &roleID + in.Perms = &perms + } else if req.Permissions != nil { + n := authx.NormalizePerms(*req.Permissions) + if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), n); err != nil { + return nil, err + } + in.Perms = &n + } + acc, err := st.Update(l.ctx, authx.TenantID(l.ctx), agentID, in) + if err != nil { + return nil, err + } + l.attachRole(acc) + return acc, nil +} + +func (l *AgentAdminLogic) Rotate(agentID int64) (*types.AgentSecretResp, error) { + st, err := l.store() + if err != nil { + return nil, err + } + secret, err := st.RotateSecret(l.ctx, authx.TenantID(l.ctx), agentID) + if err != nil { + return nil, err + } + acc, err := st.Get(l.ctx, authx.TenantID(l.ctx), agentID) + if err != nil { + return nil, err + } + return &types.AgentSecretResp{ClientID: acc.ClientID, ClientSecret: secret}, nil +} + +func (l *AgentAdminLogic) Delete(agentID int64) error { + st, err := l.store() + if err != nil { + return err + } + return st.Delete(l.ctx, authx.TenantID(l.ctx), agentID) +} + +func (l *AuthLogic) IssueClientCredentials(clientID, clientSecret string) (*types.TokenResp, error) { + if l.svcCtx.Agents == nil { + return nil, fmt.Errorf("agent store unavailable") + } + acc, err := l.svcCtx.Agents.Authenticate(l.ctx, strings.TrimSpace(clientID), clientSecret) + if err != nil { + return nil, err + } + token, exp, err := authx.IssueAgentToken(l.svcCtx.JWT, acc.TenantID, acc.AgentID, acc.Perms) + if err != nil { + return nil, err + } + _ = l.svcCtx.Agents.TouchToken(l.ctx, acc.AgentID) + secret := l.svcCtx.Config.Agent.CapsuleSecret + if secret == "" { + secret = l.svcCtx.JWT.AccessSecret + } + return &types.TokenResp{ + AccessToken: token, + TokenType: "Bearer", + ExpiresAt: exp, + TenantID: acc.TenantID, + UserID: acc.AgentID, + Username: acc.ClientID, + DisplayName: acc.Name, + Role: authx.RoleAgent, + AgentKey: agentcap.PublicAgentKey(secret, acc.TenantID, acc.AgentID), + AgentID: acc.AgentID, + Permissions: append([]string{}, acc.Perms...), + AppSlugs: append([]string{}, acc.AppSlugs...), + }, nil +} diff --git a/platform/internal/logic/applogic/aggregate.go b/platform/internal/logic/applogic/aggregate.go new file mode 100644 index 0000000..92e686f --- /dev/null +++ b/platform/internal/logic/applogic/aggregate.go @@ -0,0 +1,91 @@ +package applogic + +import ( + "fmt" + "strconv" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/types" +) + +func (l *CrudLogic) Aggregate(slug, resource, groupBy, sumField string) (*types.AggregateResp, error) { + tenantID := authx.TenantID(l.ctx) + ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + if groupBy != "" { + ok := false + if ref.Resource.List != nil { + for _, f := range ref.Resource.List.AllowedFilters { + if f == groupBy { + ok = true + break + } + } + } + for _, f := range ref.Entity.Fields { + if f.Name == groupBy { + ok = true + break + } + } + if !ok { + return nil, fmt.Errorf("group_by not allowed: %s", groupBy) + } + } + items, total, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "") + if err != nil { + return nil, err + } + resp := &types.AggregateResp{Total: total, GroupBy: groupBy, SumField: sumField} + if groupBy == "" { + if sumField != "" { + for _, it := range items { + resp.Sum += toFloat(it[sumField]) + } + } + return resp, nil + } + buckets := map[string]*types.AggregateBucket{} + order := []string{} + for _, it := range items { + key := fmt.Sprint(it[groupBy]) + if key == "" || key == "" { + key = "(空)" + } + b, ok := buckets[key] + if !ok { + b = &types.AggregateBucket{Key: key} + buckets[key] = b + order = append(order, key) + } + b.Count++ + if sumField != "" { + b.Sum += toFloat(it[sumField]) + resp.Sum += toFloat(it[sumField]) + } + } + for _, k := range order { + resp.Buckets = append(resp.Buckets, *buckets[k]) + } + return resp, nil +} + +func toFloat(v any) float64 { + switch t := v.(type) { + case float64: + return t + case float32: + return float64(t) + case int: + return float64(t) + case int64: + return float64(t) + case string: + n, _ := strconv.ParseFloat(t, 64) + return n + default: + return 0 + } +} diff --git a/platform/internal/logic/applogic/auth.go b/platform/internal/logic/applogic/auth.go new file mode 100644 index 0000000..ab7b12e --- /dev/null +++ b/platform/internal/logic/applogic/auth.go @@ -0,0 +1,393 @@ +package applogic + +import ( + "context" + "fmt" + "log" + "strings" + + "aijianzhan/platform/internal/agentcap" + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/smsstore" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" + "aijianzhan/platform/internal/userstore" +) + +type AuthLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AuthLogic { + return &AuthLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *AuthLogic) IssueToken(req *types.TokenReq) (*types.TokenResp, error) { + gt := strings.TrimSpace(strings.ToLower(req.GrantType)) + if gt == "client_credentials" || (req.ClientID != "" && req.ClientSecret != "") { + return l.IssueClientCredentials(req.ClientID, req.ClientSecret) + } + want := l.svcCtx.Config.Auth.IssueSecret + if want == "" { + want = l.svcCtx.JWT.AccessSecret + } + if req.Secret == "" || req.Secret != want { + return nil, fmt.Errorf("invalid issue secret") + } + role := req.Role + if role == "" { + role = authx.Role管理员 + } + return l.issue(req.TenantID, req.UserID, role, "", "", 0) +} + +func (l *AuthLogic) Register(req *types.RegisterReq) (*types.TokenResp, error) { + if l.phoneLoginOnly() { + return nil, fmt.Errorf("本系统仅支持手机号登录,请联系管理员开通账号并绑定手机") + } + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + u, err := l.svcCtx.Users.Register(l.ctx, req.Username, req.Password, req.DisplayName) + if err != nil { + return nil, err + } + return l.issueUser(u) +} + +func (l *AuthLogic) phoneLoginOnly() bool { + // 有部署期限控制时,不必强制手机号登录 + if l.svcCtx.Config.License.Enabled { + return false + } + return l.svcCtx.Config.Auth.PhoneLoginOnly +} + +// PhoneLoginOnlyPolicy 对外暴露:是否强制仅手机号登录(License 开启时为 false)。 +func (l *AuthLogic) PhoneLoginOnlyPolicy() bool { + return l.phoneLoginOnly() +} + +func (l *AuthLogic) Login(req *types.LoginReq) (*types.TokenResp, error) { + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + smsCode := strings.TrimSpace(req.SMSCode) + phone := strings.TrimSpace(req.Phone) + account := strings.TrimSpace(req.Username) + if account == "" { + account = phone + } + + // 手机号 + 短信验证码 + if smsCode != "" { + if phone == "" { + phone = account + } + ns, err := userstore.NormalizePhone(phone) + if err != nil { + return nil, err + } + if l.svcCtx.SMS == nil { + return nil, fmt.Errorf("短信服务未启用") + } + if err := l.svcCtx.SMS.Consume(smsstore.PurposeLogin, ns, smsCode); err != nil { + return nil, err + } + u, err := l.svcCtx.Users.GetByPhone(l.ctx, ns) + if err != nil { + return nil, fmt.Errorf("该手机号未绑定账号") + } + if u.Status == "disabled" { + return nil, fmt.Errorf("账号已停用") + } + return l.issueUser(u) + } + + if strings.TrimSpace(req.Password) == "" { + return nil, fmt.Errorf("请填写密码或短信验证码") + } + + // 仅手机号登录:必须用手机号+密码 + if l.phoneLoginOnly() { + loginPhone := phone + if loginPhone == "" { + loginPhone = account + } + ns, err := userstore.NormalizePhone(loginPhone) + if err != nil { + return nil, fmt.Errorf("请使用手机号登录") + } + u, err := l.svcCtx.Users.Login(l.ctx, ns, req.Password) + if err != nil { + return nil, err + } + if err := l.enforceLoginPolicy(u, false); err != nil { + return nil, err + } + return l.issueUser(u) + } + + // 兼容模式:用户名/手机号 + 密码 + if account == "" { + return nil, fmt.Errorf("请填写用户名或手机号") + } + u, err := l.svcCtx.Users.Login(l.ctx, account, req.Password) + if err != nil { + return nil, err + } + viaUsername := !userstore.LooksLikePhone(account) + if err := l.enforceLoginPolicy(u, viaUsername); err != nil { + return nil, err + } + return l.issueUser(u) +} + +func (l *AuthLogic) enforceLoginPolicy(u *userstore.User, viaUsername bool) error { + cfg := l.svcCtx.Config.Auth + phoneOnly := l.phoneLoginOnly() + requirePhone := cfg.RequirePhoneBound || phoneOnly + if requirePhone && strings.TrimSpace(u.Phone) == "" { + return fmt.Errorf("本部署要求绑定手机号后才能登录,请联系管理员") + } + if !viaUsername { + return nil + } + if phoneOnly || cfg.DisableUsernameLoginIfPhoneBound || u.UsernameLoginDisabled { + if strings.TrimSpace(u.Phone) == "" { + return fmt.Errorf("已禁用用户名登录,请联系管理员绑定手机号") + } + return fmt.Errorf("已禁用用户名登录,请使用手机号+密码或短信验证码") + } + return nil +} + +type SendSMSResult struct { + ExpiresIn int + RetryAfter int + DebugCode string + Message string +} + +func (l *AuthLogic) SendLoginSMS(phone string) (*SendSMSResult, error) { + if l.svcCtx.Users == nil || l.svcCtx.SMS == nil { + return nil, fmt.Errorf("短信服务未启用") + } + provider := strings.ToLower(strings.TrimSpace(l.svcCtx.Config.SMS.Provider)) + if provider == "off" || provider == "disabled" { + return nil, fmt.Errorf("短信登录未开启") + } + ns, err := userstore.NormalizePhone(phone) + if err != nil { + return nil, err + } + if _, err := l.svcCtx.Users.GetByPhone(l.ctx, ns); err != nil { + return nil, fmt.Errorf("该手机号未绑定账号,请联系管理员绑定手机后再登录") + } + code, expiresIn, retryAfter, err := l.svcCtx.SMS.Issue(smsstore.PurposeLogin, ns) + if err != nil { + return &SendSMSResult{RetryAfter: retryAfter}, err + } + out := &SendSMSResult{ + ExpiresIn: expiresIn, + Message: "验证码已发送", + } + if provider == "" || provider == "dev" { + log.Printf("[sms:dev] login code for %s = %s (expires %ds)", ns, code, expiresIn) + out.DebugCode = code + out.Message = "开发模式:验证码已写入服务日志(并返回 debug_code)" + } + return out, nil +} + +func (l *AuthLogic) ChangePassword(oldPassword, newPassword string) error { + if l.svcCtx.Users == nil { + return fmt.Errorf("user store unavailable") + } + uid := authx.UserID(l.ctx) + if uid <= 0 { + return fmt.Errorf("未登录") + } + return l.svcCtx.Users.ChangePassword(l.ctx, uid, oldPassword, newPassword) +} + +func (l *AuthLogic) BindPhone(phone string) (*userstore.User, error) { + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + uid := authx.UserID(l.ctx) + if uid <= 0 { + return nil, fmt.Errorf("未登录") + } + if l.phoneLoginOnly() && strings.TrimSpace(phone) == "" { + return nil, fmt.Errorf("本部署仅支持手机号登录,不可解绑手机号") + } + return l.svcCtx.Users.BindPhone(l.ctx, uid, phone) +} + +func (l *AuthLogic) SetUsernameLoginDisabled(disabled bool) (*userstore.User, error) { + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + uid := authx.UserID(l.ctx) + if uid <= 0 { + return nil, fmt.Errorf("未登录") + } + return l.svcCtx.Users.SetUsernameLoginDisabled(l.ctx, uid, disabled) +} + +func (l *AuthLogic) Me() (*userstore.User, error) { + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + uid := authx.UserID(l.ctx) + if uid <= 0 { + return nil, fmt.Errorf("未登录") + } + return l.svcCtx.Users.GetByID(l.ctx, uid) +} + +func (l *AuthLogic) issue(tenantID, userID int64, role, username, displayName string, orgUnitID int64) (*types.TokenResp, error) { + role = authx.NormalizeRole(role) + token, exp, err := authx.IssueToken(l.svcCtx.JWT, tenantID, userID, role, orgUnitID) + if err != nil { + return nil, err + } + secret := l.svcCtx.Config.Agent.CapsuleSecret + if secret == "" { + secret = l.svcCtx.JWT.AccessSecret + } + return &types.TokenResp{ + AccessToken: token, + TokenType: "Bearer", + ExpiresAt: exp, + TenantID: tenantID, + UserID: userID, + Username: username, + DisplayName: displayName, + Role: role, + OrgUnitID: orgUnitID, + AgentKey: agentcap.PublicAgentKey(secret, tenantID, userID), + }, nil +} + +type CapsuleLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCapsuleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CapsuleLogic { + return &CapsuleLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *CapsuleLogic) Build(slug string) (*types.CapsuleResp, error) { + tenantID := authx.TenantID(l.ctx) + userID := authx.UserID(l.ctx) + if authx.Role(l.ctx) == authx.RoleAgent { + if l.svcCtx.Agents == nil { + return nil, fmt.Errorf("agent store unavailable") + } + ok, err := l.svcCtx.Agents.HasAppAccess(l.ctx, authx.AgentID(l.ctx), slug) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("app not granted to agent") + } + } + app, err := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug) + if err != nil { + return nil, err + } + if app.Status != meta.StatusPublished { + return nil, fmt.Errorf("app not published") + } + base := l.svcCtx.Config.PublicBaseURL + if base == "" { + base = fmt.Sprintf("http://127.0.0.1:%d", l.svcCtx.Config.Port) + } + desc := &agentcap.Descriptor{ + Version: "1", + BaseURL: strings.TrimRight(base, "/"), + AppSlug: slug, + TenantHint: fmt.Sprintf("t%d", tenantID), + Auth: agentcap.AuthSpec{Type: "bearer_jwt", Header: "Authorization"}, + Notes: "Decrypt with agent_key from /auth/token. Never expose plaintext API map in UI.", + } + for _, r := range app.Blueprint.Apis.Resources { + path := r.Path + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + full := fmt.Sprintf("/api/v1/apps/%s%s", slug, path) + methods := make([]string, 0, len(r.Operations)) + for _, op := range r.Operations { + switch op { + case "list", "get", "export": + methods = append(methods, "GET") + case "create", "import": + methods = append(methods, "POST") + case "update": + methods = append(methods, "PUT") + case "delete": + methods = append(methods, "DELETE") + } + } + var entityFields []agentcap.FieldSpec + pk := "id" + for _, e := range app.Blueprint.Entities { + if e.Name != r.Entity { + continue + } + pk = e.PrimaryKey + for _, f := range e.Fields { + entityFields = append(entityFields, agentcap.FieldSpec{Name: f.Name, Type: f.Type}) + } + } + filters, sorts := []string{}, []string{} + if r.List != nil { + filters = r.List.AllowedFilters + sorts = r.List.AllowedSorts + } + name := strings.TrimPrefix(path, "/") + desc.Resources = append(desc.Resources, agentcap.ResourceSpec{ + Name: name, + Path: full, + Methods: uniqStrings(methods), + Filters: filters, + Sorts: sorts, + Fields: entityFields, + PrimaryKey: pk, + }) + } + + secret := l.svcCtx.Config.Agent.CapsuleSecret + if secret == "" { + secret = l.svcCtx.JWT.AccessSecret + } + key := agentcap.DeriveKey(secret, tenantID, userID) + capsule, err := agentcap.Encrypt(key, desc) + if err != nil { + return nil, err + } + return &types.CapsuleResp{ + Capsule: capsule, + Format: agentcap.Prefix, + Hint: "仅智能体使用 agent_key 解密;前端只展示密文", + }, nil +} + +func uniqStrings(in []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(in)) + for _, s := range in { + if _, ok := seen[s]; ok { + continue + } + seen[s] = struct{}{} + out = append(out, s) + } + return out +} diff --git a/platform/internal/logic/applogic/crud.go b/platform/internal/logic/applogic/crud.go new file mode 100644 index 0000000..5997992 --- /dev/null +++ b/platform/internal/logic/applogic/crud.go @@ -0,0 +1,123 @@ +package applogic + +import ( + "context" + "fmt" + "strconv" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/crud" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" +) + +type CrudLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCrudLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CrudLogic { + return &CrudLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *CrudLogic) withOrgScope() context.Context { + ctx := l.ctx + role := authx.Role(ctx) + orgID := authx.OrgUnitID(ctx) + scope := crud.RowScope{WriteOrgUnit: orgID} + // owner / agent 不按组织裁剪;其它角色若绑定了组织则只看本组织及下级 + if !authx.IsCompanyAdmin(role) && role != authx.Role智能体 && orgID > 0 && l.svcCtx.OrgUnits != nil { + ids, err := l.svcCtx.OrgUnits.DescendantIDs(ctx, authx.TenantID(ctx), orgID) + if err == nil { + scope.OrgUnitIDs = ids + } else { + scope.OrgUnitIDs = []int64{orgID} + } + } + return crud.WithRowScope(ctx, scope) +} + +func (l *CrudLogic) List(slug, resource string, page, pageSize int, filters map[string]string, sortBy string) (*types.PageResult, error) { + ctx := l.withOrgScope() + tenantID := authx.TenantID(ctx) + ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + items, total, err := l.svcCtx.CRUD.List(ctx, ref, tenantID, page, pageSize, filters, sortBy) + if err != nil { + return nil, err + } + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + return &types.PageResult{Items: items, Page: page, PageSize: pageSize, Total: total}, nil +} + +func (l *CrudLogic) Get(slug, resource, id string) (map[string]any, error) { + ctx := l.withOrgScope() + tenantID := authx.TenantID(ctx) + ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + return l.svcCtx.CRUD.Get(ctx, ref, tenantID, id) +} + +func (l *CrudLogic) Create(slug, resource string, body map[string]any) (map[string]any, error) { + ctx := l.withOrgScope() + tenantID := authx.TenantID(ctx) + userID := authx.UserID(ctx) + ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + return l.svcCtx.CRUD.Create(ctx, ref, tenantID, userID, body) +} + +func (l *CrudLogic) Update(slug, resource, id string, body map[string]any) (map[string]any, error) { + ctx := l.withOrgScope() + tenantID := authx.TenantID(ctx) + ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + return l.svcCtx.CRUD.Update(ctx, ref, tenantID, id, body) +} + +func (l *CrudLogic) Delete(slug, resource, id string) error { + ctx := l.withOrgScope() + tenantID := authx.TenantID(ctx) + ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource) + if err != nil { + return err + } + return l.svcCtx.CRUD.Delete(ctx, ref, tenantID, id) +} + +func (l *CrudLogic) GetBlueprint(slug string) (any, error) { + tenantID := authx.TenantID(l.ctx) + app, err := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug) + if err != nil { + return nil, err + } + if app.Blueprint == nil { + return nil, fmt.Errorf("blueprint missing") + } + return app.Blueprint, nil +} + +func ParsePage(pageStr, sizeStr string) (int, int) { + page, _ := strconv.Atoi(pageStr) + size, _ := strconv.Atoi(sizeStr) + if page <= 0 { + page = 1 + } + if size <= 0 { + size = 20 + } + return page, size +} diff --git a/platform/internal/logic/applogic/impex.go b/platform/internal/logic/applogic/impex.go new file mode 100644 index 0000000..adf2abd --- /dev/null +++ b/platform/internal/logic/applogic/impex.go @@ -0,0 +1,319 @@ +package applogic + +import ( + "bytes" + "encoding/csv" + "fmt" + "io" + "path/filepath" + "strconv" + "strings" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/types" + + "github.com/xuri/excelize/v2" +) + +// ImportRows accepts .xlsx (preferred) or .csv. +func (l *CrudLogic) ImportRows(slug, resource, filename string, r io.Reader) (*types.ImportResp, error) { + tenantID := authx.TenantID(l.ctx) + userID := authx.UserID(l.ctx) + ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + if !hasOp(ref, "import") { + return nil, fmt.Errorf("operation import not allowed") + } + + ext := strings.ToLower(filepath.Ext(filename)) + raw, err := io.ReadAll(r) + if err != nil { + return nil, err + } + if len(raw) == 0 { + return nil, fmt.Errorf("empty file") + } + + var rows [][]string + switch { + case ext == ".xlsx" || ext == ".xlsm" || isZipOOXML(raw): + rows, err = readExcelRows(raw) + case ext == ".csv" || ext == ".txt" || looksLikeCSV(raw): + rows, err = readCSVRows(raw) + default: + // try excel then csv + rows, err = readExcelRows(raw) + if err != nil { + rows, err = readCSVRows(raw) + } + } + if err != nil { + return nil, fmt.Errorf("parse file: %w", err) + } + if len(rows) < 1 { + return nil, fmt.Errorf("no header row") + } + + headers := make([]string, len(rows[0])) + for i, h := range rows[0] { + headers[i] = strings.TrimSpace(h) + } + + resp := &types.ImportResp{Errors: []string{}} + for rowNum := 1; rowNum < len(rows); rowNum++ { + rec := rows[rowNum] + if rowEmpty(rec) { + continue + } + body := map[string]any{} + for i, h := range headers { + if i >= len(rec) || h == "" { + continue + } + fname := mapHeaderToField(ref, h) + if fname == "" || fname == ref.Entity.PrimaryKey { + continue + } + body[fname] = coerceValue(ref, fname, strings.TrimSpace(rec[i])) + } + if len(body) == 0 { + resp.Skipped++ + continue + } + if _, err := l.svcCtx.CRUD.Create(l.ctx, ref, tenantID, userID, body); err != nil { + resp.Skipped++ + if len(resp.Errors) < 100 { + resp.Errors = append(resp.Errors, fmt.Sprintf("row %d: %v", rowNum+1, err)) + } + continue + } + resp.Inserted++ + } + return resp, nil +} + +// ImportCSV kept for callers; prefers CSV parsing. +func (l *CrudLogic) ImportCSV(slug, resource string, r io.Reader) (*types.ImportResp, error) { + return l.ImportRows(slug, resource, "import.csv", r) +} + +// ExportExcel writes .xlsx workbook. +func (l *CrudLogic) ExportExcel(slug, resource string) ([]byte, string, error) { + tenantID := authx.TenantID(l.ctx) + ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource) + if err != nil { + return nil, "", err + } + if !hasOp(ref, "export") { + return nil, "", fmt.Errorf("operation export not allowed") + } + items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "") + if err != nil { + return nil, "", err + } + headers := make([]string, 0, len(ref.Entity.Fields)) + for _, f := range ref.Entity.Fields { + headers = append(headers, f.Name) + } + + f := excelize.NewFile() + sheet := f.GetSheetName(0) + for i, h := range headers { + cell, _ := excelize.CoordinatesToCellName(i+1, 1) + _ = f.SetCellValue(sheet, cell, h) + } + for ri, item := range items { + for ci, h := range headers { + cell, _ := excelize.CoordinatesToCellName(ci+1, ri+2) + if v, ok := item[h]; ok && v != nil { + _ = f.SetCellValue(sheet, cell, v) + } + } + } + buf, err := f.WriteToBuffer() + if err != nil { + return nil, "", err + } + return buf.Bytes(), resource + ".xlsx", nil +} + +// ExportCSV kept for format=csv. +func (l *CrudLogic) ExportCSV(slug, resource string) ([]byte, error) { + tenantID := authx.TenantID(l.ctx) + ref, err := l.svcCtx.Meta.ResolveResource(l.ctx, tenantID, slug, resource) + if err != nil { + return nil, err + } + if !hasOp(ref, "export") { + return nil, fmt.Errorf("operation export not allowed") + } + items, _, err := l.svcCtx.CRUD.List(l.ctx, ref, tenantID, 1, 5000, nil, "") + if err != nil { + return nil, err + } + headers := make([]string, 0, len(ref.Entity.Fields)) + for _, f := range ref.Entity.Fields { + headers = append(headers, f.Name) + } + buf := &bytes.Buffer{} + w := csv.NewWriter(buf) + _ = w.Write(headers) + for _, item := range items { + row := make([]string, len(headers)) + for i, h := range headers { + if v, ok := item[h]; ok && v != nil { + row[i] = fmt.Sprint(v) + } + } + _ = w.Write(row) + } + w.Flush() + return buf.Bytes(), w.Error() +} + +func readExcelRows(raw []byte) ([][]string, error) { + f, err := excelize.OpenReader(bytes.NewReader(raw)) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + sheets := f.GetSheetList() + if len(sheets) == 0 { + return nil, fmt.Errorf("excel has no sheets") + } + // 优先业务数据页,跳过说明/超限汇总 + prefer := "" + for _, name := range sheets { + n := strings.ToLower(name) + if strings.Contains(n, "settlement") || (strings.Contains(name, "测点") && !strings.Contains(name, "超限") && !strings.Contains(name, "说明")) { + prefer = name + break + } + } + if prefer == "" { + for _, name := range sheets { + if !strings.Contains(name, "说明") && !strings.Contains(name, "超限") { + prefer = name + break + } + } + } + if prefer == "" { + prefer = sheets[0] + } + return f.GetRows(prefer) +} + +func readCSVRows(raw []byte) ([][]string, error) { + cr := csv.NewReader(bytes.NewReader(raw)) + cr.FieldsPerRecord = -1 + return cr.ReadAll() +} + +func isZipOOXML(raw []byte) bool { + // xlsx is a zip archive + return len(raw) >= 4 && raw[0] == 'P' && raw[1] == 'K' && raw[2] == 3 && raw[3] == 4 +} + +func looksLikeCSV(raw []byte) bool { + sample := raw + if len(sample) > 512 { + sample = sample[:512] + } + s := string(sample) + return strings.Contains(s, ",") || strings.Contains(s, "\t") || strings.Contains(s, ";") +} + +func rowEmpty(rec []string) bool { + for _, c := range rec { + if strings.TrimSpace(c) != "" { + return false + } + } + return true +} + +func hasOp(ref *meta.ResourceRef, op string) bool { + for _, o := range ref.Resource.Operations { + if o == op { + return true + } + } + return false +} + +func mapHeaderToField(ref *meta.ResourceRef, header string) string { + h := strings.TrimSpace(header) + if h == "" { + return "" + } + norm := normalizeHeader(h) + aliases := map[string]string{ + "编号dk": "dkilo", "测点dk": "dkilo", "dkilo": "dkilo", + "里程": "chainage", "chainage": "chainage", + "测点编号": "point_code", "测点": "point_code", "point_code": "point_code", + "断面类型": "section_type", "section_type": "section_type", + "工点": "worksite", "worksite": "worksite", + "cjl数值": "cjl_value", "观测值": "cjl_value", "观测值mm": "cjl_value", "cjl_value": "cjl_value", + "cjl颜色": "cjl_color", "观测色": "cjl_color", "cjl_color": "cjl_color", + "设计沉降": "design_settlement_mm", "设计总沉降量mm": "design_settlement_mm", "design_settlement_mm": "design_settlement_mm", + "累积沉降": "cum_settlement_mm", "累积沉降量mm": "cum_settlement_mm", "cum_settlement_mm": "cum_settlement_mm", + "预测沉降": "pred_settlement_mm", "预测沉降mm": "pred_settlement_mm", "pred_settlement_mm": "pred_settlement_mm", + "超限量": "exceed_mm", "超限量mm": "exceed_mm", "exceed_mm": "exceed_mm", + "超限累计天数": "exceed_days", "累计天数天": "exceed_days", "exceed_days": "exceed_days", + "监督天": "supervise_days", "监督周期天": "supervise_days", "supervise_days": "supervise_days", + "超期天数": "overdue_days", "overdue_days": "overdue_days", + "前一日占比": "before_day", "before_day": "before_day", "beforeday": "before_day", + "后一日占比": "next_day", "next_day": "next_day", "nextday": "next_day", + "频率": "frequency", "frequency": "frequency", + "cljd标识": "workinfo_kilo", "workinfo_kilo": "workinfo_kilo", + "状态": "status", "status": "status", + "里程米": "mileage_m", "mileage_m": "mileage_m", + } + if a, ok := aliases[norm]; ok { + h = a + norm = a + } + for _, f := range ref.Entity.Fields { + if f.Name == h || f.Label == header || normalizeHeader(f.Label) == norm || f.Name == norm { + return f.Name + } + } + return "" +} + +func normalizeHeader(s string) string { + s = strings.TrimSpace(strings.ToLower(s)) + repl := strings.NewReplacer(" ", "", "_", "", "-", "", "(", "", ")", "", "(", "", ")", "", ".", "") + return repl.Replace(s) +} + +func coerceValue(ref *meta.ResourceRef, fieldName, raw string) any { + if raw == "" { + return nil + } + for _, f := range ref.Entity.Fields { + if f.Name != fieldName { + continue + } + switch f.Type { + case "int", "bigint": + n, err := strconv.ParseInt(raw, 10, 64) + if err == nil { + return n + } + case "decimal": + n, err := strconv.ParseFloat(raw, 64) + if err == nil { + return n + } + case "boolean": + return raw == "1" || strings.EqualFold(raw, "true") || raw == "是" + } + return raw + } + return raw +} diff --git a/platform/internal/logic/applogic/members.go b/platform/internal/logic/applogic/members.go new file mode 100644 index 0000000..a08d97e --- /dev/null +++ b/platform/internal/logic/applogic/members.go @@ -0,0 +1,66 @@ +package applogic + +import ( + "fmt" + "strings" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/userstore" +) + +type MemberLogic struct { + *AuthLogic +} + +func NewMemberLogic(l *AuthLogic) *MemberLogic { + return &MemberLogic{AuthLogic: l} +} + +func (l *MemberLogic) List() ([]userstore.User, error) { + tid := authx.TenantID(l.ctx) + if tid <= 0 { + return nil, fmt.Errorf("missing tenant") + } + return l.svcCtx.Users.ListMembers(l.ctx, tid) +} + +// Create 创建成员:用户名始终随机唯一;password 为空则随机初始密码(用户登录后可自行修改)。 +func (l *MemberLogic) Create(password, displayName, role string, orgUnitID int64) (*userstore.User, string, error) { + tid := authx.TenantID(l.ctx) + if tid <= 0 { + return nil, "", fmt.Errorf("missing tenant") + } + if role == "" { + role = authx.Role编辑 + } + uname, err := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) + if err != nil { + return nil, "", err + } + u, plain, err := l.svcCtx.Users.CreateMember(l.ctx, tid, uname, password, displayName, role, orgUnitID) + if err != nil { + // 极罕见冲突:再试一次 + if strings.Contains(err.Error(), "already exists") { + uname2, e2 := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) + if e2 != nil { + return nil, "", e2 + } + return l.svcCtx.Users.CreateMember(l.ctx, tid, uname2, password, displayName, role, orgUnitID) + } + return nil, "", err + } + return u, plain, nil +} + +func (l *MemberLogic) Update(userID int64, role string, orgUnitID int64, status string) (*userstore.User, error) { + tid := authx.TenantID(l.ctx) + if tid <= 0 { + return nil, fmt.Errorf("missing tenant") + } + if userID == authx.UserID(l.ctx) && role != "" && authx.NormalizeRole(role) != authx.Role管理员 { + if authx.IsCompanyAdmin(authx.Role(l.ctx)) { + return nil, fmt.Errorf("不能修改自己的角色,请由其他管理员操作") + } + } + return l.svcCtx.Users.UpdateMember(l.ctx, tid, userID, role, orgUnitID, status) +} diff --git a/platform/internal/logic/applogic/orgunits.go b/platform/internal/logic/applogic/orgunits.go new file mode 100644 index 0000000..fae89d7 --- /dev/null +++ b/platform/internal/logic/applogic/orgunits.go @@ -0,0 +1,55 @@ +package applogic + +import ( + "context" + "fmt" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/orgunitstore" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" +) + +type OrgUnitLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewOrgUnitLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrgUnitLogic { + return &OrgUnitLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *OrgUnitLogic) List() ([]orgunitstore.OrgUnit, error) { + if l.svcCtx.OrgUnits == nil { + return nil, fmt.Errorf("org unit store unavailable") + } + return l.svcCtx.OrgUnits.List(l.ctx, authx.TenantID(l.ctx)) +} + +func (l *OrgUnitLogic) Create(req *types.OrgUnitCreateReq) (*orgunitstore.OrgUnit, error) { + if l.svcCtx.OrgUnits == nil { + return nil, fmt.Errorf("org unit store unavailable") + } + return l.svcCtx.OrgUnits.Create(l.ctx, authx.TenantID(l.ctx), orgunitstore.CreateInput{ + ParentID: req.ParentID, + Name: req.Name, + Code: req.Code, + }) +} + +func (l *OrgUnitLogic) Update(id int64, req *types.OrgUnitUpdateReq) (*orgunitstore.OrgUnit, error) { + if l.svcCtx.OrgUnits == nil { + return nil, fmt.Errorf("org unit store unavailable") + } + return l.svcCtx.OrgUnits.Update(l.ctx, authx.TenantID(l.ctx), id, orgunitstore.UpdateInput{ + Name: req.Name, + Code: req.Code, + }) +} + +func (l *OrgUnitLogic) Delete(id int64) error { + if l.svcCtx.OrgUnits == nil { + return fmt.Errorf("org unit store unavailable") + } + return l.svcCtx.OrgUnits.Delete(l.ctx, authx.TenantID(l.ctx), id) +} diff --git a/platform/internal/logic/applogic/platform.go b/platform/internal/logic/applogic/platform.go new file mode 100644 index 0000000..a28c4d9 --- /dev/null +++ b/platform/internal/logic/applogic/platform.go @@ -0,0 +1,381 @@ +package applogic + +import ( + "fmt" + "strings" + "time" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/invitestore" + "aijianzhan/platform/internal/types" + "aijianzhan/platform/internal/userstore" +) + +type PlatformLogic struct { + *AuthLogic +} + +type CreateTenantResult struct { + Tenant *userstore.TenantInfo `json:"tenant"` + AdminAccount *AdminAccountOut `json:"admin_account,omitempty"` + AdminInvite *invitestore.Invite `json:"admin_invite,omitempty"` +} + +// AdminAccountOut 新建公司时下发的公司管理员凭据(密码仅此一次明文返回)。 +type AdminAccountOut struct { + UserID int64 `json:"user_id"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Phone string `json:"phone,omitempty"` + UsernameLoginDisabled bool `json:"username_login_disabled,omitempty"` + DisplayName string `json:"display_name"` + Role string `json:"role"` +} + +type AdminInfo struct { + UserID int64 `json:"user_id"` + Username string `json:"username"` + Phone string `json:"phone"` + UsernameLoginDisabled bool `json:"username_login_disabled"` + DisplayName string `json:"display_name"` + Role string `json:"role"` + Status string `json:"status"` +} + +func NewPlatformLogic(l *AuthLogic) *PlatformLogic { + return &PlatformLogic{AuthLogic: l} +} + +func (l *PlatformLogic) requireSuper() (*userstore.User, error) { + if !authx.IsPlatformAdmin(authx.Role(l.ctx)) { + return nil, fmt.Errorf("需要超级管理员") + } + u, err := l.svcCtx.Users.GetByID(l.ctx, authx.UserID(l.ctx)) + if err != nil { + return nil, err + } + if !u.IsPlatformAdmin() { + return nil, fmt.Errorf("需要超级管理员") + } + return u, nil +} + +func (l *PlatformLogic) ListTenants() ([]userstore.TenantInfo, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + return l.svcCtx.Users.ListTenants(l.ctx) +} + +func (l *PlatformLogic) CreateTenant(name, slug string, withAdminInvite bool, adminPhone string) (*CreateTenantResult, error) { + super, err := l.requireSuper() + if err != nil { + return nil, err + } + phone := strings.TrimSpace(adminPhone) + if l.phoneLoginOnly() && phone == "" { + return nil, fmt.Errorf("本部署仅手机号登录,请填写管理员手机号") + } + t, err := l.svcCtx.Users.CreateTenant(l.ctx, strings.TrimSpace(name), strings.TrimSpace(slug)) + if err != nil { + return nil, err + } + if l.svcCtx.Roles != nil { + _ = l.svcCtx.Roles.EnsureDefaults(l.ctx, t.TenantID) + } + if l.svcCtx.TenantPerm != nil { + _ = l.svcCtx.TenantPerm.EnsureDefault(l.ctx, t.TenantID) + } + out := &CreateTenantResult{Tenant: t} + + acc, aerr := l.createCompanyAdmin(t.TenantID, strings.TrimSpace(name), phone) + if aerr != nil { + return nil, fmt.Errorf("创建公司成功,但管理员账号生成失败: %v", aerr) + } + out.AdminAccount = acc + + if withAdminInvite && l.svcCtx.Invites != nil { + inv, ierr := l.svcCtx.Invites.Create(l.ctx, t.TenantID, super.UserID, invitestore.CreateInput{ + Role: authx.Role管理员, + MaxUses: 1, + ExpiresIn: 7 * 24 * time.Hour, + }) + if ierr == nil { + out.AdminInvite = inv + } + } + return out, nil +} + +// createCompanyAdmin 生成随机唯一管理员;phone 非空时绑定手机。 +func (l *PlatformLogic) createCompanyAdmin(tenantID int64, companyName, phone string) (*AdminAccountOut, error) { + uname, err := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) + if err != nil { + return nil, err + } + display := strings.TrimSpace(companyName) + if display == "" { + display = "公司" + } + display += "管理员" + u, plain, err := l.svcCtx.Users.CreateMember(l.ctx, tenantID, uname, "", display, authx.Role管理员, 0) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + uname2, e2 := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) + if e2 != nil { + return nil, e2 + } + u, plain, err = l.svcCtx.Users.CreateMember(l.ctx, tenantID, uname2, "", display, authx.Role管理员, 0) + } + if err != nil { + return nil, err + } + } + out := &AdminAccountOut{ + UserID: u.UserID, Username: u.Username, Password: plain, + DisplayName: u.DisplayName, Role: u.Role, + } + phone = strings.TrimSpace(phone) + if phone == "" { + return out, nil + } + ns, nerr := userstore.NormalizePhone(phone) + if nerr != nil { + return nil, nerr + } + nu, berr := l.svcCtx.Users.BindPhone(l.ctx, u.UserID, ns) + if berr != nil { + return nil, fmt.Errorf("账号已创建但绑定手机失败: %v", berr) + } + out.Phone = nu.Phone + return out, nil +} + +// IssueAdminAccount 为已有公司再发一个管理员账号;有期限控制时手机号可选。 +func (l *PlatformLogic) IssueAdminAccount(tenantID int64, adminPhone string) (*AdminAccountOut, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + if l.phoneLoginOnly() && strings.TrimSpace(adminPhone) == "" { + return nil, fmt.Errorf("本部署仅手机号登录,请填写管理员手机号") + } + t, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID) + if err != nil { + return nil, err + } + return l.createCompanyAdmin(t.TenantID, t.Name, adminPhone) +} + +// ListCompanyAdmins 列出该公司人类管理员账号(用户名唯一不可改)。 +func (l *PlatformLogic) ListCompanyAdmins(tenantID int64) ([]AdminInfo, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { + return nil, err + } + members, err := l.svcCtx.Users.ListMembers(l.ctx, tenantID) + if err != nil { + return nil, err + } + out := make([]AdminInfo, 0) + for _, u := range members { + if !authx.IsCompanyAdmin(u.Role) { + continue + } + out = append(out, AdminInfo{ + UserID: u.UserID, Username: u.Username, Phone: u.Phone, + UsernameLoginDisabled: u.UsernameLoginDisabled, + DisplayName: u.DisplayName, Role: u.Role, Status: u.Status, + }) + } + return out, nil +} + +// UpdateCompanyAdmin 平台超管重置该公司管理员密码 / 绑定手机 / 禁用用户名登录(用户名不变)。 +func (l *PlatformLogic) UpdateCompanyAdmin(tenantID, userID int64, resetPassword bool, password string, phone *string, disableUsernameLogin *bool) (*AdminAccountOut, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { + return nil, err + } + u, err := l.svcCtx.Users.GetByID(l.ctx, userID) + if err != nil { + return nil, err + } + if u.TenantID != tenantID { + return nil, fmt.Errorf("成员不属于该公司") + } + if !authx.IsCompanyAdmin(u.Role) { + return nil, fmt.Errorf("仅可管理公司管理员账号") + } + if authx.IsPlatformAdmin(u.Role) { + return nil, fmt.Errorf("cannot edit platform admin") + } + out := &AdminAccountOut{ + UserID: u.UserID, Username: u.Username, Phone: u.Phone, + UsernameLoginDisabled: u.UsernameLoginDisabled, + DisplayName: u.DisplayName, Role: u.Role, + } + if resetPassword { + plain, err := l.svcCtx.Users.SetPassword(l.ctx, userID, password) + if err != nil { + return nil, err + } + out.Password = plain + } + if phone != nil { + if l.phoneLoginOnly() && strings.TrimSpace(*phone) == "" { + return nil, fmt.Errorf("本部署仅支持手机号登录,不可解绑手机号") + } + nu, err := l.svcCtx.Users.BindPhone(l.ctx, userID, *phone) + if err != nil { + return nil, err + } + out.Phone = nu.Phone + out.DisplayName = nu.DisplayName + out.UsernameLoginDisabled = nu.UsernameLoginDisabled + } + if disableUsernameLogin != nil { + nu, err := l.svcCtx.Users.SetUsernameLoginDisabled(l.ctx, userID, *disableUsernameLogin) + if err != nil { + return nil, err + } + out.Phone = nu.Phone + out.UsernameLoginDisabled = nu.UsernameLoginDisabled + } + return out, nil +} + +func (l *PlatformLogic) UpdateTenant(tenantID int64, name, slug string) (*userstore.TenantInfo, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + return l.svcCtx.Users.UpdateTenant(l.ctx, tenantID, name, slug) +} + +func (l *PlatformLogic) GetTenantPerms(tenantID int64) ([]string, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + if l.svcCtx.TenantPerm == nil { + return authx.CompanyPermCatalog(), nil + } + if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { + return nil, err + } + return l.svcCtx.TenantPerm.Get(l.ctx, tenantID) +} + +func (l *PlatformLogic) SetTenantPerms(tenantID int64, perms []string) ([]string, error) { + if _, err := l.requireSuper(); err != nil { + return nil, err + } + if l.svcCtx.TenantPerm == nil { + return nil, fmt.Errorf("tenant perm store unavailable") + } + if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { + return nil, err + } + if err := l.svcCtx.TenantPerm.Set(l.ctx, tenantID, perms); err != nil { + return nil, err + } + return l.svcCtx.TenantPerm.Get(l.ctx, tenantID) +} + +func (l *PlatformLogic) IssueAdminInvite(tenantID int64) (*invitestore.Invite, error) { + super, err := l.requireSuper() + if err != nil { + return nil, err + } + if l.svcCtx.Invites == nil { + return nil, fmt.Errorf("invite store unavailable") + } + if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { + return nil, err + } + return l.svcCtx.Invites.Create(l.ctx, tenantID, super.UserID, invitestore.CreateInput{ + Role: authx.Role管理员, + MaxUses: 1, + ExpiresIn: 7 * 24 * time.Hour, + }) +} + +// EnterTenant 进入某公司上下文(JWT 仍为超级管理员,但带上 tenant_id)。 +func (l *PlatformLogic) EnterTenant(tenantID int64) (*types.TokenResp, error) { + super, err := l.requireSuper() + if err != nil { + return nil, err + } + if tenantID <= 0 { + return nil, fmt.Errorf("tenant_id required") + } + t, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID) + if err != nil { + return nil, err + } + resp, err := l.issue(t.TenantID, super.UserID, authx.Role超级管理员, super.Username, super.DisplayName, 0) + if err != nil { + return nil, err + } + resp.Phone = super.Phone + resp.Status = userstore.StatusActive + resp.TenantName = t.Name + resp.Message = fmt.Sprintf("已打开「%s」的管理视图(你仍是平台超管,不是该公司账号)", t.Name) + return resp, nil +} + +// ExitTenant 退出公司管理视图,回到平台工作台(tenant_id=0)。 +func (l *PlatformLogic) ExitTenant() (*types.TokenResp, error) { + super, err := l.requireSuper() + if err != nil { + return nil, err + } + resp, err := l.issue(0, super.UserID, authx.Role超级管理员, super.Username, super.DisplayName, 0) + if err != nil { + return nil, err + } + resp.Phone = super.Phone + resp.Status = userstore.StatusActive + resp.Message = "已回到平台工作台" + return resp, nil +} + +// CompanyEntitlements 本公司可用权限。 +func (l *AuthLogic) CompanyEntitlements() ([]string, []authx.PermModule, error) { + tid := authx.TenantID(l.ctx) + if tid <= 0 { + return nil, nil, fmt.Errorf("missing tenant") + } + var perms []string + var err error + // 超管进入公司后看全量权限模块,便于代管(不受该公司额度裁剪展示) + if authx.IsPlatformAdmin(authx.Role(l.ctx)) { + perms = authx.CompanyPermCatalog() + } else if l.svcCtx.TenantPerm != nil { + perms, err = l.svcCtx.TenantPerm.Get(l.ctx, tid) + if err != nil { + return nil, nil, err + } + } else { + perms = authx.CompanyPermCatalog() + } + allow := map[string]struct{}{} + for _, p := range perms { + allow[p] = struct{}{} + } + var modules []authx.PermModule + for _, m := range authx.PermModules() { + var items []authx.PermItem + for _, it := range m.Items { + if _, ok := allow[it.Perm]; ok { + items = append(items, it) + } + } + if len(items) > 0 { + modules = append(modules, authx.PermModule{Title: m.Title, Items: items}) + } + } + return perms, modules, nil +} diff --git a/platform/internal/logic/applogic/publish.go b/platform/internal/logic/applogic/publish.go new file mode 100644 index 0000000..43a2b0e --- /dev/null +++ b/platform/internal/logic/applogic/publish.go @@ -0,0 +1,470 @@ +package applogic + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "aijianzhan/platform/internal/audit" + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/agentcap" + "aijianzhan/platform/internal/blueprint" + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/schema" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" + + "github.com/google/uuid" +) + +type PublishLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewPublishLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PublishLogic { + return &PublishLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *PublishLogic) ListApps() (*types.AppListResp, error) { + tenantID := authx.TenantID(l.ctx) + items, err := l.svcCtx.Meta.ListByTenant(l.ctx, tenantID) + if err != nil { + return nil, err + } + allowed := map[string]struct{}{} + filter := false + scope := "all" + // 管理账号:本租户全部模块(含在建) + // 智能体:app_slugs 为空或含 * → 不限制;否则仅白名单 + if authx.Role(l.ctx) == authx.RoleAgent && l.svcCtx.Agents != nil { + acc, err := l.svcCtx.Agents.Get(l.ctx, tenantID, authx.AgentID(l.ctx)) + if err != nil { + return nil, err + } + open := len(acc.AppSlugs) == 0 + for _, s := range acc.AppSlugs { + if s == "*" { + open = true + } + allowed[s] = struct{}{} + } + if open { + scope = "open" + filter = false + } else { + scope = "granted" + filter = true + } + } + out := make([]types.AppListItem, 0, len(items)) + for _, it := range items { + if filter { + if _, ok := allowed[it.Slug]; !ok { + continue + } + } + st := it.Status + out = append(out, types.AppListItem{ + AppID: it.AppID, + Slug: it.Slug, + Name: it.Name, + Status: string(st), + StatusLabel: meta.StatusLabelCN(st), + Building: meta.IsBuilding(st), + SchemaName: it.SchemaName, + PageCount: it.PageCount, + EntityCount: it.EntityCount, + UpdatedAt: it.UpdatedAt.UTC().Format(time.RFC3339), + CreatedAt: it.CreatedAt.UTC().Format(time.RFC3339), + }) + } + return &types.AppListResp{Items: out, Scope: scope}, nil +} + +// SaveDraft 登记/更新「在建」模块蓝图(不跑 DDL)。管理账号可用来看到生成中尚未发布的模块。 +func (l *PublishLogic) SaveDraft(slug string, req *types.DraftReq) (*types.AppListItem, error) { + tenantID := authx.TenantID(l.ctx) + bp, err := blueprint.Parse(req.Blueprint) + if err != nil { + return nil, err + } + slug = blueprint.NormalizeIdent(slug) + bp.Meta.Slug = blueprint.NormalizeIdent(bp.Meta.Slug) + if bp.Meta.Slug == "" { + bp.Meta.Slug = slug + } + if slug == "" { + slug = bp.Meta.Slug + } + bp.Meta.Slug = slug + if bp.Apis.BasePath == "" || strings.Contains(bp.Apis.BasePath, "-") { + bp.Apis.BasePath = "/api/v1/apps/" + slug + } + if bp.Meta.Name == "" { + bp.Meta.Name = slug + } + if bp.Version == "" { + bp.Version = "1.0" + } + if bp.Storage.Mode == "" { + bp.Storage.Mode = "schema_per_app" + } + if bp.Storage.Engine == "" { + bp.Storage.Engine = "postgres" + } + // 草稿允许尚未完全合法;尽量校验,失败则仍以宽松方式保存关键字段 + _ = bp.Validate(slug) + + now := time.Now().UTC() + appID := uuid.NewString() + schemaName := bp.AssignSchemaName(tenantID) + if existing, err := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug); err == nil && existing != nil { + if existing.Status == meta.StatusPublished { + return nil, fmt.Errorf("module already published: %s (use publish to update pages)", slug) + } + appID = existing.AppID + now = existing.CreatedAt + if existing.SchemaName != "" { + schemaName = existing.SchemaName + bp.Storage.SchemaName = existing.SchemaName + } + } + rec := &meta.AppRecord{ + AppID: appID, + TenantID: tenantID, + Slug: slug, + Name: bp.Meta.Name, + SchemaName: schemaName, + Engine: bp.Storage.Engine, + Status: meta.StatusDraft, + Blueprint: bp, + CreatedAt: now, + UpdatedAt: time.Now().UTC(), + } + if err := l.svcCtx.Meta.Save(l.ctx, rec); err != nil { + return nil, err + } + _ = l.audit("draft.save", audit.DetailJSON(map[string]any{"slug": slug, "user_id": authx.UserID(l.ctx)})) + return &types.AppListItem{ + AppID: rec.AppID, + Slug: slug, + Name: rec.Name, + Status: string(meta.StatusDraft), + StatusLabel: meta.StatusLabelCN(meta.StatusDraft), + Building: true, + SchemaName: schemaName, + PageCount: len(bp.Pages), + EntityCount: len(bp.Entities), + UpdatedAt: rec.UpdatedAt.UTC().Format(time.RFC3339), + CreatedAt: rec.CreatedAt.UTC().Format(time.RFC3339), + }, nil +} + +func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.PublishResp, error) { + tenantID := authx.TenantID(l.ctx) + userID := authx.UserID(l.ctx) + + incoming, err := blueprint.Parse(req.Blueprint) + if err != nil { + return nil, err + } + slug = blueprint.NormalizeIdent(slug) + incoming.Meta.Slug = blueprint.NormalizeIdent(incoming.Meta.Slug) + if incoming.Meta.Slug == "" { + incoming.Meta.Slug = slug + } + if slug == "" { + slug = incoming.Meta.Slug + } + // 路径与蓝图不一致时以路径 slug 为准(先选应用再发布) + if slug != "" && slug != incoming.Meta.Slug { + incoming.Meta.Slug = slug + } + if incoming.Apis.BasePath == "" || strings.Contains(incoming.Apis.BasePath, "-") { + incoming.Apis.BasePath = "/api/v1/apps/" + slug + } + + mode := strings.ToLower(strings.TrimSpace(req.Mode)) + if mode == "" { + mode = "auto" + } + if mode == "merge" { + mode = "add_pages" // 兼容旧别名 + } + switch mode { + case "auto", "add_pages", "create", "replace": + default: + return nil, fmt.Errorf("unsupported publish mode: %s (use auto|add_pages|create|replace)", mode) + } + + existing, existErr := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug) + exists := existErr == nil && existing != nil + + publishMode := "created" + var mergeRes *blueprint.MergeResult + var bp *blueprint.Blueprint + + switch { + case mode == "create": + if exists { + return nil, fmt.Errorf("app already exists: %s (select it and publish with mode=add_pages to add newly generated pages)", slug) + } + bp = incoming + publishMode = "created" + case mode == "add_pages": + if !exists { + return nil, fmt.Errorf("app not found: %s (create it first, or use mode=create/auto)", slug) + } + if existing.Blueprint == nil { + return nil, fmt.Errorf("existing app has no blueprint") + } + bp = existing.Blueprint + bp.Meta.Slug = slug + bp.Apis.BasePath = "/api/v1/apps/" + slug + mergeRes, err = blueprint.MergeInto(bp, incoming) + if err != nil { + return nil, err + } + publishMode = "pages_added" + case mode == "replace": + bp = incoming + if exists { + publishMode = "replaced" + } else { + publishMode = "created" + } + default: // auto + if exists { + if existing.Blueprint == nil { + return nil, fmt.Errorf("existing app has no blueprint") + } + bp = existing.Blueprint + bp.Meta.Slug = slug + bp.Apis.BasePath = "/api/v1/apps/" + slug + mergeRes, err = blueprint.MergeInto(bp, incoming) + if err != nil { + return nil, err + } + publishMode = "pages_added" + } else { + bp = incoming + publishMode = "created" + } + } + + if err := bp.Validate(slug); err != nil { + return nil, err + } + + schemaName := bp.AssignSchemaName(tenantID) + dbName := bp.AssignDatabaseName(tenantID) + ddl, err := schema.BuildPostgresDDL(bp) + if err != nil { + return nil, err + } + + appID := uuid.NewString() + now := time.Now().UTC() + republish := false + if exists { + appID = existing.AppID + now = existing.CreatedAt + republish = existing.Status == meta.StatusPublished + if existing.SchemaName != "" { + schemaName = existing.SchemaName + bp.Storage.SchemaName = existing.SchemaName + } + if existing.DatabaseName != "" { + dbName = existing.DatabaseName + } + ddl, err = schema.BuildPostgresDDL(bp) + if err != nil { + return nil, err + } + } + rec := &meta.AppRecord{ + AppID: appID, + TenantID: tenantID, + Slug: slug, + Name: bp.Meta.Name, + SchemaName: schemaName, + DatabaseName: dbName, + Engine: bp.Storage.Engine, + Status: meta.StatusProvisioning, + Blueprint: bp, + DDL: ddl, + CreatedAt: now, + UpdatedAt: time.Now().UTC(), + } + if err := l.svcCtx.Meta.Save(l.ctx, rec); err != nil { + return nil, fmt.Errorf("meta save: %w", err) + } + + runner := l.svcCtx.Schema + var appDB *sql.DB + if dbName != "" { + if err := runner.EnsureDatabase(l.ctx, dbName); err != nil { + rec.Status = meta.StatusFailed + rec.Error = err.Error() + _ = l.svcCtx.Meta.Save(l.ctx, rec) + _ = l.audit("publish.failed", audit.DetailJSON(map[string]any{"slug": slug, "error": err.Error()})) + return nil, fmt.Errorf("ensure database: %w", err) + } + if l.svcCtx.Config.DataSource != "" { + dsn, err := schema.DSNForDatabase(l.svcCtx.Config.DataSource, dbName) + if err != nil { + return nil, err + } + appDB, err = sql.Open("postgres", dsn) + if err != nil { + return nil, err + } + defer appDB.Close() + runner = &schema.PostgresRunner{DB: appDB} + if l.svcCtx.DBPool != nil { + _, _ = l.svcCtx.DBPool.ForApp(rec) + } + } + } + + if err := runner.ExecDDL(l.ctx, ddl); err != nil { + rec.Status = meta.StatusFailed + rec.Error = err.Error() + rec.UpdatedAt = time.Now().UTC() + _ = l.svcCtx.Meta.Save(l.ctx, rec) + _ = l.audit("publish.failed", audit.DetailJSON(map[string]any{"slug": slug, "error": err.Error()})) + return nil, fmt.Errorf("provision failed: %w", err) + } + + endpoints := buildEndpoints(bp) + rec.Status = meta.StatusPublished + rec.Endpoints = endpoints + rec.Error = "" + rec.UpdatedAt = time.Now().UTC() + if err := l.svcCtx.Meta.Save(l.ctx, rec); err != nil { + return nil, err + } + action := "publish.success" + if republish { + action = "publish.republish" + } + detail := map[string]any{ + "slug": slug, "schema": schemaName, "database": dbName, "user_id": userID, + "republish": republish, "publish_mode": publishMode, + } + if mergeRes != nil { + detail["added_pages"] = mergeRes.AddedPages + detail["added_entities"] = mergeRes.AddedEntities + } + _ = l.audit(action, audit.DetailJSON(detail)) + + // 智能体新建发布:自动把该 slug 写入可访问模块,后续不必再去控制台授权 + if authx.Role(l.ctx) == authx.RoleAgent && l.svcCtx.Agents != nil && slug != "" { + if aid := authx.AgentID(l.ctx); aid > 0 { + _ = l.svcCtx.Agents.GrantAppSlug(l.ctx, aid, slug) + } + } + + ownerID := authx.AgentID(l.ctx) + if ownerID <= 0 { + ownerID = userID + } + secret := l.svcCtx.Config.Agent.CapsuleSecret + if secret == "" { + secret = l.svcCtx.JWT.AccessSecret + } + accessPath := "" + accessURL := "" + if secret != "" && ownerID > 0 { + _, filePath, err := agentcap.SealModulePath(secret, tenantID, ownerID, slug) + if err == nil { + accessPath = filePath + base := strings.TrimRight(l.svcCtx.Config.PublicBaseURL, "/") + if req.HostMeta != nil && strings.TrimSpace(req.HostMeta.HostBaseURL) != "" { + accessURL = strings.TrimRight(strings.TrimSpace(req.HostMeta.HostBaseURL), "/") + } else if base != "" { + accessURL = base + "/api/v1/public/" + filePath + "/blueprint" + } + } + } + + moduleName := bp.Meta.Name + publishStyle := "" + if req.HostMeta != nil { + if n := strings.TrimSpace(req.HostMeta.ModuleName); n != "" { + moduleName = n + } + publishStyle = strings.TrimSpace(req.HostMeta.PublishStyle) + } + if publishStyle == "" { + publishStyle = "immediate" + } + + resp := &types.PublishResp{ + AppID: rec.AppID, + Slug: slug, + SchemaName: schemaName, + DatabaseName: dbName, + Status: string(meta.StatusPublished), + Endpoints: endpoints, + DDL: ddl, + MemoryMode: l.svcCtx.MemoryMode, + PublishMode: publishMode, + ModuleName: moduleName, + PublishStyle: publishStyle, + AccessPath: accessPath, + AccessURL: accessURL, + PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339), + OwnerID: ownerID, + } + if mergeRes != nil { + resp.AddedPages = mergeRes.AddedPages + resp.AddedEntities = mergeRes.AddedEntities + resp.AddedResources = mergeRes.AddedResources + } + return resp, nil +} + +func (l *PublishLogic) audit(action, detail string) error { + if l.svcCtx.Audit == nil { + return nil + } + return l.svcCtx.Audit.Log(l.ctx, authx.TenantID(l.ctx), authx.UserID(l.ctx), action, detail) +} + +func buildEndpoints(bp *blueprint.Blueprint) []string { + base := strings.TrimRight(bp.Apis.BasePath, "/") + if base == "" { + base = "/api/v1/apps/" + bp.Meta.Slug + } + out := []string{"GET " + base + "/blueprint"} + for _, r := range bp.Apis.Resources { + path := r.Path + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + full := base + path + for _, op := range r.Operations { + switch op { + case "list": + out = append(out, "GET "+full) + case "create": + out = append(out, "POST "+full) + case "get": + out = append(out, "GET "+full+"/{id}") + case "update": + out = append(out, "PUT "+full+"/{id}") + case "delete": + out = append(out, "DELETE "+full+"/{id}") + case "import": + out = append(out, "POST "+full+"/import") + case "export": + out = append(out, "GET "+full+"/export") + } + } + } + return out +} diff --git a/platform/internal/logic/applogic/roles.go b/platform/internal/logic/applogic/roles.go new file mode 100644 index 0000000..270db4a --- /dev/null +++ b/platform/internal/logic/applogic/roles.go @@ -0,0 +1,93 @@ +package applogic + +import ( + "context" + "fmt" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/rolestore" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/tenantperm" + "aijianzhan/platform/internal/types" +) + +type RoleAdminLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewRoleAdminLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RoleAdminLogic { + return &RoleAdminLogic{ctx: ctx, svcCtx: svcCtx} +} + +func (l *RoleAdminLogic) store() (rolestore.Store, error) { + if l.svcCtx.Roles == nil { + return nil, fmt.Errorf("role store unavailable") + } + return l.svcCtx.Roles, nil +} + +func (l *RoleAdminLogic) List() ([]rolestore.Role, error) { + st, err := l.store() + if err != nil { + return nil, err + } + tid := authx.TenantID(l.ctx) + _ = st.EnsureDefaults(l.ctx, tid) + return st.List(l.ctx, tid) +} + +func (l *RoleAdminLogic) Get(roleID int64) (*rolestore.Role, error) { + st, err := l.store() + if err != nil { + return nil, err + } + return st.Get(l.ctx, authx.TenantID(l.ctx), roleID) +} + +func (l *RoleAdminLogic) Create(req *types.RoleCreateReq) (*rolestore.Role, error) { + st, err := l.store() + if err != nil { + return nil, err + } + perms := authx.NormalizePerms(req.Permissions) + if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { + return nil, err + } + return st.Create(l.ctx, authx.TenantID(l.ctx), rolestore.CreateInput{ + Code: req.Code, + Name: req.Name, + Description: req.Description, + Permissions: perms, + }) +} + +func (l *RoleAdminLogic) Update(roleID int64, req *types.RoleUpdateReq) (*rolestore.Role, error) { + st, err := l.store() + if err != nil { + return nil, err + } + in := rolestore.UpdateInput{} + if req.Name != nil { + in.Name = req.Name + } + if req.Description != nil { + in.Description = req.Description + } + if req.Permissions != nil { + perms := authx.NormalizePerms(*req.Permissions) + if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { + return nil, err + } + in.Permissions = &perms + } + return st.Update(l.ctx, authx.TenantID(l.ctx), roleID, in) +} + +func (l *RoleAdminLogic) Delete(roleID int64) error { + st, err := l.store() + if err != nil { + return err + } + return st.Delete(l.ctx, authx.TenantID(l.ctx), roleID) +} diff --git a/platform/internal/logic/applogic/tenant_invite.go b/platform/internal/logic/applogic/tenant_invite.go new file mode 100644 index 0000000..ceab2cc --- /dev/null +++ b/platform/internal/logic/applogic/tenant_invite.go @@ -0,0 +1,154 @@ +package applogic + +import ( + "context" + "fmt" + "strings" + "time" + + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/invitestore" + "aijianzhan/platform/internal/svc" + "aijianzhan/platform/internal/types" + "aijianzhan/platform/internal/userstore" +) + +func (l *AuthLogic) issueUser(u *userstore.User) (*types.TokenResp, error) { + resp, err := l.issue(u.TenantID, u.UserID, u.Role, u.Username, u.DisplayName, u.OrgUnitID) + if err != nil { + return nil, err + } + resp.Phone = u.Phone + resp.UsernameLoginDisabled = u.UsernameLoginDisabled + resp.Status = u.Status + if resp.Status == "" { + if u.HasTenant() || u.IsPlatformAdmin() { + resp.Status = userstore.StatusActive + } else { + resp.Status = userstore.StatusPending + } + } + if u.IsPlatformAdmin() { + resp.Message = "平台超级管理员工作台:管理全部公司;打开某公司可查看其内部功能" + return resp, nil + } + if !u.HasTenant() { + resp.Message = "账号待加入租户:请使用邀请码加入公司,或创建自己的公司" + } + return resp, nil +} + +func (l *AuthLogic) AcceptInvite(req *types.InviteAcceptReq) (*types.TokenResp, error) { + if l.svcCtx.Users == nil || l.svcCtx.Invites == nil { + return nil, fmt.Errorf("invite store unavailable") + } + code := strings.TrimSpace(req.Code) + if code == "" { + return nil, fmt.Errorf("invite code required") + } + userID := authx.UserID(l.ctx) + cur, err := l.svcCtx.Users.GetByID(l.ctx, userID) + if err != nil { + return nil, err + } + if cur.HasTenant() { + return nil, fmt.Errorf("already joined a tenant") + } + inv, err := l.svcCtx.Invites.GetByCode(l.ctx, code) + if err != nil { + return nil, fmt.Errorf("invalid invite code") + } + if err := invitestore.ValidateUsable(inv); err != nil { + return nil, err + } + if inv.OrgUnitID > 0 && l.svcCtx.OrgUnits != nil { + if _, err := l.svcCtx.OrgUnits.Get(l.ctx, inv.TenantID, inv.OrgUnitID); err != nil { + return nil, fmt.Errorf("invite org unit invalid") + } + } + if err := l.svcCtx.Invites.Consume(l.ctx, inv.InviteID); err != nil { + return nil, err + } + u, err := l.svcCtx.Users.JoinTenant(l.ctx, userID, inv.TenantID, inv.Role, inv.OrgUnitID) + if err != nil { + return nil, err + } + if l.svcCtx.Roles != nil { + _ = l.svcCtx.Roles.EnsureDefaults(l.ctx, u.TenantID) + } + resp, err := l.issueUser(u) + if err != nil { + return nil, err + } + resp.Message = "已加入租户" + return resp, nil +} + +func (l *AuthLogic) CreateTenant(req *types.TenantCreateReq) (*types.TokenResp, error) { + if l.svcCtx.Users == nil { + return nil, fmt.Errorf("user store unavailable") + } + userID := authx.UserID(l.ctx) + u, err := l.svcCtx.Users.CreateTenantAsOwner(l.ctx, userID, req.Name) + if err != nil { + return nil, err + } + if l.svcCtx.Roles != nil { + _ = l.svcCtx.Roles.EnsureDefaults(l.ctx, u.TenantID) + } + if l.svcCtx.TenantPerm != nil { + _ = l.svcCtx.TenantPerm.EnsureDefault(l.ctx, u.TenantID) + } + resp, err := l.issueUser(u) + if err != nil { + return nil, err + } + resp.Message = "已创建公司并成为管理员" + return resp, nil +} + +type InviteAdminLogic struct { + AuthLogic +} + +func NewInviteAdminLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InviteAdminLogic { + return &InviteAdminLogic{AuthLogic: AuthLogic{ctx: ctx, svcCtx: svcCtx}} +} + +func (l *InviteAdminLogic) List() ([]invitestore.Invite, error) { + if l.svcCtx.Invites == nil { + return nil, fmt.Errorf("invite store unavailable") + } + return l.svcCtx.Invites.List(l.ctx, authx.TenantID(l.ctx)) +} + +func (l *InviteAdminLogic) Create(req *types.InviteCreateReq) (*invitestore.Invite, error) { + if l.svcCtx.Invites == nil { + return nil, fmt.Errorf("invite store unavailable") + } + tid := authx.TenantID(l.ctx) + if req.OrgUnitID > 0 { + if l.svcCtx.OrgUnits == nil { + return nil, fmt.Errorf("org unit store unavailable") + } + if _, err := l.svcCtx.OrgUnits.Get(l.ctx, tid, req.OrgUnitID); err != nil { + return nil, fmt.Errorf("invalid org_unit_id") + } + } + in := invitestore.CreateInput{ + Role: req.Role, + OrgUnitID: req.OrgUnitID, + MaxUses: req.MaxUses, + } + if req.ExpiresInHours > 0 { + in.ExpiresIn = time.Duration(req.ExpiresInHours) * time.Hour + } + return l.svcCtx.Invites.Create(l.ctx, tid, authx.UserID(l.ctx), in) +} + +func (l *InviteAdminLogic) Revoke(inviteID int64) error { + if l.svcCtx.Invites == nil { + return fmt.Errorf("invite store unavailable") + } + return l.svcCtx.Invites.Revoke(l.ctx, authx.TenantID(l.ctx), inviteID) +} diff --git a/platform/internal/meta/migrate.go b/platform/internal/meta/migrate.go new file mode 100644 index 0000000..55ecf2d --- /dev/null +++ b/platform/internal/meta/migrate.go @@ -0,0 +1,216 @@ +package meta + +import ( + "context" + "database/sql" + "fmt" +) + +const metaSchemaSQL = ` +CREATE SCHEMA IF NOT EXISTS platform_meta; + +CREATE TABLE IF NOT EXISTS platform_meta.tenants ( + tenant_id BIGSERIAL PRIMARY KEY, + name VARCHAR(128) NOT NULL, + slug VARCHAR(64) NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS platform_meta.users ( + user_id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT REFERENCES platform_meta.tenants(tenant_id), + username VARCHAR(64) NOT NULL, + password_hash TEXT NOT NULL, + display_name VARCHAR(128) NOT NULL DEFAULT '', + role VARCHAR(32) NOT NULL DEFAULT 'pending', + status VARCHAR(32) NOT NULL DEFAULT 'pending', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_users_username UNIQUE (username) +); + +CREATE INDEX IF NOT EXISTS idx_users_tenant ON platform_meta.users (tenant_id); + +-- 兼容旧库:允许无租户(pending) +ALTER TABLE platform_meta.users ALTER COLUMN tenant_id DROP NOT NULL; +ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS status VARCHAR(32) NOT NULL DEFAULT 'active'; + +CREATE TABLE IF NOT EXISTS platform_meta.tenant_apps ( + app_id UUID PRIMARY KEY, + tenant_id BIGINT NOT NULL, + slug VARCHAR(64) NOT NULL, + name VARCHAR(128) NOT NULL, + schema_name VARCHAR(64) NOT NULL, + engine VARCHAR(32) NOT NULL, + status VARCHAR(32) NOT NULL, + blueprint_json JSONB NOT NULL, + ddl_json JSONB NOT NULL DEFAULT '[]'::jsonb, + endpoints_json JSONB NOT NULL DEFAULT '[]'::jsonb, + error_msg TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_tenant_apps_tenant_slug UNIQUE (tenant_id, slug) +); + +CREATE INDEX IF NOT EXISTS idx_tenant_apps_tenant_status + ON platform_meta.tenant_apps (tenant_id, status); + +ALTER TABLE platform_meta.tenant_apps + ADD COLUMN IF NOT EXISTS database_name VARCHAR(64) NOT NULL DEFAULT ''; + +CREATE TABLE IF NOT EXISTS platform_meta.audit_logs ( + id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + action VARCHAR(64) NOT NULL, + detail TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 智能体服务账号(机器身份,供宿主自动登录) +CREATE TABLE IF NOT EXISTS platform_meta.agent_accounts ( + agent_id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id), + name VARCHAR(128) NOT NULL, + client_id VARCHAR(64) NOT NULL, + client_secret_hash TEXT NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'active', + created_by BIGINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + last_token_at TIMESTAMPTZ, + CONSTRAINT uq_agent_client_id UNIQUE (client_id) +); + +CREATE INDEX IF NOT EXISTS idx_agent_accounts_tenant + ON platform_meta.agent_accounts (tenant_id); + +ALTER TABLE platform_meta.agent_accounts + ADD COLUMN IF NOT EXISTS host_key VARCHAR(128) NOT NULL DEFAULT ''; + +CREATE UNIQUE INDEX IF NOT EXISTS uq_agent_tenant_host_key + ON platform_meta.agent_accounts (tenant_id, host_key) + WHERE host_key <> ''; + +CREATE TABLE IF NOT EXISTS platform_meta.agent_permissions ( + agent_id BIGINT NOT NULL REFERENCES platform_meta.agent_accounts(agent_id) ON DELETE CASCADE, + perm VARCHAR(64) NOT NULL, + PRIMARY KEY (agent_id, perm) +); + +CREATE TABLE IF NOT EXISTS platform_meta.agent_app_grants ( + agent_id BIGINT NOT NULL REFERENCES platform_meta.agent_accounts(agent_id) ON DELETE CASCADE, + slug VARCHAR(64) NOT NULL, + PRIMARY KEY (agent_id, slug) +); + +-- 可编辑角色(租户级) +CREATE TABLE IF NOT EXISTS platform_meta.roles ( + role_id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id), + code VARCHAR(64) NOT NULL, + name VARCHAR(128) NOT NULL, + description TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_roles_tenant_code UNIQUE (tenant_id, code) +); + +CREATE INDEX IF NOT EXISTS idx_roles_tenant ON platform_meta.roles (tenant_id); + +CREATE TABLE IF NOT EXISTS platform_meta.role_permissions ( + role_id BIGINT NOT NULL REFERENCES platform_meta.roles(role_id) ON DELETE CASCADE, + perm VARCHAR(64) NOT NULL, + PRIMARY KEY (role_id, perm) +); + +ALTER TABLE platform_meta.agent_accounts + ADD COLUMN IF NOT EXISTS role_id BIGINT; + +-- 租户邀请码:pending 用户凭码加入已有公司 +CREATE TABLE IF NOT EXISTS platform_meta.tenant_invites ( + invite_id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE, + code VARCHAR(64) NOT NULL, + role VARCHAR(32) NOT NULL DEFAULT 'editor', + created_by BIGINT NOT NULL DEFAULT 0, + max_uses INT NOT NULL DEFAULT 1, + used_count INT NOT NULL DEFAULT 0, + status VARCHAR(32) NOT NULL DEFAULT 'active', + expires_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_tenant_invites_code UNIQUE (code) +); + +CREATE INDEX IF NOT EXISTS idx_tenant_invites_tenant + ON platform_meta.tenant_invites (tenant_id); + +-- 租户内多级组织(默认最多 5 级) +CREATE TABLE IF NOT EXISTS platform_meta.org_units ( + org_unit_id BIGSERIAL PRIMARY KEY, + tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE, + parent_id BIGINT REFERENCES platform_meta.org_units(org_unit_id) ON DELETE CASCADE, + name VARCHAR(128) NOT NULL, + code VARCHAR(64) NOT NULL DEFAULT '', + depth INT NOT NULL DEFAULT 1, + path VARCHAR(512) NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_org_units_tenant_parent + ON platform_meta.org_units (tenant_id, parent_id); + +CREATE INDEX IF NOT EXISTS idx_org_units_tenant_path + ON platform_meta.org_units (tenant_id, path); + +CREATE UNIQUE INDEX IF NOT EXISTS uq_org_units_tenant_code + ON platform_meta.org_units (tenant_id, code) + WHERE code <> ''; + +ALTER TABLE platform_meta.users + ADD COLUMN IF NOT EXISTS org_unit_id BIGINT; + +ALTER TABLE platform_meta.tenant_invites + ADD COLUMN IF NOT EXISTS org_unit_id BIGINT NOT NULL DEFAULT 0; + +ALTER TABLE platform_meta.users + ALTER COLUMN role TYPE VARCHAR(64); + +CREATE TABLE IF NOT EXISTS platform_meta.tenant_permissions ( + tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE, + perm VARCHAR(64) NOT NULL, + PRIMARY KEY (tenant_id, perm) +); + +CREATE TABLE IF NOT EXISTS platform_meta.tenant_entitlement_state ( + tenant_id BIGINT PRIMARY KEY REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 公司路径 slug(www.example.com/{slug}/);旧库补列 +ALTER TABLE platform_meta.tenants ADD COLUMN IF NOT EXISTS slug VARCHAR(64) NOT NULL DEFAULT ''; +CREATE UNIQUE INDEX IF NOT EXISTS uq_tenants_slug ON platform_meta.tenants (slug) WHERE slug <> ''; + +-- 手机号(可绑定;非空时全局唯一,可用于登录) +ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS phone VARCHAR(20) NOT NULL DEFAULT ''; +CREATE UNIQUE INDEX IF NOT EXISTS uq_users_phone ON platform_meta.users (phone) WHERE phone <> ''; + +-- 已绑手机时可禁用用户名登录(仅手机号+密码/短信) +ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS username_login_disabled BOOLEAN NOT NULL DEFAULT false; + +-- 授权租约已消费 id(删 leases 目录后仍能拦截旧延期包) +CREATE TABLE IF NOT EXISTS platform_meta.license_consumed ( + lease_id VARCHAR(64) PRIMARY KEY, + active BOOLEAN NOT NULL DEFAULT false, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX IF NOT EXISTS idx_license_consumed_active ON platform_meta.license_consumed (active) WHERE active; +` + +// EnsureSchema 创建平台元数据表(幂等)。 +func EnsureSchema(ctx context.Context, db *sql.DB) error { + if db == nil { + return fmt.Errorf("db is nil") + } + if _, err := db.ExecContext(ctx, metaSchemaSQL); err != nil { + return fmt.Errorf("meta migrate: %w", err) + } + return nil +} diff --git a/platform/internal/meta/postgres.go b/platform/internal/meta/postgres.go new file mode 100644 index 0000000..6e9515e --- /dev/null +++ b/platform/internal/meta/postgres.go @@ -0,0 +1,206 @@ +package meta + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "time" + + "aijianzhan/platform/internal/blueprint" + + "github.com/lib/pq" +) + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { + return &PostgresStore{DB: db} +} + +func (s *PostgresStore) GetBySlug(ctx context.Context, tenantID int64, slug string) (*AppRecord, error) { + const q = ` +SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status, + blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at +FROM platform_meta.tenant_apps +WHERE tenant_id = $1 AND slug = $2 +LIMIT 1` + row := s.DB.QueryRowContext(ctx, q, tenantID, slug) + rec, err := scanApp(row) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("app not found") + } + return rec, err +} + +func (s *PostgresStore) FindPublishedBySlug(ctx context.Context, slug string) (*AppRecord, error) { + const q = ` +SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status, + blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at +FROM platform_meta.tenant_apps +WHERE slug = $1 AND status = $2 +ORDER BY updated_at DESC +LIMIT 1` + row := s.DB.QueryRowContext(ctx, q, slug, string(StatusPublished)) + rec, err := scanApp(row) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("app not found") + } + return rec, err +} + +func (s *PostgresStore) ListByTenant(ctx context.Context, tenantID int64) ([]AppSummary, error) { + const q = ` +SELECT app_id, tenant_id, slug, name, schema_name, COALESCE(database_name,''), engine, status, + blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at +FROM platform_meta.tenant_apps +WHERE tenant_id = $1 +ORDER BY updated_at DESC` + rows, err := s.DB.QueryContext(ctx, q, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + out := make([]AppSummary, 0) + for rows.Next() { + rec, err := scanApp(rows) + if err != nil { + return nil, err + } + out = append(out, summarizeApp(rec)) + } + return out, rows.Err() +} + +func (s *PostgresStore) Save(ctx context.Context, app *AppRecord) error { + if app == nil { + return fmt.Errorf("app is nil") + } + if app.Blueprint == nil { + return fmt.Errorf("blueprint is nil") + } + bpRaw, err := json.Marshal(app.Blueprint) + if err != nil { + return err + } + ddlRaw, err := json.Marshal(app.DDL) + if err != nil { + return err + } + epRaw, err := json.Marshal(app.Endpoints) + if err != nil { + return err + } + if app.CreatedAt.IsZero() { + app.CreatedAt = time.Now().UTC() + } + app.UpdatedAt = time.Now().UTC() + + const q = ` +INSERT INTO platform_meta.tenant_apps ( + app_id, tenant_id, slug, name, schema_name, database_name, engine, status, + blueprint_json, ddl_json, endpoints_json, error_msg, created_at, updated_at +) VALUES ( + $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14 +) +ON CONFLICT (tenant_id, slug) DO UPDATE SET + name = EXCLUDED.name, + schema_name = EXCLUDED.schema_name, + database_name = EXCLUDED.database_name, + engine = EXCLUDED.engine, + status = EXCLUDED.status, + blueprint_json = EXCLUDED.blueprint_json, + ddl_json = EXCLUDED.ddl_json, + endpoints_json = EXCLUDED.endpoints_json, + error_msg = EXCLUDED.error_msg, + updated_at = EXCLUDED.updated_at, + app_id = platform_meta.tenant_apps.app_id, + created_at = platform_meta.tenant_apps.created_at +RETURNING app_id, created_at, updated_at` + + err = s.DB.QueryRowContext(ctx, q, + app.AppID, + app.TenantID, + app.Slug, + app.Name, + app.SchemaName, + app.DatabaseName, + app.Engine, + string(app.Status), + bpRaw, + ddlRaw, + epRaw, + app.Error, + app.CreatedAt, + app.UpdatedAt, + ).Scan(&app.AppID, &app.CreatedAt, &app.UpdatedAt) + if err != nil { + return fmt.Errorf("meta save: %w", err) + } + return nil +} + +func (s *PostgresStore) ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) { + app, err := s.GetBySlug(ctx, tenantID, slug) + if err != nil { + return nil, err + } + return resolveResource(app, resource) +} + +type scannable interface { + Scan(dest ...any) error +} + +func scanApp(row scannable) (*AppRecord, error) { + var ( + rec AppRecord + status string + bpRaw, ddlRaw, epRaw []byte + errMsg sql.NullString + ) + err := row.Scan( + &rec.AppID, + &rec.TenantID, + &rec.Slug, + &rec.Name, + &rec.SchemaName, + &rec.DatabaseName, + &rec.Engine, + &status, + &bpRaw, + &ddlRaw, + &epRaw, + &errMsg, + &rec.CreatedAt, + &rec.UpdatedAt, + ) + if err != nil { + return nil, err + } + rec.Status = AppStatus(status) + if errMsg.Valid { + rec.Error = errMsg.String + } + var bp blueprint.Blueprint + if err := json.Unmarshal(bpRaw, &bp); err != nil { + return nil, fmt.Errorf("blueprint json: %w", err) + } + rec.Blueprint = &bp + if len(ddlRaw) > 0 { + _ = json.Unmarshal(ddlRaw, &rec.DDL) + } + if len(epRaw) > 0 { + _ = json.Unmarshal(epRaw, &rec.Endpoints) + } + return &rec, nil +} + +// IsUniqueViolation 便于上层识别冲突(预留)。 +func IsUniqueViolation(err error) bool { + var pqErr *pq.Error + return errors.As(err, &pqErr) && pqErr.Code == "23505" +} diff --git a/platform/internal/meta/postgres_test.go b/platform/internal/meta/postgres_test.go new file mode 100644 index 0000000..cb66dfc --- /dev/null +++ b/platform/internal/meta/postgres_test.go @@ -0,0 +1,126 @@ +package meta_test + +import ( + "context" + "database/sql" + "os" + "testing" + "time" + + "aijianzhan/platform/internal/blueprint" + "aijianzhan/platform/internal/meta" + + _ "github.com/lib/pq" +) + +func testDSN(t *testing.T) string { + t.Helper() + dsn := os.Getenv("PLATFORM_TEST_DSN") + if dsn == "" { + dsn = "postgres://platform:platform@127.0.0.1:5432/platform?sslmode=disable" + } + return dsn +} + +func TestPostgresMetaPersist(t *testing.T) { + db, err := sql.Open("postgres", testDSN(t)) + if err != nil { + t.Skip(err) + } + if err := db.Ping(); err != nil { + t.Skip(err) + } + defer db.Close() + + ctx := context.Background() + if err := meta.EnsureSchema(ctx, db); err != nil { + t.Fatal(err) + } + + store := meta.NewPostgresStore(db) + slug := "meta_persist_demo" + tenantID := int64(99001) + _, _ = db.ExecContext(ctx, `DELETE FROM platform_meta.tenant_apps WHERE tenant_id=$1 AND slug=$2`, tenantID, slug) + + n := false + bp := &blueprint.Blueprint{ + Version: "1.0", + Meta: blueprint.Meta{Name: "持久化测试", Slug: slug, Locale: "zh-CN"}, + Storage: blueprint.Storage{Mode: "schema_per_app", Engine: "postgres", SchemaName: "app_t99001_meta_persist_demo"}, + Entities: []blueprint.Entity{{ + Name: "item", Table: "item", Label: "Item", PrimaryKey: "id", + Fields: []blueprint.Field{ + {Name: "id", Type: "bigint", Label: "ID", Nullable: &n}, + {Name: "title", Type: "string", Label: "标题", Nullable: &n, MaxLength: 64}, + }, + }}, + Apis: blueprint.Apis{ + BasePath: "/api/v1/apps/" + slug, + Resources: []blueprint.APIResource{{ + Entity: "item", Path: "/items", + Operations: []string{"list", "get", "create"}, + }}, + }, + Pages: []blueprint.Page{{ + ID: "list", Title: "列表", Route: "/items", Type: "list", Entity: "item", + }}, + Security: blueprint.Security{ + Visibility: "private", + Roles: []blueprint.Role{{ + Name: "管理员", Permissions: []string{"读取模块", "写入模块", "发布模块", "新增数据", "查询数据"}, + }}, + RowPolicies: []blueprint.RowPolicy{{Entity: "item", Rule: "tenant_isolated"}}, + }, + } + + rec := &meta.AppRecord{ + AppID: "11111111-1111-1111-1111-111111111111", + TenantID: tenantID, + Slug: slug, + Name: bp.Meta.Name, + SchemaName: bp.Storage.SchemaName, + Engine: "postgres", + Status: meta.StatusPublished, + Blueprint: bp, + DDL: []string{"SELECT 1"}, + Endpoints: []string{"GET /api/v1/apps/" + slug + "/items"}, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + if err := store.Save(ctx, rec); err != nil { + t.Fatal(err) + } + + got, err := store.GetBySlug(ctx, tenantID, slug) + if err != nil { + t.Fatal(err) + } + if got.Status != meta.StatusPublished || got.Blueprint == nil || got.Blueprint.Meta.Name != "持久化测试" { + t.Fatalf("unexpected record: %+v", got) + } + + ref, err := store.ResolveResource(ctx, tenantID, slug, "items") + if err != nil { + t.Fatal(err) + } + if ref.Entity.Name != "item" { + t.Fatalf("entity=%s", ref.Entity.Name) + } + + // 二次 Save 应保留 app_id + rec.Name = "持久化测试-更新" + rec.AppID = "22222222-2222-2222-2222-222222222222" + if err := store.Save(ctx, rec); err != nil { + t.Fatal(err) + } + got2, err := store.GetBySlug(ctx, tenantID, slug) + if err != nil { + t.Fatal(err) + } + if got2.AppID != "11111111-1111-1111-1111-111111111111" { + t.Fatalf("app_id should be preserved, got %s", got2.AppID) + } + if got2.Name != "持久化测试-更新" { + t.Fatalf("name not updated: %s", got2.Name) + } +} diff --git a/platform/internal/meta/store.go b/platform/internal/meta/store.go new file mode 100644 index 0000000..0e45dba --- /dev/null +++ b/platform/internal/meta/store.go @@ -0,0 +1,217 @@ +package meta + +import ( + "context" + "encoding/json" + "fmt" + "sync" + "time" + + "aijianzhan/platform/internal/blueprint" +) + +type AppStatus string + +const ( + StatusDraft AppStatus = "draft" // 在建:已生成蓝图、尚未成功发布 + StatusValidating AppStatus = "validating" + StatusProvisioning AppStatus = "provisioning" + StatusPublished AppStatus = "published" + StatusFailed AppStatus = "failed" +) + +// StatusLabelCN 管理端展示用中文状态。 +func StatusLabelCN(s AppStatus) string { + switch s { + case StatusPublished: + return "已发布" + case StatusFailed: + return "失败" + case StatusDraft, StatusValidating, StatusProvisioning: + return "在建" + default: + if s == "" { + return "未知" + } + return string(s) + } +} + +// IsBuilding 是否视为在建(含草稿与发布中)。 +func IsBuilding(s AppStatus) bool { + return s == StatusDraft || s == StatusValidating || s == StatusProvisioning +} + +type AppRecord struct { + AppID string `json:"app_id"` + TenantID int64 `json:"tenant_id"` + Slug string `json:"slug"` + Name string `json:"name"` + SchemaName string `json:"schema_name"` + DatabaseName string `json:"database_name,omitempty"` + Engine string `json:"engine"` + Status AppStatus `json:"status"` + Blueprint *blueprint.Blueprint `json:"blueprint"` + DDL []string `json:"ddl,omitempty"` + Endpoints []string `json:"endpoints,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Error string `json:"error,omitempty"` +} + +type ResourceRef struct { + App *AppRecord + Entity blueprint.Entity + Resource blueprint.APIResource +} + +type AppSummary struct { + AppID string `json:"app_id"` + Slug string `json:"slug"` + Name string `json:"name"` + Status AppStatus `json:"status"` + SchemaName string `json:"schema_name,omitempty"` + PageCount int `json:"page_count"` + EntityCount int `json:"entity_count"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` +} + +type Store interface { + GetBySlug(ctx context.Context, tenantID int64, slug string) (*AppRecord, error) + FindPublishedBySlug(ctx context.Context, slug string) (*AppRecord, error) + ListByTenant(ctx context.Context, tenantID int64) ([]AppSummary, error) + Save(ctx context.Context, app *AppRecord) error + ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) +} + +type MemoryStore struct { + mu sync.RWMutex + apps map[string]*AppRecord // key: tenantID:slug +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{apps: map[string]*AppRecord{}} +} + +func key(tenantID int64, slug string) string { + return fmt.Sprintf("%d:%s", tenantID, slug) +} + +func (s *MemoryStore) GetBySlug(_ context.Context, tenantID int64, slug string) (*AppRecord, error) { + s.mu.RLock() + defer s.mu.RUnlock() + app, ok := s.apps[key(tenantID, slug)] + if !ok { + return nil, fmt.Errorf("app not found") + } + return cloneApp(app), nil +} + +func (s *MemoryStore) FindPublishedBySlug(_ context.Context, slug string) (*AppRecord, error) { + s.mu.RLock() + defer s.mu.RUnlock() + var best *AppRecord + for _, app := range s.apps { + if app == nil || app.Slug != slug || app.Status != StatusPublished { + continue + } + if best == nil || app.UpdatedAt.After(best.UpdatedAt) { + best = app + } + } + if best == nil { + return nil, fmt.Errorf("app not found") + } + return cloneApp(best), nil +} + +func (s *MemoryStore) ListByTenant(_ context.Context, tenantID int64) ([]AppSummary, error) { + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]AppSummary, 0) + for _, app := range s.apps { + if app == nil || app.TenantID != tenantID { + continue + } + out = append(out, summarizeApp(app)) + } + return out, nil +} + +func summarizeApp(app *AppRecord) AppSummary { + sum := AppSummary{ + AppID: app.AppID, + Slug: app.Slug, + Name: app.Name, + Status: app.Status, + SchemaName: app.SchemaName, + UpdatedAt: app.UpdatedAt, + CreatedAt: app.CreatedAt, + } + if app.Blueprint != nil { + sum.PageCount = len(app.Blueprint.Pages) + sum.EntityCount = len(app.Blueprint.Entities) + } + return sum +} + +func (s *MemoryStore) Save(_ context.Context, app *AppRecord) error { + s.mu.Lock() + defer s.mu.Unlock() + s.apps[key(app.TenantID, app.Slug)] = cloneApp(app) + return nil +} + +func (s *MemoryStore) ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) { + app, err := s.GetBySlug(ctx, tenantID, slug) + if err != nil { + return nil, err + } + return resolveResource(app, resource) +} + +func resolveResource(app *AppRecord, resource string) (*ResourceRef, error) { + if app.Status != StatusPublished { + return nil, fmt.Errorf("app not published") + } + if app.Blueprint == nil { + return nil, fmt.Errorf("blueprint missing") + } + for _, r := range app.Blueprint.Apis.Resources { + path := r.Path + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + if path != resource { + continue + } + for _, e := range app.Blueprint.Entities { + if e.Name == r.Entity { + return &ResourceRef{App: app, Entity: e, Resource: r}, nil + } + } + return nil, fmt.Errorf("entity missing for resource") + } + return nil, fmt.Errorf("resource not found") +} + +func cloneApp(app *AppRecord) *AppRecord { + if app == nil { + return nil + } + cp := *app + if app.Blueprint != nil { + raw, _ := json.Marshal(app.Blueprint) + var bp blueprint.Blueprint + _ = json.Unmarshal(raw, &bp) + cp.Blueprint = &bp + } + if app.DDL != nil { + cp.DDL = append([]string{}, app.DDL...) + } + if app.Endpoints != nil { + cp.Endpoints = append([]string{}, app.Endpoints...) + } + return &cp +} diff --git a/platform/internal/orgunitstore/store.go b/platform/internal/orgunitstore/store.go new file mode 100644 index 0000000..edbd97e --- /dev/null +++ b/platform/internal/orgunitstore/store.go @@ -0,0 +1,352 @@ +package orgunitstore + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "sync" + "time" +) + +// MaxDepth 组织树最大深度(公司下第 1 级为 depth=1)。 +const MaxDepth = 5 + +type OrgUnit struct { + OrgUnitID int64 `json:"org_unit_id"` + TenantID int64 `json:"tenant_id"` + ParentID int64 `json:"parent_id,omitempty"` + Name string `json:"name"` + Code string `json:"code,omitempty"` + Depth int `json:"depth"` + Path string `json:"path"` + CreatedAt time.Time `json:"created_at"` +} + +type CreateInput struct { + ParentID int64 + Name string + Code string +} + +type UpdateInput struct { + Name *string + Code *string +} + +type Store interface { + List(ctx context.Context, tenantID int64) ([]OrgUnit, error) + Get(ctx context.Context, tenantID, orgUnitID int64) (*OrgUnit, error) + Create(ctx context.Context, tenantID int64, in CreateInput) (*OrgUnit, error) + Update(ctx context.Context, tenantID, orgUnitID int64, in UpdateInput) (*OrgUnit, error) + Delete(ctx context.Context, tenantID, orgUnitID int64) error + // DescendantIDs 含自身。 + DescendantIDs(ctx context.Context, tenantID, orgUnitID int64) ([]int64, error) +} + +type MemoryStore struct { + mu sync.Mutex + byID map[int64]*OrgUnit + seq int64 +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{byID: map[int64]*OrgUnit{}} +} + +func (s *MemoryStore) List(_ context.Context, tenantID int64) ([]OrgUnit, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := make([]OrgUnit, 0) + for _, o := range s.byID { + if o.TenantID == tenantID { + out = append(out, *o) + } + } + return out, nil +} + +func (s *MemoryStore) Get(_ context.Context, tenantID, orgUnitID int64) (*OrgUnit, error) { + s.mu.Lock() + defer s.mu.Unlock() + o, ok := s.byID[orgUnitID] + if !ok || o.TenantID != tenantID { + return nil, fmt.Errorf("org unit not found") + } + cp := *o + return &cp, nil +} + +func (s *MemoryStore) Create(_ context.Context, tenantID int64, in CreateInput) (*OrgUnit, error) { + s.mu.Lock() + defer s.mu.Unlock() + name := strings.TrimSpace(in.Name) + if name == "" { + return nil, fmt.Errorf("name required") + } + code := strings.TrimSpace(in.Code) + depth := 1 + pathPrefix := "" + if in.ParentID > 0 { + p, ok := s.byID[in.ParentID] + if !ok || p.TenantID != tenantID { + return nil, fmt.Errorf("parent not found") + } + if p.Depth >= MaxDepth { + return nil, fmt.Errorf("max org depth is %d", MaxDepth) + } + depth = p.Depth + 1 + pathPrefix = p.Path + } + if code != "" { + for _, o := range s.byID { + if o.TenantID == tenantID && o.Code == code { + return nil, fmt.Errorf("org code already exists") + } + } + } + s.seq++ + id := s.seq + path := fmt.Sprintf("%s/%d", pathPrefix, id) + if pathPrefix == "" { + path = fmt.Sprintf("/%d", id) + } + o := &OrgUnit{ + OrgUnitID: id, + TenantID: tenantID, + ParentID: in.ParentID, + Name: name, + Code: code, + Depth: depth, + Path: path, + CreatedAt: time.Now().UTC(), + } + s.byID[id] = o + cp := *o + return &cp, nil +} + +func (s *MemoryStore) Update(_ context.Context, tenantID, orgUnitID int64, in UpdateInput) (*OrgUnit, error) { + s.mu.Lock() + defer s.mu.Unlock() + o, ok := s.byID[orgUnitID] + if !ok || o.TenantID != tenantID { + return nil, fmt.Errorf("org unit not found") + } + if in.Name != nil { + n := strings.TrimSpace(*in.Name) + if n == "" { + return nil, fmt.Errorf("name required") + } + o.Name = n + } + if in.Code != nil { + code := strings.TrimSpace(*in.Code) + for _, x := range s.byID { + if x.TenantID == tenantID && x.Code == code && x.OrgUnitID != orgUnitID { + return nil, fmt.Errorf("org code already exists") + } + } + o.Code = code + } + cp := *o + return &cp, nil +} + +func (s *MemoryStore) Delete(_ context.Context, tenantID, orgUnitID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + o, ok := s.byID[orgUnitID] + if !ok || o.TenantID != tenantID { + return fmt.Errorf("org unit not found") + } + for _, x := range s.byID { + if x.TenantID == tenantID && x.ParentID == orgUnitID { + return fmt.Errorf("org unit has children") + } + } + delete(s.byID, orgUnitID) + return nil +} + +func (s *MemoryStore) DescendantIDs(_ context.Context, tenantID, orgUnitID int64) ([]int64, error) { + s.mu.Lock() + defer s.mu.Unlock() + o, ok := s.byID[orgUnitID] + if !ok || o.TenantID != tenantID { + return nil, fmt.Errorf("org unit not found") + } + out := []int64{orgUnitID} + prefix := o.Path + "/" + for _, x := range s.byID { + if x.TenantID == tenantID && strings.HasPrefix(x.Path, prefix) { + out = append(out, x.OrgUnitID) + } + } + return out, nil +} + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { + return &PostgresStore{DB: db} +} + +func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]OrgUnit, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT org_unit_id, tenant_id, COALESCE(parent_id,0), name, COALESCE(code,''), depth, path, created_at +FROM platform_meta.org_units WHERE tenant_id=$1 +ORDER BY path`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + out := make([]OrgUnit, 0) + for rows.Next() { + var o OrgUnit + if err := rows.Scan(&o.OrgUnitID, &o.TenantID, &o.ParentID, &o.Name, &o.Code, &o.Depth, &o.Path, &o.CreatedAt); err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +func (s *PostgresStore) Get(ctx context.Context, tenantID, orgUnitID int64) (*OrgUnit, error) { + var o OrgUnit + err := s.DB.QueryRowContext(ctx, ` +SELECT org_unit_id, tenant_id, COALESCE(parent_id,0), name, COALESCE(code,''), depth, path, created_at +FROM platform_meta.org_units WHERE org_unit_id=$1 AND tenant_id=$2`, orgUnitID, tenantID, + ).Scan(&o.OrgUnitID, &o.TenantID, &o.ParentID, &o.Name, &o.Code, &o.Depth, &o.Path, &o.CreatedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("org unit not found") + } + return &o, err +} + +func (s *PostgresStore) Create(ctx context.Context, tenantID int64, in CreateInput) (*OrgUnit, error) { + name := strings.TrimSpace(in.Name) + if name == "" { + return nil, fmt.Errorf("name required") + } + code := strings.TrimSpace(in.Code) + depth := 1 + parentPath := "" + var parentAny any + if in.ParentID > 0 { + p, err := s.Get(ctx, tenantID, in.ParentID) + if err != nil { + return nil, fmt.Errorf("parent not found") + } + if p.Depth >= MaxDepth { + return nil, fmt.Errorf("max org depth is %d", MaxDepth) + } + depth = p.Depth + 1 + parentPath = p.Path + parentAny = in.ParentID + } + + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + + var id int64 + err = tx.QueryRowContext(ctx, ` +INSERT INTO platform_meta.org_units(tenant_id, parent_id, name, code, depth, path) +VALUES($1,$2,$3,$4,$5,'') +RETURNING org_unit_id`, tenantID, parentAny, name, code, depth).Scan(&id) + if err != nil { + if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "duplicate") { + return nil, fmt.Errorf("org code already exists") + } + return nil, err + } + path := fmt.Sprintf("/%d", id) + if parentPath != "" { + path = parentPath + "/" + fmt.Sprintf("%d", id) + } + if _, err := tx.ExecContext(ctx, `UPDATE platform_meta.org_units SET path=$1 WHERE org_unit_id=$2`, path, id); err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return s.Get(ctx, tenantID, id) +} + +func (s *PostgresStore) Update(ctx context.Context, tenantID, orgUnitID int64, in UpdateInput) (*OrgUnit, error) { + cur, err := s.Get(ctx, tenantID, orgUnitID) + if err != nil { + return nil, err + } + name, code := cur.Name, cur.Code + if in.Name != nil { + name = strings.TrimSpace(*in.Name) + if name == "" { + return nil, fmt.Errorf("name required") + } + } + if in.Code != nil { + code = strings.TrimSpace(*in.Code) + } + _, err = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.org_units SET name=$1, code=$2 WHERE org_unit_id=$3 AND tenant_id=$4`, + name, code, orgUnitID, tenantID) + if err != nil { + if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "duplicate") { + return nil, fmt.Errorf("org code already exists") + } + return nil, err + } + return s.Get(ctx, tenantID, orgUnitID) +} + +func (s *PostgresStore) Delete(ctx context.Context, tenantID, orgUnitID int64) error { + var n int + if err := s.DB.QueryRowContext(ctx, ` +SELECT COUNT(1) FROM platform_meta.org_units WHERE tenant_id=$1 AND parent_id=$2`, tenantID, orgUnitID).Scan(&n); err != nil { + return err + } + if n > 0 { + return fmt.Errorf("org unit has children") + } + res, err := s.DB.ExecContext(ctx, ` +DELETE FROM platform_meta.org_units WHERE org_unit_id=$1 AND tenant_id=$2`, orgUnitID, tenantID) + if err != nil { + return err + } + aff, _ := res.RowsAffected() + if aff == 0 { + return fmt.Errorf("org unit not found") + } + return nil +} + +func (s *PostgresStore) DescendantIDs(ctx context.Context, tenantID, orgUnitID int64) ([]int64, error) { + o, err := s.Get(ctx, tenantID, orgUnitID) + if err != nil { + return nil, err + } + rows, err := s.DB.QueryContext(ctx, ` +SELECT org_unit_id FROM platform_meta.org_units +WHERE tenant_id=$1 AND (org_unit_id=$2 OR path LIKE $3) +ORDER BY path`, tenantID, orgUnitID, o.Path+"/%") + if err != nil { + return nil, err + } + defer rows.Close() + out := make([]int64, 0) + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + out = append(out, id) + } + return out, rows.Err() +} diff --git a/platform/internal/ratelimit/limiter.go b/platform/internal/ratelimit/limiter.go new file mode 100644 index 0000000..b4c6423 --- /dev/null +++ b/platform/internal/ratelimit/limiter.go @@ -0,0 +1,84 @@ +package ratelimit + +import ( + "net/http" + "sync" + "time" + + "aijianzhan/platform/internal/authx" +) + +type Limiter struct { + mu sync.Mutex + visitors map[string]*visitor + rate int + window time.Duration +} + +type visitor struct { + count int + reset time.Time +} + +func New(ratePerMinute int) *Limiter { + if ratePerMinute <= 0 { + ratePerMinute = 120 + } + return &Limiter{ + visitors: map[string]*visitor{}, + rate: ratePerMinute, + window: time.Minute, + } +} + +func (l *Limiter) Middleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + key := r.RemoteAddr + if tid := authx.TenantID(r.Context()); tid > 0 { + key = "t:" + itoa(tid) + } + if !l.allow(key) { + authx.WriteError(w, http.StatusTooManyRequests, "rate limit exceeded") + return + } + next(w, r) + } +} + +func (l *Limiter) allow(key string) bool { + l.mu.Lock() + defer l.mu.Unlock() + now := time.Now() + v, ok := l.visitors[key] + if !ok || now.After(v.reset) { + l.visitors[key] = &visitor{count: 1, reset: now.Add(l.window)} + return true + } + if v.count >= l.rate { + return false + } + v.count++ + return true +} + +func itoa(n int64) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var b [20]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + b[i] = '-' + } + return string(b[i:]) +} diff --git a/platform/internal/rolestore/store.go b/platform/internal/rolestore/store.go new file mode 100644 index 0000000..9643b64 --- /dev/null +++ b/platform/internal/rolestore/store.go @@ -0,0 +1,419 @@ +package rolestore + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "sync" + "time" + + "aijianzhan/platform/internal/authx" +) + +type Role struct { + RoleID int64 `json:"role_id"` + TenantID int64 `json:"tenant_id"` + Code string `json:"code"` + Name string `json:"name"` + Description string `json:"description"` + Permissions []string `json:"permissions"` + CreatedAt time.Time `json:"created_at"` +} + +type CreateInput struct { + Code string + Name string + Description string + Permissions []string +} + +type UpdateInput struct { + Name *string + Description *string + Permissions *[]string +} + +type Store interface { + List(ctx context.Context, tenantID int64) ([]Role, error) + Get(ctx context.Context, tenantID, roleID int64) (*Role, error) + Create(ctx context.Context, tenantID int64, in CreateInput) (*Role, error) + Update(ctx context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) + Delete(ctx context.Context, tenantID, roleID int64) error + EnsureDefaults(ctx context.Context, tenantID int64) error +} + +var defaultRoles = []CreateInput{ + { + Code: authx.AgentRole生成发布, Name: "生成发布", Description: "生成蓝图、发布应用并导入业务数据(智能体常用最低权限)", + Permissions: []string{ + "读取模块", "发布模块", + "导入数据", "查询数据", + "上传文件", "下载文件", + }, + }, + { + Code: authx.AgentRole只读, Name: "只读", Description: "读应用与数据,可导出,不可改写", + Permissions: []string{"读取模块", "查询数据", "导出数据", "下载文件", "查看审计"}, + }, + { + Code: authx.AgentRole读写, Name: "读写", Description: "读写业务数据与导入导出,不可发布模块", + Permissions: []string{ + "读取模块", "写入模块", + "新增数据", "查询数据", "更新数据", "导出数据", "导入数据", + "上传文件", "下载文件", + }, + }, + { + Code: authx.AgentRole运维, Name: "运维", Description: "含发布、删除及全量行操作,适合运维类智能体", + Permissions: []string{ + "读取模块", "写入模块", "发布模块", + "新增数据", "查询数据", "更新数据", "删除数据", "导出数据", "导入数据", + "上传文件", "下载文件", "查看审计", + }, + }, +} + +type memRole struct{ Role } + +type MemoryStore struct { + mu sync.Mutex + seq int64 + by map[int64]*memRole +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{by: map[int64]*memRole{}} +} + +func findDefaultRole(byCode map[string]Role, d CreateInput) (Role, bool) { + if r, ok := byCode[d.Code]; ok { + return r, true + } + legacy := map[string]string{ + "publisher": authx.AgentRole生成发布, + "viewer": authx.AgentRole只读, + "editor": authx.AgentRole读写, + "operator": authx.AgentRole运维, + } + for eng, zh := range legacy { + if zh == d.Code { + if r, ok := byCode[eng]; ok { + return r, true + } + } + } + return Role{}, false +} + +func (s *MemoryStore) EnsureDefaults(ctx context.Context, tenantID int64) error { + list, err := s.List(ctx, tenantID) + if err != nil { + return err + } + byCode := map[string]Role{} + for _, r := range list { + byCode[r.Code] = r + } + for _, d := range defaultRoles { + if existing, ok := findDefaultRole(byCode, d); ok { + if d.Code == authx.AgentRole生成发布 { + perms := append([]string{}, d.Permissions...) + if _, err := s.Update(ctx, tenantID, existing.RoleID, UpdateInput{ + Name: &d.Name, + Description: &d.Description, + Permissions: &perms, + }); err != nil { + return err + } + } + continue + } + if _, err := s.Create(ctx, tenantID, d); err != nil { + return err + } + } + return nil +} + +func (s *MemoryStore) List(_ context.Context, tenantID int64) ([]Role, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := []Role{} + for _, r := range s.by { + if r.TenantID == tenantID { + out = append(out, clone(r.Role)) + } + } + return out, nil +} + +func (s *MemoryStore) Get(_ context.Context, tenantID, roleID int64) (*Role, error) { + s.mu.Lock() + defer s.mu.Unlock() + r, ok := s.by[roleID] + if !ok || r.TenantID != tenantID { + return nil, fmt.Errorf("role not found") + } + cp := clone(r.Role) + return &cp, nil +} + +func (s *MemoryStore) Create(_ context.Context, tenantID int64, in CreateInput) (*Role, error) { + s.mu.Lock() + defer s.mu.Unlock() + code := strings.TrimSpace(in.Code) + name := strings.TrimSpace(in.Name) + if code == "" || name == "" { + return nil, fmt.Errorf("code and name required") + } + for _, r := range s.by { + if r.TenantID == tenantID && r.Code == code { + return nil, fmt.Errorf("role code already exists") + } + } + s.seq++ + r := &memRole{Role: Role{ + RoleID: s.seq, + TenantID: tenantID, + Code: code, + Name: name, + Description: strings.TrimSpace(in.Description), + Permissions: uniq(in.Permissions), + CreatedAt: time.Now().UTC(), + }} + s.by[r.RoleID] = r + cp := clone(r.Role) + return &cp, nil +} + +func (s *MemoryStore) Update(_ context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) { + s.mu.Lock() + defer s.mu.Unlock() + r, ok := s.by[roleID] + if !ok || r.TenantID != tenantID { + return nil, fmt.Errorf("role not found") + } + if in.Name != nil { + r.Name = strings.TrimSpace(*in.Name) + } + if in.Description != nil { + r.Description = strings.TrimSpace(*in.Description) + } + if in.Permissions != nil { + r.Permissions = uniq(*in.Permissions) + } + cp := clone(r.Role) + return &cp, nil +} + +func (s *MemoryStore) Delete(_ context.Context, tenantID, roleID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + r, ok := s.by[roleID] + if !ok || r.TenantID != tenantID { + return fmt.Errorf("role not found") + } + delete(s.by, roleID) + return nil +} + +type PostgresStore struct{ DB *sql.DB } + +func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db} } + +func (s *PostgresStore) EnsureDefaults(ctx context.Context, tenantID int64) error { + list, err := s.List(ctx, tenantID) + if err != nil { + return err + } + byCode := map[string]Role{} + for _, r := range list { + byCode[r.Code] = r + } + for _, d := range defaultRoles { + if existing, ok := findDefaultRole(byCode, d); ok { + // 英文旧编码升级为中文 + if existing.Code != d.Code { + if _, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.roles SET code=$1, name=$2 WHERE role_id=$3 AND tenant_id=$4`, + d.Code, d.Name, existing.RoleID, tenantID); err != nil { + return err + } + existing.Code = d.Code + existing.Name = d.Name + byCode[d.Code] = existing + } + if d.Code == authx.AgentRole生成发布 { + perms := append([]string{}, d.Permissions...) + if _, err := s.Update(ctx, tenantID, existing.RoleID, UpdateInput{ + Name: &d.Name, + Description: &d.Description, + Permissions: &perms, + }); err != nil { + return err + } + } + continue + } + if _, err := s.Create(ctx, tenantID, d); err != nil { + return err + } + } + return nil +} + +func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]Role, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT role_id, tenant_id, code, name, description, created_at +FROM platform_meta.roles WHERE tenant_id=$1 ORDER BY role_id`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []Role + for rows.Next() { + var r Role + if err := rows.Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt); err != nil { + return nil, err + } + perms, err := s.loadPerms(ctx, r.RoleID) + if err != nil { + return nil, err + } + r.Permissions = perms + out = append(out, r) + } + return out, rows.Err() +} + +func (s *PostgresStore) Get(ctx context.Context, tenantID, roleID int64) (*Role, error) { + var r Role + err := s.DB.QueryRowContext(ctx, ` +SELECT role_id, tenant_id, code, name, description, created_at +FROM platform_meta.roles WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID, + ).Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("role not found") + } + if err != nil { + return nil, err + } + perms, err := s.loadPerms(ctx, r.RoleID) + if err != nil { + return nil, err + } + r.Permissions = perms + return &r, nil +} + +func (s *PostgresStore) Create(ctx context.Context, tenantID int64, in CreateInput) (*Role, error) { + code := strings.TrimSpace(in.Code) + name := strings.TrimSpace(in.Name) + if code == "" || name == "" { + return nil, fmt.Errorf("code and name required") + } + var r Role + err := s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.roles(tenant_id, code, name, description) +VALUES($1,$2,$3,$4) +RETURNING role_id, tenant_id, code, name, description, created_at`, + tenantID, code, name, strings.TrimSpace(in.Description), + ).Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt) + if err != nil { + return nil, err + } + perms := uniq(in.Permissions) + if err := s.replacePerms(ctx, r.RoleID, perms); err != nil { + return nil, err + } + r.Permissions = perms + return &r, nil +} + +func (s *PostgresStore) Update(ctx context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) { + r, err := s.Get(ctx, tenantID, roleID) + if err != nil { + return nil, err + } + name, desc := r.Name, r.Description + if in.Name != nil { + name = strings.TrimSpace(*in.Name) + } + if in.Description != nil { + desc = strings.TrimSpace(*in.Description) + } + if _, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.roles SET name=$1, description=$2 WHERE role_id=$3 AND tenant_id=$4`, + name, desc, roleID, tenantID); err != nil { + return nil, err + } + if in.Permissions != nil { + if err := s.replacePerms(ctx, roleID, uniq(*in.Permissions)); err != nil { + return nil, err + } + } + return s.Get(ctx, tenantID, roleID) +} + +func (s *PostgresStore) Delete(ctx context.Context, tenantID, roleID int64) error { + _, _ = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.agent_accounts SET role_id=NULL WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID) + res, err := s.DB.ExecContext(ctx, ` +DELETE FROM platform_meta.roles WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("role not found") + } + return nil +} + +func (s *PostgresStore) loadPerms(ctx context.Context, roleID int64) ([]string, error) { + rows, err := s.DB.QueryContext(ctx, `SELECT perm FROM platform_meta.role_permissions WHERE role_id=$1`, roleID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var p string + if err := rows.Scan(&p); err != nil { + return nil, err + } + out = append(out, p) + } + if err := rows.Err(); err != nil { + return nil, err + } + return authx.NormalizePerms(out), nil +} + +func (s *PostgresStore) replacePerms(ctx context.Context, roleID int64, perms []string) error { + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.role_permissions WHERE role_id=$1`, roleID); err != nil { + return err + } + for _, p := range authx.NormalizePerms(perms) { + if _, err := tx.ExecContext(ctx, `INSERT INTO platform_meta.role_permissions(role_id, perm) VALUES($1,$2)`, roleID, p); err != nil { + return err + } + } + return tx.Commit() +} + +func clone(r Role) Role { + cp := r + cp.Permissions = append([]string{}, r.Permissions...) + return cp +} + +func uniq(in []string) []string { + return authx.NormalizePerms(in) +} diff --git a/platform/internal/schema/ddl.go b/platform/internal/schema/ddl.go new file mode 100644 index 0000000..9c35497 --- /dev/null +++ b/platform/internal/schema/ddl.go @@ -0,0 +1,168 @@ +package schema + +import ( + "fmt" + "strings" + + "aijianzhan/platform/internal/blueprint" +) + +// BuildPostgresDDL 仅拼接白名单标识符与固定类型映射。 +func BuildPostgresDDL(bp *blueprint.Blueprint) ([]string, error) { + if bp.Storage.SchemaName == "" { + return nil, fmt.Errorf("schema_name empty") + } + schema := bp.Storage.SchemaName + stmts := []string{ + fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", quoteIdent(schema)), + } + + for _, e := range bp.Entities { + cols := make([]string, 0, len(e.Fields)+4) + hasTenant := false + hasOrgUnit := false + hasCreatedAt := false + hasUpdatedAt := false + hasCreatedBy := false + + for _, f := range e.Fields { + sqlType, err := mapType(f) + if err != nil { + return nil, err + } + + var col string + switch { + case f.Name == e.PrimaryKey && f.Type == "bigint": + col = fmt.Sprintf("%s BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY", quoteIdent(f.Name)) + case f.Name == e.PrimaryKey && f.Type == "int": + col = fmt.Sprintf("%s INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY", quoteIdent(f.Name)) + default: + nullSQL := "NULL" + if !blueprint.BoolOr(f.Nullable, true) { + nullSQL = "NOT NULL" + } + col = fmt.Sprintf("%s %s %s", quoteIdent(f.Name), sqlType, nullSQL) + if f.Name == e.PrimaryKey { + col += " PRIMARY KEY" + } else if f.Unique { + col += " UNIQUE" + } + if f.Type == "enum" && len(f.EnumValues) > 0 { + // 枚举值仅作 UI 提示,不写死 CHECK,避免导入新值被拒 + } + } + cols = append(cols, col) + switch f.Name { + case "tenant_id": + hasTenant = true + case "org_unit_id": + hasOrgUnit = true + case "created_at": + hasCreatedAt = true + case "updated_at": + hasUpdatedAt = true + case "created_by": + hasCreatedBy = true + } + } + + // 系统强制列 + if !hasTenant { + cols = append(cols, "tenant_id BIGINT NOT NULL") + } + if !hasOrgUnit { + cols = append(cols, "org_unit_id BIGINT") + } + if !hasCreatedBy { + cols = append(cols, "created_by BIGINT") + } + if !hasCreatedAt { + cols = append(cols, "created_at TIMESTAMPTZ NOT NULL DEFAULT now()") + } + if !hasUpdatedAt { + cols = append(cols, "updated_at TIMESTAMPTZ NOT NULL DEFAULT now()") + } + + create := fmt.Sprintf( + "CREATE TABLE IF NOT EXISTS %s.%s (\n %s\n)", + quoteIdent(schema), + quoteIdent(e.Table), + strings.Join(cols, ",\n "), + ) + stmts = append(stmts, create) + + for _, idx := range e.Indexes { + unique := "" + if idx.Unique { + unique = "UNIQUE " + } + colsQuoted := make([]string, 0, len(idx.Columns)) + for _, c := range idx.Columns { + colsQuoted = append(colsQuoted, quoteIdent(c)) + } + stmts = append(stmts, fmt.Sprintf( + "CREATE %sINDEX IF NOT EXISTS %s ON %s.%s (%s)", + unique, + quoteIdent(idx.Name), + quoteIdent(schema), + quoteIdent(e.Table), + strings.Join(colsQuoted, ", "), + )) + } + } + return stmts, nil +} + +func mapType(f blueprint.Field) (string, error) { + switch f.Type { + case "string": + n := f.MaxLength + if n <= 0 { + n = 255 + } + return fmt.Sprintf("VARCHAR(%d)", n), nil + case "text": + return "TEXT", nil + case "int": + return "INTEGER", nil + case "bigint": + return "BIGINT", nil + case "decimal": + p, s := f.Precision, f.Scale + if p <= 0 { + p = 18 + } + if s < 0 { + s = 2 + } + return fmt.Sprintf("NUMERIC(%d,%d)", p, s), nil + case "boolean": + return "BOOLEAN", nil + case "date": + return "DATE", nil + case "datetime": + return "TIMESTAMPTZ", nil + case "enum": + return "VARCHAR(64)", nil + case "json": + return "JSONB", nil + case "file_ref": + return "VARCHAR(512)", nil + default: + return "", fmt.Errorf("unknown field type: %s", f.Type) + } +} + +func enumCheck(col string, values []string) string { + quoted := make([]string, 0, len(values)) + for _, v := range values { + quoted = append(quoted, "'"+strings.ReplaceAll(v, "'", "''")+"'") + } + return fmt.Sprintf("CHECK (%s IN (%s))", quoteIdent(col), strings.Join(quoted, ", ")) +} + +func quoteIdent(name string) string { + // 调用方已白名单校验;仍用双引号包裹防止关键字冲突 + return `"` + strings.ReplaceAll(name, `"`, ``) + `"` +} diff --git a/platform/internal/schema/ddl_test.go b/platform/internal/schema/ddl_test.go new file mode 100644 index 0000000..b4bdffa --- /dev/null +++ b/platform/internal/schema/ddl_test.go @@ -0,0 +1,38 @@ +package schema_test + +import ( + "encoding/json" + "os" + "path/filepath" + "runtime" + "testing" + + "aijianzhan/platform/internal/blueprint" + "aijianzhan/platform/internal/schema" +) + +func TestBuildDDLFromExample(t *testing.T) { + _, file, _, _ := runtime.Caller(0) + root := filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..", "..")) + example := filepath.Join(root, "blueprint", "examples", "inventory-ledger.blueprint.json") + raw, err := os.ReadFile(example) + if err != nil { + t.Fatalf("read example: %v", err) + } + bp, err := blueprint.Parse(json.RawMessage(raw)) + if err != nil { + t.Fatal(err) + } + if err := bp.Validate("inventory_ledger"); err != nil { + t.Fatal(err) + } + bp.AssignSchemaName(1) + stmts, err := schema.BuildPostgresDDL(bp) + if err != nil { + t.Fatal(err) + } + if len(stmts) < 2 { + t.Fatalf("expected schema+table ddl, got %d", len(stmts)) + } + t.Logf("ddl count=%d first=%s", len(stmts), stmts[0]) +} diff --git a/platform/internal/schema/runner.go b/platform/internal/schema/runner.go new file mode 100644 index 0000000..0db5f02 --- /dev/null +++ b/platform/internal/schema/runner.go @@ -0,0 +1,92 @@ +package schema + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "regexp" + "strings" +) + +var dbNameRe = regexp.MustCompile(`^[a-z][a-z0-9_]{1,47}$`) + +type Runner interface { + ExecDDL(ctx context.Context, stmts []string) error + EnsureDatabase(ctx context.Context, dbName string) error +} + +type NoopRunner struct{} + +func (NoopRunner) ExecDDL(context.Context, []string) error { return nil } +func (NoopRunner) EnsureDatabase(context.Context, string) error { return nil } + +type PostgresRunner struct { + DB *sql.DB + AdminDSN string // 用于 CREATE DATABASE(连到 postgres 库) +} + +func (r *PostgresRunner) ExecDDL(ctx context.Context, stmts []string) error { + tx, err := r.DB.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + for _, s := range stmts { + if _, err := tx.ExecContext(ctx, s); err != nil { + return fmt.Errorf("ddl failed: %w\nsql: %s", err, s) + } + } + return tx.Commit() +} + +func (r *PostgresRunner) EnsureDatabase(ctx context.Context, dbName string) error { + if !dbNameRe.MatchString(dbName) { + return fmt.Errorf("invalid database name: %s", dbName) + } + admin := r.DB + var err error + if r.AdminDSN != "" { + admin, err = sql.Open("postgres", r.AdminDSN) + if err != nil { + return err + } + defer admin.Close() + } + var exists bool + if err := admin.QueryRowContext(ctx, + `SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname=$1)`, dbName, + ).Scan(&exists); err != nil { + return err + } + if exists { + return nil + } + // CREATE DATABASE 不能在事务中 + _, err = admin.ExecContext(ctx, fmt.Sprintf(`CREATE DATABASE %s`, quoteIdent(dbName))) + return err +} + +// DSNForDatabase 把原 DSN 的库名替换为目标库。 +func DSNForDatabase(baseDSN, dbName string) (string, error) { + if !dbNameRe.MatchString(dbName) { + return "", fmt.Errorf("invalid database name") + } + u, err := url.Parse(baseDSN) + if err != nil { + return "", err + } + u.Path = "/" + dbName + return u.String(), nil +} + +func QuoteIdentExport(name string) string { return quoteIdent(name) } + +func SanitizeDBName(tenantID int64, slug string) string { + name := fmt.Sprintf("appdb_t%d_%s", tenantID, slug) + name = strings.ToLower(name) + if len(name) > 48 { + name = name[:48] + } + return name +} diff --git a/platform/internal/smsstore/store.go b/platform/internal/smsstore/store.go new file mode 100644 index 0000000..e5b54d7 --- /dev/null +++ b/platform/internal/smsstore/store.go @@ -0,0 +1,125 @@ +package smsstore + +import ( + "crypto/rand" + "fmt" + "sync" + "time" +) + +type Purpose string + +const ( + PurposeLogin Purpose = "login" + PurposeBind Purpose = "bind" +) + +type Record struct { + Code string + ExpiresAt time.Time + SentAt time.Time + Attempts int +} + +type Store struct { + mu sync.Mutex + byKey map[string]*Record + ttl time.Duration + resendGap time.Duration + maxAttempts int + fixedCode string // 开发固定码;空则随机 6 位 +} + +func New(ttl, resendGap time.Duration, fixedCode string) *Store { + if ttl <= 0 { + ttl = 5 * time.Minute + } + if resendGap <= 0 { + resendGap = 60 * time.Second + } + return &Store{ + byKey: map[string]*Record{}, + ttl: ttl, + resendGap: resendGap, + maxAttempts: 5, + fixedCode: fixedCode, + } +} + +func key(purpose Purpose, phone string) string { + return string(purpose) + ":" + phone +} + +// Issue 生成并保存验证码;若未到重发间隔返回 retryAfter>0。 +func (s *Store) Issue(purpose Purpose, phone string) (code string, expiresIn, retryAfter int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + k := key(purpose, phone) + now := time.Now() + if old, ok := s.byKey[k]; ok && now.Sub(old.SentAt) < s.resendGap { + left := int((s.resendGap - now.Sub(old.SentAt)).Seconds()) + if left < 1 { + left = 1 + } + return "", 0, left, fmt.Errorf("发送过于频繁,请 %d 秒后再试", left) + } + code = s.fixedCode + if code == "" { + var e error + code, e = randomDigits(6) + if e != nil { + return "", 0, 0, e + } + } + s.byKey[k] = &Record{ + Code: code, + ExpiresAt: now.Add(s.ttl), + SentAt: now, + } + return code, int(s.ttl.Seconds()), 0, nil +} + +// Consume 校验并消费验证码(成功后删除)。 +func (s *Store) Consume(purpose Purpose, phone, code string) error { + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + k := key(purpose, phone) + rec, ok := s.byKey[k] + if !ok || time.Now().After(rec.ExpiresAt) { + delete(s.byKey, k) + return fmt.Errorf("验证码无效或已过期") + } + if rec.Code != code { + rec.Attempts++ + if rec.Attempts >= s.maxAttempts { + delete(s.byKey, k) + return fmt.Errorf("验证码错误次数过多,请重新获取") + } + return fmt.Errorf("验证码错误") + } + delete(s.byKey, k) + return nil +} + +func (s *Store) gcLocked() { + now := time.Now() + for k, r := range s.byKey { + if now.After(r.ExpiresAt.Add(time.Minute)) { + delete(s.byKey, k) + } + } +} + +func randomDigits(n int) (string, error) { + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + return "", err + } + out := make([]byte, n) + for i := range b { + out[i] = '0' + b[i]%10 + } + return string(out), nil +} diff --git a/platform/internal/smsstore/store_test.go b/platform/internal/smsstore/store_test.go new file mode 100644 index 0000000..4e04c48 --- /dev/null +++ b/platform/internal/smsstore/store_test.go @@ -0,0 +1,23 @@ +package smsstore + +import ( + "testing" + "time" +) + +func TestIssueAndConsume(t *testing.T) { + s := New(time.Minute, time.Second, "654321") + code, exp, retry, err := s.Issue(PurposeLogin, "13800138000") + if err != nil || code != "654321" || exp <= 0 || retry != 0 { + t.Fatalf("issue %q %d %d %v", code, exp, retry, err) + } + if err := s.Consume(PurposeLogin, "13800138000", "000000"); err == nil { + t.Fatal("bad code should fail") + } + if err := s.Consume(PurposeLogin, "13800138000", "654321"); err != nil { + t.Fatal(err) + } + if err := s.Consume(PurposeLogin, "13800138000", "654321"); err == nil { + t.Fatal("consumed twice") + } +} diff --git a/platform/internal/storage/local.go b/platform/internal/storage/local.go new file mode 100644 index 0000000..293e115 --- /dev/null +++ b/platform/internal/storage/local.go @@ -0,0 +1,112 @@ +package storage + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" +) + +type ObjectMeta struct { + Key string `json:"key"` + URL string `json:"url"` + Filename string `json:"filename"` + ContentType string `json:"content_type"` + Size int64 `json:"size"` + CreatedAt time.Time `json:"created_at"` +} + +type Store interface { + Put(ctx context.Context, tenantID int64, filename, contentType string, r io.Reader, size int64) (*ObjectMeta, error) + Open(ctx context.Context, key string) (io.ReadCloser, *ObjectMeta, error) +} + +type LocalStore struct { + Root string + PublicBase string // e.g. http://127.0.0.1:8180/api/v1/storage +} + +func NewLocalStore(root, publicBase string) (*LocalStore, error) { + if root == "" { + root = "./data/uploads" + } + if err := os.MkdirAll(root, 0o755); err != nil { + return nil, err + } + return &LocalStore{Root: root, PublicBase: strings.TrimRight(publicBase, "/")}, nil +} + +func (s *LocalStore) Put(_ context.Context, tenantID int64, filename, contentType string, r io.Reader, size int64) (*ObjectMeta, error) { + ext := filepath.Ext(filename) + if ext == "" { + ext = guessExt(contentType) + } + id := make([]byte, 8) + _, _ = rand.Read(id) + key := fmt.Sprintf("t%d/%s/%s%s", tenantID, time.Now().UTC().Format("20060102"), hex.EncodeToString(id), ext) + full := filepath.Join(s.Root, filepath.FromSlash(key)) + if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { + return nil, err + } + f, err := os.Create(full) + if err != nil { + return nil, err + } + defer f.Close() + n, err := io.Copy(f, r) + if err != nil { + return nil, err + } + if size <= 0 { + size = n + } + meta := &ObjectMeta{ + Key: key, Filename: filename, ContentType: contentType, Size: size, CreatedAt: time.Now().UTC(), + URL: s.PublicBase + "/" + key, + } + // sidecar meta + _ = os.WriteFile(full+".meta", []byte(fmt.Sprintf("%s\n%s\n%d\n%s", filename, contentType, size, meta.CreatedAt.Format(time.RFC3339))), 0o644) + return meta, nil +} + +func (s *LocalStore) Open(_ context.Context, key string) (io.ReadCloser, *ObjectMeta, error) { + key = strings.TrimPrefix(key, "/") + if strings.Contains(key, "..") { + return nil, nil, fmt.Errorf("invalid key") + } + full := filepath.Join(s.Root, filepath.FromSlash(key)) + f, err := os.Open(full) + if err != nil { + return nil, nil, err + } + st, _ := f.Stat() + meta := &ObjectMeta{Key: key, Size: st.Size(), URL: s.PublicBase + "/" + key, CreatedAt: st.ModTime().UTC()} + if b, err := os.ReadFile(full + ".meta"); err == nil { + parts := strings.Split(string(b), "\n") + if len(parts) >= 2 { + meta.Filename = parts[0] + meta.ContentType = parts[1] + } + } + return f, meta, nil +} + +func guessExt(ct string) string { + switch ct { + case "image/png": + return ".png" + case "image/jpeg": + return ".jpg" + case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + return ".xlsx" + case "text/csv": + return ".csv" + default: + return ".bin" + } +} diff --git a/platform/internal/svc/servicecontext.go b/platform/internal/svc/servicecontext.go new file mode 100644 index 0000000..156e0b4 --- /dev/null +++ b/platform/internal/svc/servicecontext.go @@ -0,0 +1,213 @@ +package svc + +import ( + "context" + "database/sql" + "log" + "net/url" + "strings" + "time" + + "aijianzhan/platform/internal/agentstore" + "aijianzhan/platform/internal/audit" + "aijianzhan/platform/internal/authx" + "aijianzhan/platform/internal/config" + "aijianzhan/platform/internal/crud" + "aijianzhan/platform/internal/dbsync" + "aijianzhan/platform/internal/invitestore" + "aijianzhan/platform/internal/license" + "aijianzhan/platform/internal/meta" + "aijianzhan/platform/internal/orgunitstore" + "aijianzhan/platform/internal/ratelimit" + "aijianzhan/platform/internal/rolestore" + "aijianzhan/platform/internal/schema" + "aijianzhan/platform/internal/smsstore" + "aijianzhan/platform/internal/storage" + "aijianzhan/platform/internal/tenantperm" + "aijianzhan/platform/internal/userstore" + + _ "github.com/lib/pq" +) + +type ServiceContext struct { + Config config.Config + Meta meta.Store + Schema schema.Runner + CRUD crud.Engine + Users userstore.Store + Agents agentstore.Store + Roles rolestore.Store + Invites invitestore.Store + OrgUnits orgunitstore.Store + Audit audit.Store + Objects storage.Store + Limiter *ratelimit.Limiter + DBPool *crud.DBPool + MemoryMode bool + DB *sql.DB + JWT authx.JWTConfig + DBSync *dbsync.Manager + TenantPerm tenantperm.Store + SMS *smsstore.Store + License *license.Manager +} + +func NewServiceContext(c config.Config) *ServiceContext { + users := userstore.NewMemoryStore() + aud := audit.NewMemoryStore() + agents := agentstore.NewMemoryStore() + roles := rolestore.NewMemoryStore() + invites := invitestore.NewMemoryStore() + orgUnits := orgunitstore.NewMemoryStore() + pubBase := strings.TrimRight(c.PublicBaseURL, "/") + if pubBase == "" { + pubBase = "http://127.0.0.1:8180" + } + storageBase := c.Storage.PublicBase + if storageBase == "" { + storageBase = pubBase + "/api/v1/storage" + } + obj, err := storage.NewLocalStore(c.Storage.LocalRoot, storageBase) + if err != nil { + log.Printf("storage init: %v", err) + obj, _ = storage.NewLocalStore("./data/uploads", storageBase) + } + + ctx := &ServiceContext{ + Config: c, + Meta: meta.NewMemoryStore(), + Schema: schema.NoopRunner{}, + CRUD: crud.NewMemoryEngine(), + Users: users, + Agents: agents, + Roles: roles, + Invites: invites, + OrgUnits: orgUnits, + Audit: aud, + TenantPerm: tenantperm.NewMemoryStore(), + Objects: obj, + Limiter: ratelimit.New(c.RateLimitPerMin), + MemoryMode: true, + SMS: smsstore.New( + time.Duration(c.SMS.CodeTTLSeconds)*time.Second, + time.Duration(c.SMS.ResendSeconds)*time.Second, + c.SMS.DevFixedCode, + ), + JWT: authx.JWTConfig{ + AccessSecret: c.Auth.AccessSecret, + AccessExpire: c.Auth.AccessExpire, + }, + } + if ctx.JWT.AccessSecret == "" { + ctx.JWT.AccessSecret = "dev-only-change-me" + } + + if c.DataSource != "" && !c.DryRun { + db, err := sql.Open("postgres", c.DataSource) + if err != nil { + log.Printf("postgres open failed, fallback memory: %v", err) + } else if err := db.Ping(); err != nil { + log.Printf("postgres ping failed, fallback memory: %v", err) + _ = db.Close() + } else { + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(5) + migCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + if err := meta.EnsureSchema(migCtx, db); err != nil { + log.Printf("meta migrate failed: %v", err) + ctx.DB = db + ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)} + pool := crud.NewDBPool(db, c.DataSource) + ctx.DBPool = pool + ctx.CRUD = crud.NewPostgresEngine(db, pool) + ctx.MemoryMode = false + } else { + ctx.DB = db + ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)} + pool := crud.NewDBPool(db, c.DataSource) + ctx.DBPool = pool + ctx.CRUD = crud.NewPostgresEngine(db, pool) + ctx.Meta = meta.NewPostgresStore(db) + ctx.Users = userstore.NewPostgresStore(db) + ctx.Agents = agentstore.NewPostgresStore(db) + ctx.Roles = rolestore.NewPostgresStore(db) + ctx.Invites = invitestore.NewPostgresStore(db) + ctx.OrgUnits = orgunitstore.NewPostgresStore(db) + ctx.Audit = audit.NewPostgresStore(db) + ctx.TenantPerm = tenantperm.NewPostgresStore(db) + ctx.MemoryMode = false + log.Printf("postgres engine + meta + users + agents + roles + invites + org_units + audit + tenant_perm enabled") + } + } + } + userstore.EnsureDemoUser(context.Background(), ctx.Users) + userstore.EnsurePlatformAdminUser(context.Background(), ctx.Users) + if ctx.Users != nil { + if err := ctx.Users.EnsureTenantSlugs(context.Background()); err != nil { + log.Printf("ensure tenant slugs: %v", err) + } + } + _ = ctx.Roles.EnsureDefaults(context.Background(), 1) + if ctx.TenantPerm != nil { + _ = ctx.TenantPerm.EnsureDefault(context.Background(), 1) + authx.EntitlementChecker = func(c context.Context, tenantID int64, perm string) bool { + ok, err := ctx.TenantPerm.Allows(c, tenantID, perm) + if err != nil { + log.Printf("entitlement check: %v", err) + return false + } + return ok + } + } + if c.DryRun { + ctx.Schema = schema.NoopRunner{} + } + + // 跨库同步中间件(默认启用) + if c.DBSync.Enabled { + dir := c.DBSync.DataDir + if dir == "" { + dir = "./data/dbsync" + } + store, err := dbsync.NewFileStore(dir) + if err != nil { + log.Printf("dbsync store: %v", err) + } else { + ctx.DBSync = dbsync.NewManager(store) + log.Printf("dbsync middleware enabled (dir=%s)", dir) + } + } + + licCfg := c.License + if licCfg.Enabled { + if strings.TrimSpace(licCfg.ControlSecret) == "" { + licCfg.ControlSecret = c.Auth.IssueSecret + } + if strings.TrimSpace(licCfg.SignSecret) == "" { + licCfg.SignSecret = licCfg.ControlSecret + } + } + lic, err := license.NewManager(licCfg) + if err != nil { + log.Printf("license manager: %v", err) + } else { + ctx.License = lic + if lic.Enabled() { + if ctx.DB != nil { + lic.AttachMirror(license.NewPGMirror(ctx.DB)) + } + log.Printf("license lease enabled (leases=%s state=%s)", lic.Path(), lic.StatePath()) + } + } + return ctx +} + +func adminDSN(dsn string) string { + u, err := url.Parse(dsn) + if err != nil { + return dsn + } + u.Path = "/postgres" + return u.String() +} diff --git a/platform/internal/tenantperm/store.go b/platform/internal/tenantperm/store.go new file mode 100644 index 0000000..cb09da7 --- /dev/null +++ b/platform/internal/tenantperm/store.go @@ -0,0 +1,195 @@ +package tenantperm + +import ( + "context" + "database/sql" + "sync" + + "aijianzhan/platform/internal/authx" +) + +// Store 公司权限额度:超管授予,公司内分配不得越界。 +// configured=false 表示从未配置 → 读时视为全量默认;configured=true 时允许空额度。 +type Store interface { + Get(ctx context.Context, tenantID int64) ([]string, error) + Set(ctx context.Context, tenantID int64, perms []string) error + Allows(ctx context.Context, tenantID int64, perm string) (bool, error) + EnsureDefault(ctx context.Context, tenantID int64) error + Configured(ctx context.Context, tenantID int64) (bool, error) +} + +type MemoryStore struct { + mu sync.Mutex + by map[int64][]string // key 存在即已配置 +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{by: map[int64][]string{}} +} + +func (s *MemoryStore) Configured(_ context.Context, tenantID int64) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.by[tenantID] + return ok, nil +} + +func (s *MemoryStore) Get(_ context.Context, tenantID int64) ([]string, error) { + s.mu.Lock() + defer s.mu.Unlock() + if ps, ok := s.by[tenantID]; ok { + return append([]string{}, ps...), nil + } + return authx.CompanyPermCatalog(), nil +} + +func (s *MemoryStore) Set(_ context.Context, tenantID int64, perms []string) error { + s.mu.Lock() + defer s.mu.Unlock() + s.by[tenantID] = intersectCatalog(perms) // 可为空切片 + return nil +} + +func (s *MemoryStore) Allows(ctx context.Context, tenantID int64, perm string) (bool, error) { + ps, err := s.Get(ctx, tenantID) + if err != nil { + return false, err + } + want := authx.NormalizePerm(perm) + for _, p := range ps { + if p == want { + return true, nil + } + } + return false, nil +} + +func (s *MemoryStore) EnsureDefault(ctx context.Context, tenantID int64) error { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.by[tenantID]; ok { + return nil + } + s.by[tenantID] = authx.CompanyPermCatalog() + return nil +} + +type PostgresStore struct{ DB *sql.DB } + +func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db} } + +func (s *PostgresStore) Configured(ctx context.Context, tenantID int64) (bool, error) { + var n int + err := s.DB.QueryRowContext(ctx, ` +SELECT COUNT(*) FROM platform_meta.tenant_entitlement_state WHERE tenant_id=$1`, tenantID).Scan(&n) + return n > 0, err +} + +func (s *PostgresStore) Get(ctx context.Context, tenantID int64) ([]string, error) { + ok, err := s.Configured(ctx, tenantID) + if err != nil { + return nil, err + } + if !ok { + return authx.CompanyPermCatalog(), nil + } + rows, err := s.DB.QueryContext(ctx, ` +SELECT perm FROM platform_meta.tenant_permissions WHERE tenant_id=$1 ORDER BY perm`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + out := make([]string, 0) + for rows.Next() { + var p string + if err := rows.Scan(&p); err != nil { + return nil, err + } + out = append(out, authx.NormalizePerm(p)) + } + if err := rows.Err(); err != nil { + return nil, err + } + return authx.NormalizePerms(out), nil +} + +func (s *PostgresStore) Set(ctx context.Context, tenantID int64, perms []string) error { + allowed := intersectCatalog(perms) + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.tenant_permissions WHERE tenant_id=$1`, tenantID); err != nil { + return err + } + for _, p := range allowed { + if _, err := tx.ExecContext(ctx, ` +INSERT INTO platform_meta.tenant_permissions(tenant_id, perm) VALUES($1,$2)`, tenantID, p); err != nil { + return err + } + } + if _, err := tx.ExecContext(ctx, ` +INSERT INTO platform_meta.tenant_entitlement_state(tenant_id, updated_at) +VALUES($1, now()) +ON CONFLICT (tenant_id) DO UPDATE SET updated_at=now()`, tenantID); err != nil { + return err + } + return tx.Commit() +} + +func (s *PostgresStore) Allows(ctx context.Context, tenantID int64, perm string) (bool, error) { + ps, err := s.Get(ctx, tenantID) + if err != nil { + return false, err + } + want := authx.NormalizePerm(perm) + for _, p := range ps { + if p == want { + return true, nil + } + } + return false, nil +} + +func (s *PostgresStore) EnsureDefault(ctx context.Context, tenantID int64) error { + ok, err := s.Configured(ctx, tenantID) + if err != nil { + return err + } + if ok { + return nil + } + return s.Set(ctx, tenantID, authx.CompanyPermCatalog()) +} + +func intersectCatalog(perms []string) []string { + cat := map[string]struct{}{} + for _, p := range authx.CompanyPermCatalog() { + cat[p] = struct{}{} + } + out := make([]string, 0, len(perms)) + seen := map[string]struct{}{} + for _, p := range authx.NormalizePerms(perms) { + if _, ok := cat[p]; !ok { + continue + } + if _, ok := seen[p]; ok { + continue + } + seen[p] = struct{}{} + out = append(out, p) + } + return out +} + +func MustAllow(ctx context.Context, st Store, tenantID int64, want []string) error { + if st == nil || tenantID <= 0 { + return nil + } + allowance, err := st.Get(ctx, tenantID) + if err != nil { + return err + } + return authx.AssertWithinEntitlement(want, allowance) +} diff --git a/platform/internal/types/types.go b/platform/internal/types/types.go new file mode 100644 index 0000000..6cbbafc --- /dev/null +++ b/platform/internal/types/types.go @@ -0,0 +1,253 @@ +package types + +import "encoding/json" + +type PublishReq struct { + Blueprint json.RawMessage `json:"blueprint"` + // Mode: + // "" / "auto" — 模块已存在则发布新生成的页面,不存在则新建(默认) + // "add_pages" — 必须已存在,把草稿中的新页面发布到该模块(亦接受别名 merge) + // "create" — 必须不存在,新建模块 + // "replace" — 整份蓝图覆盖(旧行为) + Mode string `json:"mode,omitempty"` + // HostMeta 宿主(宇恒)侧展示用元数据,原样回写到响应,便于宿主表格展示。 + HostMeta *PublishHostMeta `json:"host_meta,omitempty"` +} + +type PublishHostMeta struct { + ModuleName string `json:"module_name,omitempty"` // 模块名称(宿主表格「应用/模块名称」) + PublishStyle string `json:"publish_style,omitempty"` // 如 immediate → 立即发布上线 + HostBaseURL string `json:"host_base_url,omitempty"` // 宿主域名,如 https://whm123.yuheng.com +} + +type PublishResp struct { + AppID string `json:"app_id"` + Slug string `json:"slug"` + SchemaName string `json:"schema_name"` + DatabaseName string `json:"database_name,omitempty"` + Status string `json:"status"` + Endpoints []string `json:"endpoints"` + DDL []string `json:"ddl,omitempty"` + MemoryMode bool `json:"memory_mode"` + PublishMode string `json:"publish_mode"` // created | pages_added | replaced + AddedPages []string `json:"added_pages,omitempty"` + AddedEntities []string `json:"added_entities,omitempty"` + AddedResources []string `json:"added_resources,omitempty"` + // 宿主建站回执(对应「AI 表格数据」) + ModuleName string `json:"module_name,omitempty"` + PublishStyle string `json:"publish_style,omitempty"` + AccessPath string `json:"access_path,omitempty"` // 加密文件路径,如 m/ajzm1_... + AccessURL string `json:"access_url,omitempty"` // 可打开的地址(宿主域名或平台公开地址) + PublishedAt string `json:"published_at,omitempty"` // RFC3339 + OwnerID int64 `json:"owner_id,omitempty"` // 用于路径加密的用户/智能体 id +} + +type AppListItem struct { + AppID string `json:"app_id"` + Slug string `json:"slug"` + Name string `json:"name"` + Status string `json:"status"` + StatusLabel string `json:"status_label"` // 已发布 / 在建 / 失败 + Building bool `json:"building"` // 在建(含草稿、发布中) + SchemaName string `json:"schema_name,omitempty"` + PageCount int `json:"page_count"` + EntityCount int `json:"entity_count"` + UpdatedAt string `json:"updated_at"` + CreatedAt string `json:"created_at"` +} + +type AppListResp struct { + Items []AppListItem `json:"items"` + // Scope: all = 管理账号看本租户全部;granted = 智能体仅已授权 + Scope string `json:"scope,omitempty"` +} + +type DraftReq struct { + Blueprint json.RawMessage `json:"blueprint"` +} + +type PageResult struct { + Items []map[string]any `json:"items"` + Page int `json:"page"` + PageSize int `json:"page_size"` + Total int `json:"total"` +} + +type TokenReq struct { + GrantType string `json:"grant_type,omitempty"` // client_credentials + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Role string `json:"role,omitempty"` + Secret string `json:"secret"` +} + +type TokenResp struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresAt int64 `json:"expires_at"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + Username string `json:"username,omitempty"` + Phone string `json:"phone,omitempty"` + UsernameLoginDisabled bool `json:"username_login_disabled,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Role string `json:"role,omitempty"` + OrgUnitID int64 `json:"org_unit_id,omitempty"` + Status string `json:"status,omitempty"` // pending | active + AgentKey string `json:"agent_key"` + AgentID int64 `json:"agent_id,omitempty"` + Permissions []string `json:"permissions,omitempty"` + AppSlugs []string `json:"app_slugs,omitempty"` + Message string `json:"message,omitempty"` + TenantName string `json:"tenant_name,omitempty"` // 超管打开某公司管理视图时带回 +} + +type AgentCreateReq struct { + Name string `json:"name"` + RoleID int64 `json:"role_id"` + Permissions []string `json:"permissions"` + AppSlugs []string `json:"app_slugs"` +} + +type AgentUpdateReq struct { + Name *string `json:"name,omitempty"` + Status *string `json:"status,omitempty"` + RoleID *int64 `json:"role_id,omitempty"` + Permissions *[]string `json:"permissions,omitempty"` + AppSlugs *[]string `json:"app_slugs,omitempty"` +} + +type AgentCreateResp struct { + Account any `json:"account"` + ClientSecret string `json:"client_secret"` +} + +type AgentSecretResp struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +type RoleCreateReq struct { + Code string `json:"code"` + Name string `json:"name"` + Description string `json:"description"` + Permissions []string `json:"permissions"` +} + +type RoleUpdateReq struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Permissions *[]string `json:"permissions,omitempty"` +} + + +// AgentSelfRegisterReq 宿主首次连接自注册(公开)。 +type AgentSelfRegisterReq struct { + Name string `json:"name"` + HostKey string `json:"host_key,omitempty"` // 宿主稳定标识,用于幂等 + TenantID int64 `json:"tenant_id,omitempty"` + RegisterSecret string `json:"register_secret"` +} + +type AgentSelfRegisterResp struct { + Account any `json:"account"` + ClientSecret string `json:"client_secret"` + Reused bool `json:"reused"` // 同一 host_key 的 pending 重连会轮换 secret + Message string `json:"message"` +} + +type RegisterReq struct { + Username string `json:"username"` + Password string `json:"password"` + DisplayName string `json:"display_name"` +} + +type LoginReq struct { + Username string `json:"username,omitempty"` // 用户名;也可填手机号(密码登录) + Phone string `json:"phone,omitempty"` // 手机号登录时使用 + Password string `json:"password,omitempty"` + SMSCode string `json:"sms_code,omitempty"` // 短信验证码登录(与 password 二选一) +} + +type SMSSendReq struct { + Phone string `json:"phone"` + Purpose string `json:"purpose,omitempty"` // login | bind,默认 login +} + +type SMSSendResp struct { + OK bool `json:"ok"` + ExpiresIn int `json:"expires_in"` + RetryAfter int `json:"retry_after,omitempty"` + Message string `json:"message,omitempty"` + DebugCode string `json:"debug_code,omitempty"` // 仅 Provider=dev 时返回 +} + +type InviteCreateReq struct { + Role string `json:"role"` // owner | editor | viewer + OrgUnitID int64 `json:"org_unit_id"` // 可选,加入后绑定组织 + MaxUses int `json:"max_uses"` // 默认 1 + ExpiresInHours int `json:"expires_in_hours"` // 0=不过期 +} + +type InviteAcceptReq struct { + Code string `json:"code"` +} + +type TenantCreateReq struct { + Name string `json:"name"` +} + +type OrgUnitCreateReq struct { + ParentID int64 `json:"parent_id"` + Name string `json:"name"` + Code string `json:"code"` +} + +type OrgUnitUpdateReq struct { + Name *string `json:"name,omitempty"` + Code *string `json:"code,omitempty"` +} + +type CapsuleResp struct { + // Capsule 密文:AJZ1..,不含明文 API 契约 + Capsule string `json:"capsule"` + Format string `json:"format"` + Hint string `json:"hint"` +} + +type ImportResp struct { + Inserted int `json:"inserted"` + Skipped int `json:"skipped"` + Errors []string `json:"errors"` +} + +type AggregateResp struct { + Total int `json:"total"` + GroupBy string `json:"group_by,omitempty"` + Buckets []AggregateBucket `json:"buckets,omitempty"` + SumField string `json:"sum_field,omitempty"` + Sum float64 `json:"sum,omitempty"` +} + +type AggregateBucket struct { + Key string `json:"key"` + Count int `json:"count"` + Sum float64 `json:"sum,omitempty"` +} + +type AuditListResp struct { + Items []any `json:"items"` + Total int `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +type UploadResp struct { + Key string `json:"key"` + URL string `json:"url"` + Filename string `json:"filename"` + ContentType string `json:"content_type"` + Size int64 `json:"size"` +} diff --git a/platform/internal/userstore/creds.go b/platform/internal/userstore/creds.go new file mode 100644 index 0000000..aec782b --- /dev/null +++ b/platform/internal/userstore/creds.go @@ -0,0 +1,102 @@ +package userstore + +import ( + "context" + "crypto/rand" + "fmt" + "strings" +) + +const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789" +const usernameAlphabet = "abcdefghijkmnopqrstuvwxyz23456789" +const usernameFirst = "abcdefghijkmnopqrstuvwxyz" + +// RandomPassword 生成可读随机密码(不含易混字符 0/O/1/l)。 +func RandomPassword(n int) (string, error) { + if n < 8 { + n = 12 + } + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + return "", err + } + out := make([]byte, n) + for i := range b { + out[i] = passwordAlphabet[int(b[i])%len(passwordAlphabet)] + } + return string(out), nil +} + +// RandomUsername 生成随机登录名(字母开头,仅小写字母与数字,默认 10 位)。 +func RandomUsername() (string, error) { + const n = 10 + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + return "", err + } + out := make([]byte, n) + out[0] = usernameFirst[int(b[0])%len(usernameFirst)] + for i := 1; i < n; i++ { + out[i] = usernameAlphabet[int(b[i])%len(usernameAlphabet)] + } + return string(out), nil +} + +// AllocUniqueUsername 分配全局唯一随机用户名。 +func AllocUniqueUsername(ctx context.Context, exists func(context.Context, string) (bool, error)) (string, error) { + var last error + for i := 0; i < 16; i++ { + u, err := RandomUsername() + if err != nil { + return "", err + } + ok, err := exists(ctx, u) + if err != nil { + return "", err + } + if ok { + last = fmt.Errorf("username collision") + continue + } + return u, nil + } + if last != nil { + return "", fmt.Errorf("无法生成唯一用户名: %v", last) + } + return "", fmt.Errorf("无法生成唯一用户名") +} + +// Deprecated: 保留测试兼容;新逻辑请用 RandomUsername / AllocUniqueUsername。 +func SuggestAdminUsername(slug string) string { + s := strings.ToLower(strings.TrimSpace(slug)) + var b strings.Builder + for _, r := range s { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { + b.WriteRune(r) + } + } + base := b.String() + if base == "" { + base = "co" + } + if len(base) > 20 { + base = base[:20] + } + return base + "_adm" +} + +// Deprecated: 见 AllocUniqueUsername。 +func NextUsernameCandidate(base string, attempt int) string { + base = strings.TrimSpace(base) + if base == "" { + base = "user" + } + if attempt <= 0 { + return base + } + suffix, err := RandomPassword(4) + if err != nil { + return fmt.Sprintf("%s%d", base, attempt) + } + return fmt.Sprintf("%s_%s", base, strings.ToLower(suffix)) +} diff --git a/platform/internal/userstore/creds_test.go b/platform/internal/userstore/creds_test.go new file mode 100644 index 0000000..9e61503 --- /dev/null +++ b/platform/internal/userstore/creds_test.go @@ -0,0 +1,51 @@ +package userstore + +import ( + "context" + "testing" +) + +func TestRandomUsernameUnique(t *testing.T) { + seen := map[string]struct{}{} + for i := 0; i < 20; i++ { + u, err := RandomUsername() + if err != nil || len(u) != 10 { + t.Fatalf("got %q %v", u, err) + } + if u[0] < 'a' || u[0] > 'z' { + t.Fatalf("must start with letter: %q", u) + } + seen[u] = struct{}{} + } + if len(seen) < 15 { + t.Fatalf("expected diverse usernames, got %d", len(seen)) + } +} + +func TestCreateMemberAndChangePassword(t *testing.T) { + s := NewMemoryStore() + ten, err := s.CreateTenant(context.Background(), "测试公司", "acme") + if err != nil { + t.Fatal(err) + } + uname, err := AllocUniqueUsername(context.Background(), s.UsernameExists) + if err != nil { + t.Fatal(err) + } + u, plain, err := s.CreateMember(context.Background(), ten.TenantID, uname, "", "管理员", "管理员", 0) + if err != nil || plain == "" { + t.Fatalf("u=%v plain=%q err=%v", u, plain, err) + } + if _, err := s.Login(context.Background(), uname, plain); err != nil { + t.Fatal(err) + } + if err := s.ChangePassword(context.Background(), u.UserID, plain, "newpass99"); err != nil { + t.Fatal(err) + } + if _, err := s.Login(context.Background(), uname, "newpass99"); err != nil { + t.Fatal(err) + } + if err := s.ChangePassword(context.Background(), u.UserID, "wrong", "x"); err == nil { + t.Fatal("expected old password check") + } +} diff --git a/platform/internal/userstore/phone.go b/platform/internal/userstore/phone.go new file mode 100644 index 0000000..5cc0efd --- /dev/null +++ b/platform/internal/userstore/phone.go @@ -0,0 +1,34 @@ +package userstore + +import ( + "fmt" + "regexp" + "strings" +) + +var chinaMobileRe = regexp.MustCompile(`^1[3-9]\d{9}$`) + +// NormalizePhone 规范化中国大陆手机号(去空格、去 +86/86 前缀)。 +func NormalizePhone(raw string) (string, error) { + s := strings.TrimSpace(raw) + s = strings.ReplaceAll(s, " ", "") + s = strings.ReplaceAll(s, "-", "") + if strings.HasPrefix(s, "+86") { + s = s[3:] + } else if strings.HasPrefix(s, "86") && len(s) == 13 { + s = s[2:] + } + if s == "" { + return "", fmt.Errorf("请填写手机号") + } + if !chinaMobileRe.MatchString(s) { + return "", fmt.Errorf("手机号格式不正确(需 11 位大陆号)") + } + return s, nil +} + +// LooksLikePhone 判断登录账号是否按手机号解析。 +func LooksLikePhone(raw string) bool { + _, err := NormalizePhone(raw) + return err == nil +} diff --git a/platform/internal/userstore/phone_test.go b/platform/internal/userstore/phone_test.go new file mode 100644 index 0000000..c88dd2c --- /dev/null +++ b/platform/internal/userstore/phone_test.go @@ -0,0 +1,38 @@ +package userstore + +import "testing" + +func TestNormalizePhone(t *testing.T) { + ok, err := NormalizePhone("+86 138-0013-8000") + if err != nil || ok != "13800138000" { + t.Fatalf("got %q %v", ok, err) + } + if _, err := NormalizePhone("12345"); err == nil { + t.Fatal("expected error") + } + if !LooksLikePhone("13912345678") { + t.Fatal("should look like phone") + } +} + +func TestLoginByPhone(t *testing.T) { + s := NewMemoryStore() + ten, err := s.CreateTenant(nil, "测", "ph") + if err != nil { + t.Fatal(err) + } + u, plain, err := s.CreateMember(nil, ten.TenantID, "abcdefgxyz", "pass1234", "测", "管理员", 0) + if err != nil { + t.Fatal(err) + } + if _, err := s.BindPhone(nil, u.UserID, "13900001111"); err != nil { + t.Fatal(err) + } + got, err := s.Login(nil, "13900001111", plain) + if err != nil || got.Username != "abcdefgxyz" { + t.Fatalf("login by phone: %v %#v", err, got) + } + if _, err := s.Login(nil, "abcdefgxyz", plain); err != nil { + t.Fatal(err) + } +} diff --git a/platform/internal/userstore/store.go b/platform/internal/userstore/store.go new file mode 100644 index 0000000..b1b27c8 --- /dev/null +++ b/platform/internal/userstore/store.go @@ -0,0 +1,1536 @@ +package userstore + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "sync" + "time" + + "golang.org/x/crypto/bcrypt" + + "aijianzhan/platform/internal/authx" +) + +const ( + StatusPending = "pending" + StatusActive = "active" + RolePending = authx.Role待加入 +) + +type User struct { + UserID int64 + TenantID int64 // 0 = 未加入任何租户 + OrgUnitID int64 // 0 = 未绑定组织(可见租户全量,由角色决定) + Username string + Phone string // 绑定手机号,可作登录账号 + UsernameLoginDisabled bool // 已绑手机时可禁用「用户名+密码」登录 + DisplayName string + Role string + Status string + CreatedAt time.Time +} + +func (u *User) HasTenant() bool { + return u != nil && u.TenantID > 0 && u.Status == StatusActive && u.Role != RolePending +} + +func (u *User) IsPlatformAdmin() bool { + return u != nil && authx.IsPlatformAdmin(u.Role) +} + +type TenantInfo struct { + TenantID int64 `json:"tenant_id"` + Name string `json:"name"` + Slug string `json:"slug"` // 全局唯一路径前缀,如 aaa → /aaa/ + CreatedAt time.Time `json:"created_at"` + UserCount int64 `json:"user_count"` + AppCount int64 `json:"app_count"` +} + +type Store interface { + Register(ctx context.Context, username, password, displayName string) (*User, error) + EnsureBootstrapOwner(ctx context.Context, username, password, displayName, companyName string) (*User, error) + EnsurePlatformAdmin(ctx context.Context, username, password, displayName string) (*User, error) + Login(ctx context.Context, username, password string) (*User, error) + GetByID(ctx context.Context, userID int64) (*User, error) + GetByPhone(ctx context.Context, phone string) (*User, error) + BindPhone(ctx context.Context, userID int64, phone string) (*User, error) + SetUsernameLoginDisabled(ctx context.Context, userID int64, disabled bool) (*User, error) + PhoneExists(ctx context.Context, phone string, excludeUserID int64) (bool, error) + JoinTenant(ctx context.Context, userID, tenantID int64, role string, orgUnitID int64) (*User, error) + CreateTenantAsOwner(ctx context.Context, userID int64, tenantName string) (*User, error) + SetOrgUnit(ctx context.Context, userID, tenantID, orgUnitID int64) (*User, error) + ListTenants(ctx context.Context) ([]TenantInfo, error) + CreateTenant(ctx context.Context, name, slug string) (*TenantInfo, error) + GetTenant(ctx context.Context, tenantID int64) (*TenantInfo, error) + GetTenantBySlug(ctx context.Context, slug string) (*TenantInfo, error) + UpdateTenant(ctx context.Context, tenantID int64, name, slug string) (*TenantInfo, error) + RenameTenantIf(ctx context.Context, oldName, newName string) error + EnsureTenantSlugs(ctx context.Context) error + ListMembers(ctx context.Context, tenantID int64) ([]User, error) + UpdateMember(ctx context.Context, tenantID, userID int64, role string, orgUnitID int64, status string) (*User, error) + // CreateMember 在指定公司直接创建登录账号;password 为空则随机生成。返回明文密码(仅此一次)。 + CreateMember(ctx context.Context, tenantID int64, username, password, displayName, role string, orgUnitID int64) (*User, string, error) + UsernameExists(ctx context.Context, username string) (bool, error) + // ChangePassword 校验旧密码后设置新密码(用户自行改密)。 + ChangePassword(ctx context.Context, userID int64, oldPassword, newPassword string) error + // SetPassword 管理员强制设密;password 为空则随机生成。返回明文密码。 + SetPassword(ctx context.Context, userID int64, password string) (string, error) +} + +type MemoryStore struct { + mu sync.Mutex + users map[string]*memUser + byID map[int64]*memUser + tenants map[int64]*TenantInfo + seqUser int64 + seqTen int64 +} + +type memUser struct { + User + Hash string +} + +func NewMemoryStore() *MemoryStore { + return &MemoryStore{ + users: map[string]*memUser{}, + byID: map[int64]*memUser{}, + tenants: map[int64]*TenantInfo{}, + } +} + +func (s *MemoryStore) Register(_ context.Context, username, password, displayName string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.users[username]; ok { + return nil, fmt.Errorf("username already exists") + } + if len(username) < 3 || len(password) < 6 { + return nil, fmt.Errorf("username>=3 and password>=6 required") + } + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + name := displayName + if name == "" { + name = username + } + s.seqUser++ + u := &memUser{ + User: User{ + UserID: s.seqUser, TenantID: 0, Username: username, + DisplayName: name, Role: RolePending, Status: StatusPending, CreatedAt: time.Now().UTC(), + }, + Hash: hash, + } + s.users[username] = u + s.byID[u.UserID] = u + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) EnsureBootstrapOwner(_ context.Context, username, password, displayName, companyName string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + if existing, ok := s.users[username]; ok { + cp := existing.User + return &cp, nil + } + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + name := displayName + if name == "" { + name = username + } + tenName := strings.TrimSpace(companyName) + if tenName == "" { + tenName = name + } + s.seqTen++ + s.seqUser++ + u := &memUser{ + User: User{ + UserID: s.seqUser, TenantID: s.seqTen, Username: username, + DisplayName: name, Role: authx.Role管理员, Status: StatusActive, CreatedAt: time.Now().UTC(), + }, + Hash: hash, + } + s.users[username] = u + s.byID[u.UserID] = u + s.tenants[s.seqTen] = &TenantInfo{TenantID: s.seqTen, Name: tenName, Slug: "demo", CreatedAt: time.Now().UTC()} + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) EnsurePlatformAdmin(_ context.Context, username, password, displayName string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + name := displayName + if name == "" { + name = username + } + if existing, ok := s.users[username]; ok { + existing.Hash = hash + existing.Role = authx.Role超级管理员 + existing.Status = StatusActive + existing.TenantID = 0 + existing.OrgUnitID = 0 + if name != "" { + existing.DisplayName = name + } + cp := existing.User + return &cp, nil + } + s.seqUser++ + u := &memUser{ + User: User{ + UserID: s.seqUser, TenantID: 0, Username: username, + DisplayName: name, Role: authx.Role超级管理员, Status: StatusActive, CreatedAt: time.Now().UTC(), + }, + Hash: hash, + } + s.users[username] = u + s.byID[u.UserID] = u + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) ListTenants(_ context.Context) ([]TenantInfo, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := make([]TenantInfo, 0, len(s.tenants)) + for _, t := range s.tenants { + info := *t + var uc int64 + for _, u := range s.users { + if u.TenantID == t.TenantID && u.Status == StatusActive { + uc++ + } + } + info.UserCount = uc + out = append(out, info) + } + return out, nil +} + +func (s *MemoryStore) GetTenant(_ context.Context, tenantID int64) (*TenantInfo, error) { + s.mu.Lock() + defer s.mu.Unlock() + t, ok := s.tenants[tenantID] + if !ok { + return nil, fmt.Errorf("tenant not found") + } + cp := *t + return &cp, nil +} + +func (s *MemoryStore) CreateTenant(_ context.Context, name, slug string) (*TenantInfo, error) { + s.mu.Lock() + defer s.mu.Unlock() + name = strings.TrimSpace(name) + if name == "" { + return nil, fmt.Errorf("name required") + } + if strings.TrimSpace(slug) == "" { + slug = SuggestTenantSlug(name) + } + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + for _, t := range s.tenants { + if t.Slug == ns { + return nil, fmt.Errorf("slug already exists: %s", ns) + } + } + s.seqTen++ + t := &TenantInfo{TenantID: s.seqTen, Name: name, Slug: ns, CreatedAt: time.Now().UTC()} + s.tenants[s.seqTen] = t + cp := *t + return &cp, nil +} + +func (s *MemoryStore) UpdateTenant(_ context.Context, tenantID int64, name, slug string) (*TenantInfo, error) { + s.mu.Lock() + defer s.mu.Unlock() + t, ok := s.tenants[tenantID] + if !ok { + return nil, fmt.Errorf("tenant not found") + } + name = strings.TrimSpace(name) + if name == "" { + return nil, fmt.Errorf("name required") + } + t.Name = name + if strings.TrimSpace(slug) != "" { + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + for id, other := range s.tenants { + if id != tenantID && other.Slug == ns { + return nil, fmt.Errorf("slug already exists: %s", ns) + } + } + t.Slug = ns + } + cp := *t + return &cp, nil +} + +func (s *MemoryStore) GetTenantBySlug(_ context.Context, slug string) (*TenantInfo, error) { + s.mu.Lock() + defer s.mu.Unlock() + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + for _, t := range s.tenants { + if t.Slug == ns { + cp := *t + return &cp, nil + } + } + return nil, fmt.Errorf("tenant not found") +} + +func (s *MemoryStore) EnsureTenantSlugs(_ context.Context) error { + s.mu.Lock() + defer s.mu.Unlock() + used := map[string]struct{}{} + for _, t := range s.tenants { + if t.Slug != "" { + used[t.Slug] = struct{}{} + } + } + for _, t := range s.tenants { + if t.Slug != "" { + continue + } + base := SuggestTenantSlug(t.Name) + if base == "" { + base = fmt.Sprintf("t%d", t.TenantID) + } + cand := base + for i := 2; ; i++ { + if _, ok := used[cand]; !ok { + if _, err := NormalizeTenantSlug(cand); err == nil { + break + } + } + cand = fmt.Sprintf("%s-%d", base, i) + if i > 100 { + cand = fmt.Sprintf("t%d", t.TenantID) + break + } + } + ns, err := NormalizeTenantSlug(cand) + if err != nil { + ns = fmt.Sprintf("t%d", t.TenantID) + } + t.Slug = ns + used[ns] = struct{}{} + } + return nil +} + +func (s *MemoryStore) ListMembers(_ context.Context, tenantID int64) ([]User, error) { + s.mu.Lock() + defer s.mu.Unlock() + out := make([]User, 0) + for _, u := range s.users { + if u.TenantID == tenantID && !authx.IsPlatformAdmin(u.Role) { + cp := u.User + cp.Role = authx.NormalizeRole(cp.Role) + out = append(out, cp) + } + } + return out, nil +} + +func (s *MemoryStore) UsernameExists(_ context.Context, username string) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.users[strings.TrimSpace(username)] + return ok, nil +} + +func (s *MemoryStore) CreateMember(_ context.Context, tenantID int64, username, password, displayName, role string, orgUnitID int64) (*User, string, error) { + s.mu.Lock() + defer s.mu.Unlock() + if tenantID <= 0 { + return nil, "", fmt.Errorf("tenant required") + } + if _, ok := s.tenants[tenantID]; !ok { + return nil, "", fmt.Errorf("tenant not found") + } + username = strings.TrimSpace(username) + if len(username) < 3 { + return nil, "", fmt.Errorf("username>=3 required") + } + if _, ok := s.users[username]; ok { + return nil, "", fmt.Errorf("username already exists") + } + plain := strings.TrimSpace(password) + var err error + if plain == "" { + plain, err = RandomPassword(12) + if err != nil { + return nil, "", err + } + } + if len(plain) < 6 { + return nil, "", fmt.Errorf("password>=6 required") + } + if !authx.ValidPlatformRole(role) { + return nil, "", fmt.Errorf("invalid role") + } + role = authx.NormalizeRole(role) + hash, err := hashPassword(plain) + if err != nil { + return nil, "", err + } + name := strings.TrimSpace(displayName) + if name == "" { + name = username + } + s.seqUser++ + u := &memUser{ + User: User{ + UserID: s.seqUser, TenantID: tenantID, OrgUnitID: orgUnitID, Username: username, + DisplayName: name, Role: role, Status: StatusActive, CreatedAt: time.Now().UTC(), + }, + Hash: hash, + } + s.users[username] = u + s.byID[u.UserID] = u + if t := s.tenants[tenantID]; t != nil { + t.UserCount++ + } + cp := u.User + return &cp, plain, nil +} + +func (s *MemoryStore) ChangePassword(_ context.Context, userID int64, oldPassword, newPassword string) error { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return fmt.Errorf("user not found") + } + if bcrypt.CompareHashAndPassword([]byte(u.Hash), []byte(oldPassword)) != nil { + return fmt.Errorf("旧密码不正确") + } + newPassword = strings.TrimSpace(newPassword) + if len(newPassword) < 6 { + return fmt.Errorf("password>=6 required") + } + hash, err := hashPassword(newPassword) + if err != nil { + return err + } + u.Hash = hash + return nil +} + +func (s *MemoryStore) SetPassword(_ context.Context, userID int64, password string) (string, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return "", fmt.Errorf("user not found") + } + plain := strings.TrimSpace(password) + var err error + if plain == "" { + plain, err = RandomPassword(12) + if err != nil { + return "", err + } + } + if len(plain) < 6 { + return "", fmt.Errorf("password>=6 required") + } + hash, err := hashPassword(plain) + if err != nil { + return "", err + } + u.Hash = hash + return plain, nil +} + +func (s *MemoryStore) UpdateMember(_ context.Context, tenantID, userID int64, role string, orgUnitID int64, status string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok || u.TenantID != tenantID { + return nil, fmt.Errorf("member not found") + } + if authx.IsPlatformAdmin(u.Role) { + return nil, fmt.Errorf("cannot edit platform admin") + } + if role != "" { + if !authx.ValidPlatformRole(role) { + return nil, fmt.Errorf("invalid role") + } + u.Role = authx.NormalizeRole(role) + } + if status == StatusActive || status == StatusPending || status == "disabled" { + u.Status = status + } + u.OrgUnitID = orgUnitID + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) Login(_ context.Context, account, password string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + account = strings.TrimSpace(account) + u, ok := s.users[account] + if !ok { + if phone, err := NormalizePhone(account); err == nil { + for _, cand := range s.users { + if cand.Phone == phone { + u, ok = cand, true + break + } + } + } + } + if !ok || bcrypt.CompareHashAndPassword([]byte(u.Hash), []byte(password)) != nil { + return nil, fmt.Errorf("invalid username or password") + } + cp := u.User + cp.Role = authx.NormalizeRole(cp.Role) + if cp.Status == "disabled" { + return nil, fmt.Errorf("账号已停用") + } + return &cp, nil +} + +func (s *MemoryStore) GetByID(_ context.Context, userID int64) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return nil, fmt.Errorf("user not found") + } + cp := u.User + cp.Role = authx.NormalizeRole(cp.Role) + return &cp, nil +} + +func (s *MemoryStore) GetByPhone(_ context.Context, phone string) (*User, error) { + ns, err := NormalizePhone(phone) + if err != nil { + return nil, err + } + s.mu.Lock() + defer s.mu.Unlock() + for _, u := range s.users { + if u.Phone == ns { + cp := u.User + cp.Role = authx.NormalizeRole(cp.Role) + return &cp, nil + } + } + return nil, fmt.Errorf("user not found") +} + +func (s *MemoryStore) PhoneExists(_ context.Context, phone string, excludeUserID int64) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + phone = strings.TrimSpace(phone) + if phone == "" { + return false, nil + } + for _, u := range s.users { + if u.Phone == phone && u.UserID != excludeUserID { + return true, nil + } + } + return false, nil +} + +func (s *MemoryStore) BindPhone(_ context.Context, userID int64, phone string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return nil, fmt.Errorf("user not found") + } + phone = strings.TrimSpace(phone) + if phone == "" { + u.Phone = "" + u.UsernameLoginDisabled = false + cp := u.User + return &cp, nil + } + ns, err := NormalizePhone(phone) + if err != nil { + return nil, err + } + for _, o := range s.users { + if o.Phone == ns && o.UserID != userID { + return nil, fmt.Errorf("该手机号已被绑定") + } + } + u.Phone = ns + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) SetUsernameLoginDisabled(_ context.Context, userID int64, disabled bool) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return nil, fmt.Errorf("user not found") + } + if disabled && strings.TrimSpace(u.Phone) == "" { + return nil, fmt.Errorf("请先绑定手机号,再禁用用户名登录") + } + u.UsernameLoginDisabled = disabled + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) JoinTenant(_ context.Context, userID, tenantID int64, role string, orgUnitID int64) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return nil, fmt.Errorf("user not found") + } + if u.TenantID > 0 && u.Status == StatusActive { + return nil, fmt.Errorf("user already joined a tenant") + } + if tenantID <= 0 { + return nil, fmt.Errorf("invalid tenant") + } + role = normalizePlatformRole(role) + u.TenantID = tenantID + u.OrgUnitID = orgUnitID + u.Role = role + u.Status = StatusActive + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) SetOrgUnit(_ context.Context, userID, tenantID, orgUnitID int64) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok || u.TenantID != tenantID { + return nil, fmt.Errorf("user not found") + } + u.OrgUnitID = orgUnitID + cp := u.User + return &cp, nil +} + +func (s *MemoryStore) CreateTenantAsOwner(_ context.Context, userID int64, tenantName string) (*User, error) { + s.mu.Lock() + defer s.mu.Unlock() + u, ok := s.byID[userID] + if !ok { + return nil, fmt.Errorf("user not found") + } + if u.TenantID > 0 && u.Status == StatusActive { + return nil, fmt.Errorf("user already joined a tenant") + } + name := strings.TrimSpace(tenantName) + if name == "" { + name = u.DisplayName + } + if name == "" { + name = u.Username + } + s.seqTen++ + u.TenantID = s.seqTen + u.Role = authx.Role管理员 + u.Status = StatusActive + slug := SuggestTenantSlug(name) + if slug == "" { + slug = fmt.Sprintf("t%d", s.seqTen) + } + base := slug + for i := 2; ; i++ { + clash := false + for _, t := range s.tenants { + if t.Slug == slug { + clash = true + break + } + } + if !clash { + if _, err := NormalizeTenantSlug(slug); err == nil { + break + } + } + slug = fmt.Sprintf("%s-%d", base, i) + if i > 50 { + slug = fmt.Sprintf("t%d", s.seqTen) + break + } + } + ns, _ := NormalizeTenantSlug(slug) + if ns == "" { + ns = fmt.Sprintf("t%d", s.seqTen) + } + s.tenants[s.seqTen] = &TenantInfo{TenantID: s.seqTen, Name: name, Slug: ns, CreatedAt: time.Now().UTC()} + cp := u.User + return &cp, nil +} + +type PostgresStore struct { + DB *sql.DB +} + +func NewPostgresStore(db *sql.DB) *PostgresStore { + return &PostgresStore{DB: db} +} + +func (s *PostgresStore) Register(ctx context.Context, username, password, displayName string) (*User, error) { + if len(username) < 3 || len(password) < 6 { + return nil, fmt.Errorf("username>=3 and password>=6 required") + } + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + name := displayName + if name == "" { + name = username + } + u := &User{TenantID: 0, Username: username, DisplayName: name, Role: RolePending, Status: StatusPending} + err = s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.users(tenant_id, username, password_hash, display_name, role, status) +VALUES(NULL,$1,$2,$3,'pending','pending') +RETURNING user_id, created_at`, username, hash, name, + ).Scan(&u.UserID, &u.CreatedAt) + if err != nil { + if isUnique(err) { + return nil, fmt.Errorf("username already exists") + } + return nil, err + } + return u, nil +} + +func (s *PostgresStore) EnsureBootstrapOwner(ctx context.Context, username, password, displayName, companyName string) (*User, error) { + if u, err := s.Login(ctx, username, password); err == nil { + return u, nil + } + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + + name := displayName + if name == "" { + name = username + } + tenName := strings.TrimSpace(companyName) + if tenName == "" { + tenName = name + } + tenSlug := "demo" + if tenName != "演示公司" && tenName != "演示账号" { + tenSlug = SuggestTenantSlug(tenName) + if tenSlug == "" { + tenSlug = "demo" + } + } + var tenantID int64 + if err := tx.QueryRowContext(ctx, + `INSERT INTO platform_meta.tenants(name, slug) VALUES($1,$2) RETURNING tenant_id`, tenName, tenSlug, + ).Scan(&tenantID); err != nil { + return nil, err + } + u := &User{TenantID: tenantID, Username: username, DisplayName: name, Role: authx.Role管理员, Status: StatusActive} + err = tx.QueryRowContext(ctx, ` +INSERT INTO platform_meta.users(tenant_id, username, password_hash, display_name, role, status) +VALUES($1,$2,$3,$4,$5,'active') +RETURNING user_id, created_at`, tenantID, username, hash, name, authx.Role管理员, + ).Scan(&u.UserID, &u.CreatedAt) + if err != nil { + if isUnique(err) { + _ = tx.Rollback() + return s.Login(ctx, username, password) + } + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return u, nil +} + +func (s *PostgresStore) EnsurePlatformAdmin(ctx context.Context, username, password, displayName string) (*User, error) { + hash, err := hashPassword(password) + if err != nil { + return nil, err + } + name := displayName + if name == "" { + name = username + } + + var userID int64 + err = s.DB.QueryRowContext(ctx, `SELECT user_id FROM platform_meta.users WHERE username=$1`, username).Scan(&userID) + if err == nil { + _, err = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.users +SET password_hash=$1, role=$2, status='active', tenant_id=NULL, org_unit_id=NULL, + display_name=CASE WHEN $3='' THEN display_name ELSE $3 END +WHERE user_id=$4`, hash, authx.Role超级管理员, name, userID) + if err != nil { + return nil, err + } + return s.GetByID(ctx, userID) + } + if !errors.Is(err, sql.ErrNoRows) { + return nil, err + } + + u := &User{TenantID: 0, Username: username, DisplayName: name, Role: authx.Role超级管理员, Status: StatusActive} + err = s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.users(tenant_id, username, password_hash, display_name, role, status) +VALUES(NULL,$1,$2,$3,$4,'active') +RETURNING user_id, created_at`, username, hash, name, authx.Role超级管理员, + ).Scan(&u.UserID, &u.CreatedAt) + if err != nil { + if isUnique(err) { + // 并发创建:再走一次 upsert + return s.EnsurePlatformAdmin(ctx, username, password, displayName) + } + return nil, err + } + return u, nil +} + +func (s *PostgresStore) ListTenants(ctx context.Context) ([]TenantInfo, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT t.tenant_id, t.name, COALESCE(t.slug,''), t.created_at, + (SELECT COUNT(*) FROM platform_meta.users u WHERE u.tenant_id=t.tenant_id AND COALESCE(u.status,'active')='active') AS user_count, + (SELECT COUNT(*) FROM platform_meta.tenant_apps a WHERE a.tenant_id=t.tenant_id) AS app_count +FROM platform_meta.tenants t +ORDER BY t.tenant_id`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []TenantInfo + for rows.Next() { + var t TenantInfo + if err := rows.Scan(&t.TenantID, &t.Name, &t.Slug, &t.CreatedAt, &t.UserCount, &t.AppCount); err != nil { + return nil, err + } + out = append(out, t) + } + return out, rows.Err() +} + +func (s *PostgresStore) GetTenant(ctx context.Context, tenantID int64) (*TenantInfo, error) { + var t TenantInfo + err := s.DB.QueryRowContext(ctx, ` +SELECT t.tenant_id, t.name, COALESCE(t.slug,''), t.created_at, + (SELECT COUNT(*) FROM platform_meta.users u WHERE u.tenant_id=t.tenant_id AND COALESCE(u.status,'active')='active'), + (SELECT COUNT(*) FROM platform_meta.tenant_apps a WHERE a.tenant_id=t.tenant_id) +FROM platform_meta.tenants t WHERE t.tenant_id=$1`, tenantID, + ).Scan(&t.TenantID, &t.Name, &t.Slug, &t.CreatedAt, &t.UserCount, &t.AppCount) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("tenant not found") + } + if err != nil { + return nil, err + } + return &t, nil +} + +func (s *PostgresStore) GetTenantBySlug(ctx context.Context, slug string) (*TenantInfo, error) { + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + var t TenantInfo + err = s.DB.QueryRowContext(ctx, ` +SELECT t.tenant_id, t.name, COALESCE(t.slug,''), t.created_at, + (SELECT COUNT(*) FROM platform_meta.users u WHERE u.tenant_id=t.tenant_id AND COALESCE(u.status,'active')='active'), + (SELECT COUNT(*) FROM platform_meta.tenant_apps a WHERE a.tenant_id=t.tenant_id) +FROM platform_meta.tenants t WHERE t.slug=$1`, ns, + ).Scan(&t.TenantID, &t.Name, &t.Slug, &t.CreatedAt, &t.UserCount, &t.AppCount) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("tenant not found") + } + if err != nil { + return nil, err + } + return &t, nil +} + +func (s *PostgresStore) CreateTenant(ctx context.Context, name, slug string) (*TenantInfo, error) { + name = strings.TrimSpace(name) + if name == "" { + return nil, fmt.Errorf("name required") + } + if strings.TrimSpace(slug) == "" { + slug = SuggestTenantSlug(name) + } + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + var t TenantInfo + err = s.DB.QueryRowContext(ctx, + `INSERT INTO platform_meta.tenants(name, slug) VALUES($1,$2) RETURNING tenant_id, name, slug, created_at`, name, ns, + ).Scan(&t.TenantID, &t.Name, &t.Slug, &t.CreatedAt) + if err != nil { + if isUnique(err) { + return nil, fmt.Errorf("slug already exists: %s", ns) + } + return nil, err + } + return &t, nil +} + +func (s *PostgresStore) UpdateTenant(ctx context.Context, tenantID int64, name, slug string) (*TenantInfo, error) { + name = strings.TrimSpace(name) + if name == "" { + return nil, fmt.Errorf("name required") + } + if strings.TrimSpace(slug) == "" { + res, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.tenants SET name=$1 WHERE tenant_id=$2`, name, tenantID) + if err != nil { + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + return nil, fmt.Errorf("tenant not found") + } + return s.GetTenant(ctx, tenantID) + } + ns, err := NormalizeTenantSlug(slug) + if err != nil { + return nil, err + } + res, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.tenants SET name=$1, slug=$2 WHERE tenant_id=$3`, name, ns, tenantID) + if err != nil { + if isUnique(err) { + return nil, fmt.Errorf("slug already exists: %s", ns) + } + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + return nil, fmt.Errorf("tenant not found") + } + return s.GetTenant(ctx, tenantID) +} + +func (s *PostgresStore) EnsureTenantSlugs(ctx context.Context) error { + rows, err := s.DB.QueryContext(ctx, `SELECT tenant_id, name FROM platform_meta.tenants WHERE COALESCE(slug,'')='' ORDER BY tenant_id`) + if err != nil { + return err + } + defer rows.Close() + type row struct { + id int64 + name string + } + var need []row + for rows.Next() { + var r row + if err := rows.Scan(&r.id, &r.name); err != nil { + return err + } + need = append(need, r) + } + if err := rows.Err(); err != nil { + return err + } + for _, r := range need { + base := SuggestTenantSlug(r.name) + if base == "" { + base = fmt.Sprintf("t%d", r.id) + } + cand := base + for i := 2; i < 100; i++ { + ns, nerr := NormalizeTenantSlug(cand) + if nerr != nil { + cand = fmt.Sprintf("t%d", r.id) + ns = cand + } + _, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.tenants SET slug=$1 WHERE tenant_id=$2 AND COALESCE(slug,'')=''`, ns, r.id) + if err == nil { + break + } + if isUnique(err) { + cand = fmt.Sprintf("%s-%d", base, i) + continue + } + return err + } + } + // 演示公司固定 slug=demo(若仍空或为自动生成) + _, _ = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.tenants SET slug='demo' +WHERE name IN ('演示公司','演示账号') AND (COALESCE(slug,'')='' OR slug LIKE 't%' OR slug='yan-shi') + AND NOT EXISTS (SELECT 1 FROM platform_meta.tenants x WHERE x.slug='demo' AND x.name NOT IN ('演示公司','演示账号'))`) + return nil +} + +func (s *PostgresStore) RenameTenantIf(ctx context.Context, oldName, newName string) error { + oldName = strings.TrimSpace(oldName) + newName = strings.TrimSpace(newName) + if oldName == "" || newName == "" || oldName == newName { + return nil + } + _, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.tenants SET name=$1 WHERE name=$2`, newName, oldName) + return err +} + +func (s *MemoryStore) RenameTenantIf(_ context.Context, oldName, newName string) error { + s.mu.Lock() + defer s.mu.Unlock() + oldName = strings.TrimSpace(oldName) + newName = strings.TrimSpace(newName) + if oldName == "" || newName == "" { + return nil + } + for _, t := range s.tenants { + if t.Name == oldName { + t.Name = newName + } + } + return nil +} + +func (s *PostgresStore) ListMembers(ctx context.Context, tenantID int64) ([]User, error) { + rows, err := s.DB.QueryContext(ctx, ` +SELECT user_id, COALESCE(tenant_id,0), COALESCE(org_unit_id,0), username, COALESCE(phone,''), COALESCE(username_login_disabled,false), display_name, role, COALESCE(status,'active'), created_at +FROM platform_meta.users +WHERE tenant_id=$1 +ORDER BY user_id`, tenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []User + for rows.Next() { + var u User + if err := rows.Scan(&u.UserID, &u.TenantID, &u.OrgUnitID, &u.Username, &u.Phone, &u.UsernameLoginDisabled, &u.DisplayName, &u.Role, &u.Status, &u.CreatedAt); err != nil { + return nil, err + } + u.Role = authx.NormalizeRole(u.Role) + if authx.IsPlatformAdmin(u.Role) { + continue + } + out = append(out, u) + } + return out, rows.Err() +} + +func (s *PostgresStore) UsernameExists(ctx context.Context, username string) (bool, error) { + var n int + err := s.DB.QueryRowContext(ctx, `SELECT 1 FROM platform_meta.users WHERE username=$1 LIMIT 1`, strings.TrimSpace(username)).Scan(&n) + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (s *PostgresStore) CreateMember(ctx context.Context, tenantID int64, username, password, displayName, role string, orgUnitID int64) (*User, string, error) { + if tenantID <= 0 { + return nil, "", fmt.Errorf("tenant required") + } + if _, err := s.GetTenant(ctx, tenantID); err != nil { + return nil, "", err + } + username = strings.TrimSpace(username) + if len(username) < 3 { + return nil, "", fmt.Errorf("username>=3 required") + } + plain := strings.TrimSpace(password) + var err error + if plain == "" { + plain, err = RandomPassword(12) + if err != nil { + return nil, "", err + } + } + if len(plain) < 6 { + return nil, "", fmt.Errorf("password>=6 required") + } + if !authx.ValidPlatformRole(role) { + return nil, "", fmt.Errorf("invalid role") + } + role = authx.NormalizeRole(role) + hash, err := hashPassword(plain) + if err != nil { + return nil, "", err + } + name := strings.TrimSpace(displayName) + if name == "" { + name = username + } + u := &User{TenantID: tenantID, OrgUnitID: orgUnitID, Username: username, DisplayName: name, Role: role, Status: StatusActive} + err = s.DB.QueryRowContext(ctx, ` +INSERT INTO platform_meta.users(tenant_id, org_unit_id, username, password_hash, display_name, role, status) +VALUES($1,NULLIF($2,0),$3,$4,$5,$6,'active') +RETURNING user_id, created_at`, tenantID, orgUnitID, username, hash, name, role, + ).Scan(&u.UserID, &u.CreatedAt) + if err != nil { + if isUnique(err) { + return nil, "", fmt.Errorf("username already exists") + } + return nil, "", err + } + return u, plain, nil +} + +func (s *PostgresStore) ChangePassword(ctx context.Context, userID int64, oldPassword, newPassword string) error { + var hash string + err := s.DB.QueryRowContext(ctx, `SELECT password_hash FROM platform_meta.users WHERE user_id=$1`, userID).Scan(&hash) + if errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("user not found") + } + if err != nil { + return err + } + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(oldPassword)) != nil { + return fmt.Errorf("旧密码不正确") + } + newPassword = strings.TrimSpace(newPassword) + if len(newPassword) < 6 { + return fmt.Errorf("password>=6 required") + } + nh, err := hashPassword(newPassword) + if err != nil { + return err + } + _, err = s.DB.ExecContext(ctx, `UPDATE platform_meta.users SET password_hash=$1 WHERE user_id=$2`, nh, userID) + return err +} + +func (s *PostgresStore) SetPassword(ctx context.Context, userID int64, password string) (string, error) { + plain := strings.TrimSpace(password) + var err error + if plain == "" { + plain, err = RandomPassword(12) + if err != nil { + return "", err + } + } + if len(plain) < 6 { + return "", fmt.Errorf("password>=6 required") + } + nh, err := hashPassword(plain) + if err != nil { + return "", err + } + res, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.users SET password_hash=$1 WHERE user_id=$2`, nh, userID) + if err != nil { + return "", err + } + n, _ := res.RowsAffected() + if n == 0 { + return "", fmt.Errorf("user not found") + } + return plain, nil +} + +func (s *PostgresStore) UpdateMember(ctx context.Context, tenantID, userID int64, role string, orgUnitID int64, status string) (*User, error) { + cur, err := s.GetByID(ctx, userID) + if err != nil { + return nil, err + } + if cur.TenantID != tenantID { + return nil, fmt.Errorf("member not found") + } + if authx.IsPlatformAdmin(cur.Role) { + return nil, fmt.Errorf("cannot edit platform admin") + } + if role != "" { + if !authx.ValidPlatformRole(role) { + return nil, fmt.Errorf("invalid role") + } + role = authx.NormalizeRole(role) + } else { + role = cur.Role + } + if status == "" { + status = cur.Status + } + if status != StatusActive && status != StatusPending && status != "disabled" { + return nil, fmt.Errorf("invalid status") + } + _, err = s.DB.ExecContext(ctx, ` +UPDATE platform_meta.users SET role=$1, org_unit_id=NULLIF($2,0), status=$3 +WHERE user_id=$4 AND tenant_id=$5`, role, orgUnitID, status, userID, tenantID) + if err != nil { + return nil, err + } + return s.GetByID(ctx, userID) +} + +func (s *PostgresStore) Login(ctx context.Context, account, password string) (*User, error) { + account = strings.TrimSpace(account) + u, hash, err := s.loadAuthByUsername(ctx, account) + if errors.Is(err, sql.ErrNoRows) { + if phone, nerr := NormalizePhone(account); nerr == nil { + u, hash, err = s.loadAuthByPhone(ctx, phone) + } + } + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("invalid username or password") + } + if err != nil { + return nil, err + } + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) != nil { + return nil, fmt.Errorf("invalid username or password") + } + u.Role = authx.NormalizeRole(u.Role) + if u.Status == "disabled" { + return nil, fmt.Errorf("账号已停用") + } + return u, nil +} + +func (s *PostgresStore) loadAuthByUsername(ctx context.Context, username string) (*User, string, error) { + var u User + var hash string + var tenant sql.NullInt64 + err := s.DB.QueryRowContext(ctx, ` +SELECT user_id, tenant_id, COALESCE(org_unit_id,0), username, COALESCE(phone,''), COALESCE(username_login_disabled,false), password_hash, display_name, role, COALESCE(status,'active'), created_at +FROM platform_meta.users WHERE username=$1`, username, + ).Scan(&u.UserID, &tenant, &u.OrgUnitID, &u.Username, &u.Phone, &u.UsernameLoginDisabled, &hash, &u.DisplayName, &u.Role, &u.Status, &u.CreatedAt) + if err != nil { + return nil, "", err + } + if tenant.Valid { + u.TenantID = tenant.Int64 + } + return &u, hash, nil +} + +func (s *PostgresStore) loadAuthByPhone(ctx context.Context, phone string) (*User, string, error) { + var u User + var hash string + var tenant sql.NullInt64 + err := s.DB.QueryRowContext(ctx, ` +SELECT user_id, tenant_id, COALESCE(org_unit_id,0), username, COALESCE(phone,''), COALESCE(username_login_disabled,false), password_hash, display_name, role, COALESCE(status,'active'), created_at +FROM platform_meta.users WHERE phone=$1`, phone, + ).Scan(&u.UserID, &tenant, &u.OrgUnitID, &u.Username, &u.Phone, &u.UsernameLoginDisabled, &hash, &u.DisplayName, &u.Role, &u.Status, &u.CreatedAt) + if err != nil { + return nil, "", err + } + if tenant.Valid { + u.TenantID = tenant.Int64 + } + return &u, hash, nil +} + +func (s *PostgresStore) GetByID(ctx context.Context, userID int64) (*User, error) { + var u User + var tenant sql.NullInt64 + err := s.DB.QueryRowContext(ctx, ` +SELECT user_id, tenant_id, COALESCE(org_unit_id,0), username, COALESCE(phone,''), COALESCE(username_login_disabled,false), display_name, role, COALESCE(status,'active'), created_at +FROM platform_meta.users WHERE user_id=$1`, userID, + ).Scan(&u.UserID, &tenant, &u.OrgUnitID, &u.Username, &u.Phone, &u.UsernameLoginDisabled, &u.DisplayName, &u.Role, &u.Status, &u.CreatedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("user not found") + } + if err != nil { + return nil, err + } + if tenant.Valid { + u.TenantID = tenant.Int64 + } + u.Role = authx.NormalizeRole(u.Role) + return &u, nil +} + +func (s *PostgresStore) GetByPhone(ctx context.Context, phone string) (*User, error) { + ns, err := NormalizePhone(phone) + if err != nil { + return nil, err + } + var u User + var tenant sql.NullInt64 + err = s.DB.QueryRowContext(ctx, ` +SELECT user_id, tenant_id, COALESCE(org_unit_id,0), username, COALESCE(phone,''), COALESCE(username_login_disabled,false), display_name, role, COALESCE(status,'active'), created_at +FROM platform_meta.users WHERE phone=$1`, ns, + ).Scan(&u.UserID, &tenant, &u.OrgUnitID, &u.Username, &u.Phone, &u.UsernameLoginDisabled, &u.DisplayName, &u.Role, &u.Status, &u.CreatedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("user not found") + } + if err != nil { + return nil, err + } + if tenant.Valid { + u.TenantID = tenant.Int64 + } + u.Role = authx.NormalizeRole(u.Role) + return &u, nil +} + +func (s *PostgresStore) PhoneExists(ctx context.Context, phone string, excludeUserID int64) (bool, error) { + phone = strings.TrimSpace(phone) + if phone == "" { + return false, nil + } + var n int + err := s.DB.QueryRowContext(ctx, ` +SELECT 1 FROM platform_meta.users WHERE phone=$1 AND user_id<>$2 LIMIT 1`, phone, excludeUserID).Scan(&n) + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (s *PostgresStore) BindPhone(ctx context.Context, userID int64, phone string) (*User, error) { + phone = strings.TrimSpace(phone) + if phone == "" { + _, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.users SET phone='', username_login_disabled=false WHERE user_id=$1`, userID) + if err != nil { + return nil, err + } + return s.GetByID(ctx, userID) + } + ns, err := NormalizePhone(phone) + if err != nil { + return nil, err + } + exists, err := s.PhoneExists(ctx, ns, userID) + if err != nil { + return nil, err + } + if exists { + return nil, fmt.Errorf("该手机号已被绑定") + } + res, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.users SET phone=$1 WHERE user_id=$2`, ns, userID) + if err != nil { + if isUnique(err) { + return nil, fmt.Errorf("该手机号已被绑定") + } + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + return nil, fmt.Errorf("user not found") + } + return s.GetByID(ctx, userID) +} + +func (s *PostgresStore) SetUsernameLoginDisabled(ctx context.Context, userID int64, disabled bool) (*User, error) { + u, err := s.GetByID(ctx, userID) + if err != nil { + return nil, err + } + if disabled && strings.TrimSpace(u.Phone) == "" { + return nil, fmt.Errorf("请先绑定手机号,再禁用用户名登录") + } + _, err = s.DB.ExecContext(ctx, `UPDATE platform_meta.users SET username_login_disabled=$1 WHERE user_id=$2`, disabled, userID) + if err != nil { + return nil, err + } + return s.GetByID(ctx, userID) +} + +func (s *PostgresStore) JoinTenant(ctx context.Context, userID, tenantID int64, role string, orgUnitID int64) (*User, error) { + if tenantID <= 0 { + return nil, fmt.Errorf("invalid tenant") + } + role = normalizePlatformRole(role) + res, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.users +SET tenant_id=$1, role=$2, status='active', org_unit_id=NULLIF($3,0) +WHERE user_id=$4 AND (tenant_id IS NULL OR status='pending' OR role='pending' OR role='待加入')`, + tenantID, role, orgUnitID, userID) + if err != nil { + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + u, gerr := s.GetByID(ctx, userID) + if gerr != nil { + return nil, fmt.Errorf("user not found") + } + if u.HasTenant() { + return nil, fmt.Errorf("user already joined a tenant") + } + return nil, fmt.Errorf("join tenant failed") + } + return s.GetByID(ctx, userID) +} + +func (s *PostgresStore) SetOrgUnit(ctx context.Context, userID, tenantID, orgUnitID int64) (*User, error) { + res, err := s.DB.ExecContext(ctx, ` +UPDATE platform_meta.users SET org_unit_id=NULLIF($1,0) +WHERE user_id=$2 AND tenant_id=$3`, orgUnitID, userID, tenantID) + if err != nil { + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + return nil, fmt.Errorf("user not found") + } + return s.GetByID(ctx, userID) +} + +func (s *PostgresStore) CreateTenantAsOwner(ctx context.Context, userID int64, tenantName string) (*User, error) { + u, err := s.GetByID(ctx, userID) + if err != nil { + return nil, err + } + if u.HasTenant() { + return nil, fmt.Errorf("user already joined a tenant") + } + name := strings.TrimSpace(tenantName) + if name == "" { + name = u.DisplayName + } + if name == "" { + name = u.Username + } + tx, err := s.DB.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + + var tenantID int64 + slug := SuggestTenantSlug(name) + if slug == "" { + slug = fmt.Sprintf("co%d", time.Now().Unix()%100000) + } + ns, nerr := NormalizeTenantSlug(slug) + if nerr != nil { + ns = fmt.Sprintf("t%d", time.Now().Unix()%1000000) + } + for i := 0; i < 20; i++ { + try := ns + if i > 0 { + try = fmt.Sprintf("%s-%d", ns, i+1) + } + err = tx.QueryRowContext(ctx, + `INSERT INTO platform_meta.tenants(name, slug) VALUES($1,$2) RETURNING tenant_id`, name, try, + ).Scan(&tenantID) + if err == nil { + break + } + if !isUnique(err) { + return nil, err + } + } + if err != nil { + return nil, fmt.Errorf("create tenant slug conflict") + } + res, err := tx.ExecContext(ctx, ` +UPDATE platform_meta.users +SET tenant_id=$1, role='owner', status='active' +WHERE user_id=$2 AND (tenant_id IS NULL OR status='pending' OR role='pending')`, + tenantID, userID) + if err != nil { + return nil, err + } + n, _ := res.RowsAffected() + if n == 0 { + return nil, fmt.Errorf("user already joined a tenant") + } + if err := tx.Commit(); err != nil { + return nil, err + } + return s.GetByID(ctx, userID) +} + +// EnsureDemoUser 开发态确保 demo/demo123 为「演示公司」的公司管理员(不是超管自己)。 +func EnsureDemoUser(ctx context.Context, store Store) { + if store == nil { + return + } + if _, err := store.Login(ctx, "demo", "demo123"); err != nil { + _, _ = store.EnsureBootstrapOwner(ctx, "demo", "demo123", "演示用户", "演示公司") + } + _ = store.RenameTenantIf(ctx, "演示账号", "演示公司") + _ = store.EnsureTenantSlugs(ctx) + ensureDevPhone(ctx, store, "demo", "13800000001") +} + +// EnsurePlatformAdminUser 确保平台超级管理员 ljk_admin / ljk_admin。 +func EnsurePlatformAdminUser(ctx context.Context, store Store) { + if store == nil { + return + } + if _, err := store.EnsurePlatformAdmin(ctx, "ljk_admin", "ljk_admin", "平台超级管理员"); err != nil { + fmt.Printf("ensure platform admin ljk_admin: %v\n", err) + } + ensureDevPhone(ctx, store, "ljk_admin", "13800000000") +} + +func ensureDevPhone(ctx context.Context, store Store, username, phone string) { + var user *User + var err error + switch username { + case "demo": + user, err = store.Login(ctx, "demo", "demo123") + case "ljk_admin": + user, err = store.Login(ctx, "ljk_admin", "ljk_admin") + default: + return + } + if err != nil || user == nil { + return + } + if strings.TrimSpace(user.Phone) != "" { + return + } + if _, err := store.BindPhone(ctx, user.UserID, phone); err != nil { + fmt.Printf("ensure phone for %s: %v\n", username, err) + return + } + _, _ = store.SetUsernameLoginDisabled(ctx, user.UserID, true) +} + +func hashPassword(pw string) (string, error) { + b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) + return string(b), err +} + +func isUnique(err error) bool { + return err != nil && (strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "unique")) +} + +func normalizePlatformRole(role string) string { + n := authx.NormalizeRole(role) + if authx.ValidPlatformRole(n) { + return n + } + return authx.Role编辑 +} diff --git a/platform/internal/userstore/tenant_slug.go b/platform/internal/userstore/tenant_slug.go new file mode 100644 index 0000000..f32fbdd --- /dev/null +++ b/platform/internal/userstore/tenant_slug.go @@ -0,0 +1,77 @@ +package userstore + +import ( + "fmt" + "regexp" + "strings" + "unicode" +) + +var tenantSlugRe = regexp.MustCompile(`^[a-z][a-z0-9-]{1,31}$`) + +// 路径段保留字:不可用作公司 slug(与后续 /{slug}/ 路由冲突) +var reservedTenantSlugs = map[string]struct{}{ + "api": {}, "ai": {}, "gateway": {}, "platform": {}, "_platform": {}, + "admin": {}, "assets": {}, "static": {}, "www": {}, "m": {}, "public": {}, + "health": {}, "ops": {}, "console": {}, "login": {}, "register": {}, + "favicon.ico": {}, "robots.txt": {}, +} + +// NormalizeTenantSlug 规范化公司路径 slug(全局唯一,用于 www.yuxinda.com/{slug}/)。 +func NormalizeTenantSlug(raw string) (string, error) { + s := strings.TrimSpace(strings.ToLower(raw)) + s = strings.ReplaceAll(s, "_", "-") + s = strings.ReplaceAll(s, " ", "-") + var b strings.Builder + prevDash := false + for _, r := range s { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { + b.WriteRune(r) + prevDash = false + continue + } + if r == '-' { + if b.Len() == 0 || prevDash { + continue + } + b.WriteByte('-') + prevDash = true + continue + } + // 跳过其它字符(含中文);中文名需用户另填 slug + } + s = strings.Trim(b.String(), "-") + if s == "" { + return "", fmt.Errorf("公司路径 slug 不能为空(请用英文/数字,如 aaa)") + } + if !tenantSlugRe.MatchString(s) { + return "", fmt.Errorf("slug 须为 2–32 位,小写字母开头,仅含 a-z / 0-9 / -") + } + if _, bad := reservedTenantSlugs[s]; bad { + return "", fmt.Errorf("slug %q 为系统保留字", s) + } + return s, nil +} + +// SuggestTenantSlug 从公司名生成候选(中文名可能得不到可用 slug,需人工填写)。 +func SuggestTenantSlug(name string) string { + name = strings.TrimSpace(name) + if name == "" { + return "" + } + // 纯 ASCII 名:直接规范化 + ascii := true + for _, r := range name { + if r > unicode.MaxASCII { + ascii = false + break + } + } + if ascii { + s, err := NormalizeTenantSlug(name) + if err == nil { + return s + } + } + return "" +} diff --git a/platform/internal/userstore/tenant_slug_test.go b/platform/internal/userstore/tenant_slug_test.go new file mode 100644 index 0000000..0eddd48 --- /dev/null +++ b/platform/internal/userstore/tenant_slug_test.go @@ -0,0 +1,19 @@ +package userstore + +import "testing" + +func TestNormalizeTenantSlug(t *testing.T) { + ok, err := NormalizeTenantSlug("Aaa-01") + if err != nil || ok != "aaa-01" { + t.Fatalf("got %q %v", ok, err) + } + if _, err := NormalizeTenantSlug("api"); err == nil { + t.Fatal("api should be reserved") + } + if _, err := NormalizeTenantSlug("1abc"); err == nil { + t.Fatal("must start with letter") + } + if SuggestTenantSlug("Hello World") != "hello-world" { + t.Fatalf("suggest ascii") + } +} diff --git a/platform/platform.api b/platform/platform.api new file mode 100644 index 0000000..961ba2f --- /dev/null +++ b/platform/platform.api @@ -0,0 +1,38 @@ +syntax = "v1" + +info ( + title: "platform" + desc: "AppBlueprint publish + dynamic CRUD skeleton" + author: "aijianzhan" + version: "v1" +) + +@server ( + prefix: /api/v1 +) + +service platform { + @handler IssueToken + post /auth/token + + @handler PublishApp + post /apps/:slug/publish + + @handler GetBlueprint + get /apps/:slug/blueprint + + @handler ListRows + get /apps/:slug/:resource + + @handler CreateRow + post /apps/:slug/:resource + + @handler GetRow + get /apps/:slug/:resource/:id + + @handler UpdateRow + patch /apps/:slug/:resource/:id + + @handler DeleteRow + delete /apps/:slug/:resource/:id +} diff --git a/platform/platform.go b/platform/platform.go new file mode 100644 index 0000000..0515667 --- /dev/null +++ b/platform/platform.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + + "aijianzhan/platform/internal/config" + "aijianzhan/platform/internal/handler" + "aijianzhan/platform/internal/license" + "aijianzhan/platform/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +var configFile = flag.String("f", "etc/platform.yaml", "config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + server := rest.MustNewServer(c.RestConf, rest.WithCors()) + defer server.Stop() + + ctx := svc.NewServiceContext(c) + server.Use(func(next http.HandlerFunc) http.HandlerFunc { + return license.Middleware(ctx.License)(next) + }) + handler.RegisterHandlers(server, ctx) + + runCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + if ctx.DBSync != nil { + ctx.DBSync.StartAll(runCtx) + } + + go func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + <-ch + cancel() + server.Stop() + }() + + fmt.Printf("platform starting at %s:%d (memoryMode=%v dryRun=%v dbsync=%v)\n", + c.Host, c.Port, ctx.MemoryMode, c.DryRun, ctx.DBSync != nil) + server.Start() +} diff --git a/platform/scripts/smoke.go b/platform/scripts/smoke.go new file mode 100644 index 0000000..ba5cf55 --- /dev/null +++ b/platform/scripts/smoke.go @@ -0,0 +1,105 @@ +//go:build ignore + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "time" +) + +func main() { + base := "http://127.0.0.1:8888" + + fmt.Println("== token ==") + tokBody, _ := json.Marshal(map[string]any{ + "tenant_id": 1, + "user_id": 1, + "secret": "dev-only-change-me", + }) + code, raw := do(http.MethodPost, base+"/api/v1/auth/token", tokBody, "") + fmt.Printf("status=%d\n%s\n", code, raw) + var tok struct { + AccessToken string `json:"access_token"` + } + must(json.Unmarshal(raw, &tok)) + if tok.AccessToken == "" { + panic("empty access_token") + } + bearer := tok.AccessToken + + // 若已发布则跳过 publish,验证元数据落库后重启仍可 CRUD + fmt.Println("== blueprint (check existing) ==") + bpCode, bpRaw := do(http.MethodGet, base+"/api/v1/apps/inventory_ledger/blueprint", nil, bearer) + fmt.Printf("status=%d\n", bpCode) + + if bpCode != 200 { + bpPath := filepath.Join("..", "blueprint", "examples", "inventory-ledger.blueprint.json") + bp, err := os.ReadFile(bpPath) + must(err) + pubBody, _ := json.Marshal(map[string]json.RawMessage{"blueprint": bp}) + fmt.Println("== publish ==") + printDo(http.MethodPost, base+"/api/v1/apps/inventory_ledger/publish", pubBody, bearer) + } else { + fmt.Println("skip publish: app already in meta store") + fmt.Printf("%s\n", truncate(bpRaw, 200)) + } + + sku := fmt.Sprintf("A-%d", time.Now().Unix()%100000) + row, _ := json.Marshal(map[string]any{ + "sku": sku, "product_name": "无线鼠标", "warehouse": "华东仓", + "qty": 120, "unit_price": 59.9, "status": "在售", + }) + fmt.Println("== create ==") + printDo(http.MethodPost, base+"/api/v1/apps/inventory_ledger/inventory_items", row, bearer) + + q := url.Values{} + q.Set("filter.warehouse", "华东仓") + q.Set("sort", "-qty") + fmt.Println("== list ==") + printDo(http.MethodGet, base+"/api/v1/apps/inventory_ledger/inventory_items?"+q.Encode(), nil, bearer) +} + +func truncate(b []byte, n int) string { + if len(b) <= n { + return string(b) + } + return string(b[:n]) + "..." +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func printDo(method, rawURL string, body []byte, bearer string) { + code, raw := do(method, rawURL, body, bearer) + fmt.Printf("status=%d\n%s\n", code, raw) +} + +func do(method, rawURL string, body []byte, bearer string) (int, []byte) { + var r io.Reader + if body != nil { + r = bytes.NewReader(body) + } + req, err := http.NewRequest(method, rawURL, r) + must(err) + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + if bearer != "" { + req.Header.Set("Authorization", "Bearer "+bearer) + } + resp, err := http.DefaultClient.Do(req) + must(err) + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + return resp.StatusCode, b +} diff --git a/pull-and-restart.sh b/pull-and-restart.sh new file mode 100644 index 0000000..b6d37bc --- /dev/null +++ b/pull-and-restart.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# AI 建站 — 拉取代码并重启 +# 用法:./pull-and-restart.sh +# 环境变量:GIT_BRANCH(默认 master)、FORCE_GIT_RESET(默认 1,与远程硬对齐) +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" +# shellcheck disable=SC1091 +. "$ROOT/scripts/lib-aijz-deploy.sh" + +echo "========================================" +echo " AI 建站 拉取并重启" +echo " 路径: $ROOT" +echo "========================================" + +ensure_docker +git_pull_hard || true +stack_down +sleep 1 +stack_up 1 +healthcheck +print_endpoints diff --git a/reload-config.sh b/reload-config.sh new file mode 100644 index 0000000..6b15069 --- /dev/null +++ b/reload-config.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# 轻量重载配置(不 rebuild 镜像、不重建 web/dist、不 down 整栈) +# 用法: +# ./reload-config.sh # 全部 +# ./reload-config.sh web # 容器 nginx(web/nginx.conf) +# ./reload-config.sh platform # restart platform(platform/etc/platform.docker.yaml) +# ./reload-config.sh gateway # restart gateway +# ./reload-config.sh ai # recreate ai(.env) +# ./reload-config.sh host-nginx # 宿主机 Nginx + 证书 + verify-root +# 行尾:LF +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" +# shellcheck disable=SC1091 +. "$ROOT/scripts/lib-aijz-deploy.sh" + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; +esac diff --git a/restart.bat b/restart.bat new file mode 100644 index 0000000..7bbea4c --- /dev/null +++ b/restart.bat @@ -0,0 +1,4 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0restart.ps1" %* \ No newline at end of file diff --git a/restart.ps1 b/restart.ps1 new file mode 100644 index 0000000..6bc37cf --- /dev/null +++ b/restart.ps1 @@ -0,0 +1,23 @@ +# restart: stop then start +param( + [switch]$Rebuild, + [switch]$UseHost, + [switch]$NoBrowser, + [switch]$ShowConsole +) + +$ErrorActionPreference = "Continue" +$Root = $PSScriptRoot +Set-Location -LiteralPath $Root + +Write-Host "==> Restart: stopping..." -ForegroundColor Yellow +& (Join-Path $Root "scripts\Stop-Stack.ps1") +if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne $null) { + Write-Host " (stop reported leftovers; continuing restart anyway)" -ForegroundColor DarkGray +} + +Start-Sleep -Seconds 2 + +Write-Host "==> Restart: starting..." -ForegroundColor Cyan +& (Join-Path $Root "start.ps1") -Rebuild:$Rebuild -UseHost:$UseHost -NoBrowser:$NoBrowser -ShowConsole:$ShowConsole +exit $LASTEXITCODE diff --git a/restart.sh b/restart.sh new file mode 100644 index 0000000..b89de9e --- /dev/null +++ b/restart.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# AI 建站 — 重启(不拉代码:停 → 构建 → 起) +# 用法:./restart.sh [--rebuild] +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" +# shellcheck disable=SC1091 +. "$ROOT/scripts/lib-aijz-deploy.sh" + +REBUILD=1 +for a in "$@"; do + case "$a" in + --no-rebuild) REBUILD=0 ;; + -h|--help) + echo "用法: $0 [--no-rebuild]" + exit 0 + ;; + esac +done + +echo "========================================" +echo " AI 建站 重启" +echo " 路径: $ROOT" +echo "========================================" + +ensure_docker +stack_down +sleep 1 +stack_up "$REBUILD" +healthcheck +print_endpoints diff --git a/scripts/Apply-DockerMirrors.ps1 b/scripts/Apply-DockerMirrors.ps1 new file mode 100644 index 0000000..b1a0934 --- /dev/null +++ b/scripts/Apply-DockerMirrors.ps1 @@ -0,0 +1,56 @@ +# Apply China Docker Hub registry mirrors to Docker Desktop daemon.json. +# Safe merge: keeps existing keys; only sets/extends registry-mirrors. +# Compatible with Windows PowerShell 5.1. +param( + [switch]$NoRestartHint +) + +$ErrorActionPreference = "Stop" +$Root = Split-Path -Parent $PSScriptRoot + +$dockerDir = Join-Path $env:USERPROFILE ".docker" +New-Item -ItemType Directory -Force -Path $dockerDir | Out-Null +$daemonPath = Join-Path $dockerDir "daemon.json" + +$wanted = @( + "https://docker.m.daocloud.io", + "https://docker.1ms.run", + "https://docker.xuanyuan.me" +) + +$cfg = [ordered]@{} +if (Test-Path -LiteralPath $daemonPath) { + try { + $raw = Get-Content -LiteralPath $daemonPath -Raw -Encoding UTF8 + if ($raw -and $raw.Trim()) { + $obj = $raw | ConvertFrom-Json + foreach ($p in $obj.PSObject.Properties) { + $cfg[$p.Name] = $p.Value + } + } + } catch { + Write-Host " !! existing daemon.json parse failed; rewriting mirrors" -ForegroundColor Yellow + $cfg = [ordered]@{} + } +} + +$existing = @() +if ($cfg.Contains("registry-mirrors") -and $cfg["registry-mirrors"]) { + $existing = @($cfg["registry-mirrors"] | ForEach-Object { [string]$_ }) +} +$merged = New-Object System.Collections.Generic.List[string] +foreach ($m in ($wanted + $existing)) { + if ($m -and -not $merged.Contains($m)) { [void]$merged.Add($m) } +} +$cfg["registry-mirrors"] = @($merged) + +$json = $cfg | ConvertTo-Json -Depth 8 +Set-Content -LiteralPath $daemonPath -Value $json -Encoding UTF8 +Write-Host (" OK Docker mirrors -> {0}" -f $daemonPath) -ForegroundColor DarkGray +foreach ($m in $merged) { + Write-Host (" {0}" -f $m) -ForegroundColor DarkGray +} + +if (-not $NoRestartHint) { + Write-Host " !! Restart Docker Desktop once if compose still cannot pull" -ForegroundColor Yellow +} diff --git a/scripts/Ensure-Web.ps1 b/scripts/Ensure-Web.ps1 new file mode 100644 index 0000000..dd05a67 --- /dev/null +++ b/scripts/Ensure-Web.ps1 @@ -0,0 +1,57 @@ +# Ensure web frontend: npm install + optional dist build for Docker nginx. +# Called automatically by start.ps1 / start-host.ps1 (no manual npm). +param( + [switch]$BuildDist +) + +$ErrorActionPreference = "Stop" +$Root = Split-Path -Parent $PSScriptRoot +if (-not (Test-Path (Join-Path $Root "web\package.json"))) { + throw "web/package.json not found under repo root" +} +$WebDir = Join-Path $Root "web" + +$npmCmd = Get-Command npm.cmd -ErrorAction SilentlyContinue +if (-not $npmCmd) { $npmCmd = Get-Command npm -ErrorAction SilentlyContinue } +if (-not $npmCmd) { throw "npm not found" } + +Push-Location $WebDir +try { + if (-not (Test-Path "node_modules")) { + Write-Host " npm install (web) ..." -ForegroundColor DarkGray + & $npmCmd.Source install + if ($LASTEXITCODE -ne 0) { throw "npm install failed" } + } + + if (-not $BuildDist) { + # Clear stale native exit code left by caller (e.g. docker compose failure) + $global:LASTEXITCODE = 0 + return + } + + $distIndex = Join-Path $WebDir "dist\index.html" + $needBuild = -not (Test-Path $distIndex) + if (-not $needBuild) { + $distTime = (Get-Item $distIndex).LastWriteTime + $newer = Get-ChildItem -Path (Join-Path $WebDir "src") -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -gt $distTime } | + Select-Object -First 1 + if ($newer) { $needBuild = $true } + $cfgNewer = Get-ChildItem -Path $WebDir -File -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match '^(vite\.config|index\.html|package\.json)' -and $_.LastWriteTime -gt $distTime } | + Select-Object -First 1 + if ($cfgNewer) { $needBuild = $true } + } + if ($needBuild) { + Write-Host " npm run build (web dist, includes /#/preview) ..." -ForegroundColor DarkGray + & $npmCmd.Source run build + if ($LASTEXITCODE -ne 0) { throw "npm run build failed" } + if (-not (Test-Path $distIndex)) { throw "web/dist/index.html missing after build" } + Write-Host " OK web/dist ready" -ForegroundColor DarkGray + } else { + Write-Host " OK web/dist up to date" -ForegroundColor DarkGray + } + $global:LASTEXITCODE = 0 +} finally { + Pop-Location +} diff --git a/scripts/Stop-Stack.ps1 b/scripts/Stop-Stack.ps1 new file mode 100644 index 0000000..8499b3b --- /dev/null +++ b/scripts/Stop-Stack.ps1 @@ -0,0 +1,173 @@ +# Unified stack teardown: Docker containers (if any) + ALL native leftovers. +# Used by stop.ps1 / restart.ps1 / start.ps1 / start-host.ps1. +# Console messages are ASCII-only to avoid GBK mojibake. +param( + [switch]$Quiet +) + +$ErrorActionPreference = "SilentlyContinue" +$Root = Split-Path -Parent $PSScriptRoot +if (-not (Test-Path (Join-Path $Root "start.ps1"))) { + # allow calling from repo root as -File scripts\Stop-Stack.ps1 + if (Test-Path (Join-Path $PSScriptRoot "..\start.ps1")) { + $Root = Resolve-Path (Join-Path $PSScriptRoot "..") + } +} +Set-Location -LiteralPath $Root +$LogDir = Join-Path $Root ".runtime\logs" + +function Write-Info([string]$msg) { + if (-not $Quiet) { Write-Host $msg } +} + +function Kill-Tree([int]$ProcessId) { + if ($ProcessId -le 0) { return } + # Skip self / critical system + if ($ProcessId -eq $PID) { return } + & taskkill.exe /F /T /PID $ProcessId 2>$null | Out-Null + Stop-Process -Id $ProcessId -Force -ErrorAction SilentlyContinue +} + +function Kill-Port([int]$Port) { + $pids = @() + Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | ForEach-Object { + $pids += [int]$_.OwningProcess + } + $net = & netstat.exe -ano 2>$null + foreach ($line in $net) { + if ($line -notmatch [regex]::Escape(":${Port}")) { continue } + if ($line -notmatch "LISTENING") { continue } + $parts = ($line -split "\s+") | Where-Object { $_ } + $procId = 0 + if ([int]::TryParse($parts[-1], [ref]$procId) -and $procId -gt 0) { + $pids += $procId + } + } + foreach ($procId in ($pids | Select-Object -Unique)) { + try { + $proc = Get-Process -Id $procId -ErrorAction Stop + } catch { + continue + } + Write-Info (" free port {0} (pid={1} {2})" -f $Port, $procId, $proc.ProcessName) + Kill-Tree $procId + } +} + +function Kill-ByCommand([string]$ProcessName, [string]$Pattern) { + Get-CimInstance Win32_Process -Filter ("Name='{0}'" -f $ProcessName) -ErrorAction SilentlyContinue | Where-Object { + $_.CommandLine -and ($_.CommandLine -match $Pattern) + } | ForEach-Object { + Write-Info (" kill {0} pid={1}" -f $ProcessName, $_.ProcessId) + Kill-Tree ([int]$_.ProcessId) + } +} + +# ----- 1) Docker first (do NOT return; native may still be running) ----- +$dockerOk = $false +try { + $null = & docker info 2>&1 + if ($LASTEXITCODE -eq 0) { $dockerOk = $true } +} catch { } + +if ($dockerOk) { + Write-Info "==> docker compose down --remove-orphans" + & docker compose down --remove-orphans 2>$null + # also stop any leftover named containers from older compose projects + $names = @("ai-web-1", "ai-ai-1", "ai-gateway-1", "ai-platform-1", "ai-postgres-1") + foreach ($n in $names) { + & docker rm -f $n 2>$null | Out-Null + } +} + +# ----- 2) Watchdog first (otherwise it revives ai) ----- +Write-Info "==> stopping native processes..." +$watchPidFile = Join-Path $LogDir "watchdog.pid" +if (Test-Path $watchPidFile) { + $wid = Get-Content $watchPidFile -ErrorAction SilentlyContinue + if ($wid) { + Write-Info (" kill watchdog pid={0}" -f $wid) + Kill-Tree ([int]$wid) + } + Remove-Item $watchPidFile -Force -ErrorAction SilentlyContinue +} +Kill-ByCommand "powershell.exe" "watch-services\.ps1" +Kill-ByCommand "pwsh.exe" "watch-services\.ps1" +Kill-ByCommand "powershell.exe" "start-host\.ps1" +Kill-ByCommand "powershell.exe" "Restart-AiOnly|restart_ai_only|hard_restart" + +# ----- 3) Pid files ----- +foreach ($name in @("ai-service.pid", "platform.pid", "gateway.pid", "web.pid", "watchdog.pid")) { + $pf = Join-Path $LogDir $name + if (Test-Path $pf) { + $pidText = Get-Content $pf -ErrorAction SilentlyContinue + if ($pidText) { + Write-Info (" kill {0} -> {1}" -f $name, $pidText) + Kill-Tree ([int]$pidText) + } + Remove-Item $pf -Force -ErrorAction SilentlyContinue + } +} + +# ----- 4) By process name / cmdline ----- +Get-Process -Name "platform", "gateway" -ErrorAction SilentlyContinue | ForEach-Object { + Write-Info (" kill {0} pid={1}" -f $_.ProcessName, $_.Id) + Kill-Tree ([int]$_.Id) +} + +# uvicorn reloader = parent + workers +Kill-ByCommand "python.exe" "uvicorn" +Kill-ByCommand "python.exe" "app:app" +Kill-ByCommand "python.exe" "shot_worker\.py" +Kill-ByCommand "python.exe" "fidelity_loop|FIDELITY_SHOT" +Kill-ByCommand "python.exe" "ai-service" + +# vite / node tooling for this repo +Kill-ByCommand "node.exe" "vite" +Kill-ByCommand "node.exe" "esbuild" +Kill-ByCommand "cmd.exe" "npm.*vite|vite\.js" + +# Playwright Chromium leftovers from fidelity shots +Kill-ByCommand "chrome.exe" "ms-playwright|playwright" +Kill-ByCommand "chromium.exe" "ms-playwright|playwright" +Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { + $_.Name -match '^(chrome|chromium)\.exe$' -and $_.CommandLine -match 'ms-playwright|playwright_chromium' +} | ForEach-Object { + Write-Info (" kill playwright browser pid={0}" -f $_.ProcessId) + Kill-Tree ([int]$_.ProcessId) +} + +# ----- 5) Ports (authoritative) ----- +foreach ($port in 8888, 8001, 8180, 5173) { + Kill-Port $port +} + +Start-Sleep -Seconds 2 + +# second pass for stubborn reloaders +Kill-ByCommand "python.exe" "uvicorn|app:app|shot_worker" +Kill-ByCommand "node.exe" "vite" +foreach ($port in 8888, 8001, 8180, 5173) { + Kill-Port $port +} +Start-Sleep -Seconds 1 + +# ----- 6) Verify (only live processes count) ----- +$still = @() +foreach ($port in 8888, 8001, 8180, 5173) { + Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue | ForEach-Object { + $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue + if ($proc) { + $still += (":{0} pid={1} {2}" -f $port, $_.OwningProcess, $proc.ProcessName) + } + } +} + +if ($still.Count -gt 0) { + Write-Host "WARN still listening after stop:" -ForegroundColor Yellow + $still | ForEach-Object { Write-Host (" {0}" -f $_) } + exit 1 +} + +Write-Info "OK stopped (docker + native; ports 8888/8001/8180/5173 free)" +exit 0 diff --git a/scripts/e2e_flow.py b/scripts/e2e_flow.py new file mode 100644 index 0000000..9f0a1e7 --- /dev/null +++ b/scripts/e2e_flow.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""端到端:AI 生成 → publish → capsule → agent 解密列表 → import + 新能力冒烟""" + +from __future__ import annotations + +import sys +import time +from pathlib import Path + +import httpx + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "agent_sdk")) +from client import AgentClient, decrypt_capsule # noqa: E402 + +PLATFORM = "http://127.0.0.1:8180" +AI = "http://127.0.0.1:8180/ai" + + +def main() -> None: + excel = ROOT / "ai-service" / "sample_inventory.xlsx" + if not excel.exists(): + import subprocess + + subprocess.check_call([sys.executable, str(ROOT / "ai-service" / "make_sample_excel.py")]) + + with httpx.Client(timeout=60.0) as http: + # 网关无 token 应 401 + denied = http.get(f"{PLATFORM}/api/v1/audit/logs") + assert denied.status_code == 401, denied.text + print("gateway jwt deny ok") + + tok = http.post( + f"{PLATFORM}/api/v1/auth/login", + json={"username": "demo", "password": "demo123"}, + ) + tok.raise_for_status() + auth = tok.json() + token, agent_key = auth["access_token"], auth["agent_key"] + assert auth.get("role") in ("owner", "editor", "viewer"), auth + print("login ok", auth.get("username"), "role", auth.get("role"), "tenant", auth.get("tenant_id")) + + headers = {"Authorization": f"Bearer {token}"} + + # 对象存储 + up = http.post( + f"{PLATFORM}/api/v1/storage", + headers=headers, + files={"file": ("note.txt", b"hello-storage", "text/plain")}, + ) + up.raise_for_status() + upj = up.json() + assert upj.get("key") and upj.get("url"), upj + print("storage upload", upj["key"]) + + # 多模态(无 Key 时启发式) + png = ( + b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01" + b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00" + b"\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82" + ) + files = { + "excel": ( + "sample_inventory.xlsx", + excel.read_bytes(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ), + "images": ("ui_list.png", png, "image/png"), + } + data = {"prompt": "根据库存表和截图生成可筛选库存列表", "storage_mode": "schema_per_app"} + gen = http.post(f"{AI}/api/v1/apps/generate", data=data, files=files) + gen.raise_for_status() + gj = gen.json() + draft = gj["draft"] + warns = " ".join(gj.get("warnings") or []) + vision_ok = ( + "看图" in warns + or "截图" in warns + or bool(draft.get("meta", {}).get("source", {}).get("vision_summary")) + ) + assert vision_ok, warns + slug = draft["meta"]["slug"] + resource = draft["apis"]["resources"][0]["path"].lstrip("/") + print("generate", slug, resource, "confidence", gj["confidence"]) + + # 唯一 slug,避免重复发布 + slug = f"{slug}_{int(time.time())}" + draft["meta"]["slug"] = slug + draft["apis"]["base_path"] = f"/api/v1/apps/{slug}" + print("slug", slug) + + pub = http.post( + f"{PLATFORM}/api/v1/apps/{slug}/publish", + headers=headers, + json={"blueprint": draft}, + ) + if pub.status_code >= 400: + print(pub.text) + pub.raise_for_status() + print("published", pub.json()["schema_name"], "memory_mode", pub.json()["memory_mode"]) + + # 审计 + audit = http.get(f"{PLATFORM}/api/v1/audit/logs", headers=headers, params={"page": 1}) + audit.raise_for_status() + actions = [x.get("action") for x in audit.json().get("items") or []] + assert any(a.startswith("publish.") for a in actions) or any( + a == "storage.upload" for a in actions + ), actions + print("audit actions sample", actions[:5]) + + cap = http.get(f"{PLATFORM}/api/v1/apps/{slug}/agent-capsule", headers=headers) + cap.raise_for_status() + capsule = cap.json()["capsule"] + assert capsule.startswith("AJZ1."), capsule + desc = decrypt_capsule(agent_key, capsule) + assert "resources" in desc and desc["app_slug"] == slug + print("capsule decrypted resources:", [r["name"] for r in desc["resources"]]) + assert "/api/v1/apps/" not in capsule + + client = AgentClient(token, agent_key, capsule) + created = client.create( + resource, + { + "sku": "E2E-1", + "product_name": "测试商品", + "warehouse": "华东仓", + "qty": 3, + "unit_price": 9.9, + "status": "在售", + }, + ) + print("agent create id", created.get("id")) + listed = client.list(resource, warehouse="华东仓") + print("agent list total", listed.get("total")) + + csv_body = "sku,product_name,warehouse,qty,unit_price,status\nE2E-2,导入商品,华南仓,5,19.9,在售\n" + try: + from openpyxl import Workbook + import io + + wb = Workbook() + ws = wb.active + ws.append(["sku", "product_name", "warehouse", "qty", "unit_price", "status"]) + ws.append(["E2E-2", "导入商品", "华南仓", 5, 19.9, "在售"]) + buf = io.BytesIO() + wb.save(buf) + files = { + "file": ( + "imp.xlsx", + buf.getvalue(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + } + except Exception: + files = {"file": ("imp.csv", csv_body.encode("utf-8"), "text/csv")} + imp = http.post( + f"{PLATFORM}/api/v1/apps/{slug}/{resource}/import", + headers=headers, + files=files, + ) + imp.raise_for_status() + print("import", imp.json()) + + ag = http.get( + f"{PLATFORM}/api/v1/apps/{slug}/{resource}/aggregate", + headers=headers, + params={"group_by": "warehouse", "sum": "qty"}, + ) + ag.raise_for_status() + print("aggregate", ag.json()) + + # database_per_app 冒烟 + data2 = {"prompt": "独立库库存台账", "storage_mode": "database_per_app"} + gen2 = http.post( + f"{AI}/api/v1/apps/generate", + data=data2, + files={ + "excel": ( + "sample_inventory.xlsx", + excel.read_bytes(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + }, + ) + gen2.raise_for_status() + draft2 = gen2.json()["draft"] + assert draft2["storage"]["mode"] == "database_per_app" + slug2 = f"dbapp_{int(time.time())}" + draft2["meta"]["slug"] = slug2 + draft2["apis"]["base_path"] = f"/api/v1/apps/{slug2}" + pub2 = http.post( + f"{PLATFORM}/api/v1/apps/{slug2}/publish", + headers=headers, + json={"blueprint": draft2}, + ) + if pub2.status_code >= 400: + print("database_per_app publish:", pub2.text) + pub2.raise_for_status() + pj2 = pub2.json() + assert pj2.get("database_name"), pj2 + print("database_per_app", pj2["database_name"], "schema", pj2["schema_name"]) + + # viewer token 不应能 publish + issued = http.post( + f"{PLATFORM}/api/v1/auth/token", + json={ + "tenant_id": auth["tenant_id"], + "user_id": auth["user_id"], + "role": "viewer", + "secret": "dev-only-change-me", + }, + ) + if issued.status_code == 200: + vtok = issued.json()["access_token"] + forbid = http.post( + f"{PLATFORM}/api/v1/apps/{slug}/publish", + headers={"Authorization": f"Bearer {vtok}"}, + json={"blueprint": draft}, + ) + assert forbid.status_code == 403, forbid.text + print("rbac viewer deny publish ok") + else: + print("skip rbac token issue:", issued.status_code, issued.text[:120]) + + print("E2E OK") + + +if __name__ == "__main__": + main() diff --git a/scripts/hard_restart_host.py b/scripts/hard_restart_host.py new file mode 100644 index 0000000..d75be24 --- /dev/null +++ b/scripts/hard_restart_host.py @@ -0,0 +1,102 @@ +"""Hard-reset native stack ports and start host services.""" +from __future__ import annotations + +import os +import subprocess +import sys +import time +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +LOG = ROOT / ".runtime" / "logs" +LOG.mkdir(parents=True, exist_ok=True) +PORTS = (8888, 8001, 8180, 5173) + + +def pids_on_port(port: int) -> list[int]: + out = subprocess.check_output(["netstat", "-ano"], text=True, errors="replace") + pids: set[int] = set() + needle = f":{port}" + for line in out.splitlines(): + if needle not in line or "LISTENING" not in line.upper(): + continue + parts = line.split() + try: + pids.add(int(parts[-1])) + except ValueError: + pass + return sorted(pids) + + +def kill_pid(pid: int) -> None: + if pid <= 0: + return + subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)], check=False, capture_output=True) + + +def main() -> None: + print("==> hard stop ports", PORTS) + for name in ("watchdog.pid", "ai-service.pid", "platform.pid", "gateway.pid", "web.pid"): + pf = LOG / name + if pf.is_file(): + try: + kill_pid(int(pf.read_text(encoding="ascii").strip())) + except ValueError: + pass + pf.unlink(missing_ok=True) + + for port in PORTS: + for pid in pids_on_port(port): + print(f" kill :{port} pid={pid}") + kill_pid(pid) + time.sleep(2) + + # leftover listeners + for port in PORTS: + for pid in pids_on_port(port): + print(f" re-kill :{port} pid={pid}") + kill_pid(pid) + time.sleep(1) + + print("==> start-host.ps1 -NoBrowser") + # Prefer -UseHost path without docker + ps1 = ROOT / "start-host.ps1" + proc = subprocess.run( + [ + "powershell", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + str(ps1), + "-NoBrowser", + ], + cwd=str(ROOT), + check=False, + ) + if proc.returncode != 0: + raise SystemExit(f"start-host failed code={proc.returncode}") + + # verify + for url in ( + "http://127.0.0.1:5173/", + "http://127.0.0.1:8001/health", + "http://127.0.0.1:8180/gateway/health", + ): + try: + with urllib.request.urlopen(url, timeout=3) as r: + print(f"OK {url} -> {r.status}") + except Exception as e: + print(f"FAIL {url} -> {e}") + raise SystemExit(1) + + # only one listener on 8001 + p8001 = pids_on_port(8001) + print("listeners :8001 ->", p8001) + p5173 = pids_on_port(5173) + print("listeners :5173 ->", p5173) + + +if __name__ == "__main__": + main() diff --git a/scripts/lib-aijz-deploy.sh b/scripts/lib-aijz-deploy.sh new file mode 100644 index 0000000..782f182 --- /dev/null +++ b/scripts/lib-aijz-deploy.sh @@ -0,0 +1,402 @@ +# shellcheck shell=bash +# AI 建站 Linux 部署公共函数。由 start/restart/stop/pull-and-restart 在 ROOT 下 source。 + +run_sudo() { + if [ "$(id -u)" -eq 0 ]; then + "$@" + else + sudo "$@" + fi +} + +ensure_runtime_dirs() { + mkdir -p "$ROOT/.runtime/pgdata" "$ROOT/.runtime/uploads" "$ROOT/.runtime/logs" \ + "$ROOT/.runtime/logs/generate" "$ROOT/web/dist" +} + +ensure_env_file() { + if [ ! -f "$ROOT/.env" ]; then + if [ -f "$ROOT/.env.example" ]; then + cp "$ROOT/.env.example" "$ROOT/.env" + echo "已从 .env.example 创建 .env,请按需填写 API Key。" + else + cat >"$ROOT/.env" <<'EOF' +LLM_PROVIDER=deepseek +DEEPSEEK_API_KEY= +MINIMAX_API_KEY= +EOF + echo "已创建默认 .env" + fi + fi + sed -i 's/\r$//' "$ROOT/.env" 2>/dev/null || true + # 供 compose 变量替换与本脚本健康检查使用 + set -a + # shellcheck disable=SC1091 + [ -f "$ROOT/.env" ] && . "$ROOT/.env" + set +a + export AIJZ_WEB_PUBLISH="${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}" + export AIJZ_GATEWAY_PUBLISH="${AIJZ_GATEWAY_PUBLISH:-127.0.0.1:8180}" + export AIJZ_PLATFORM_PUBLISH="${AIJZ_PLATFORM_PUBLISH:-127.0.0.1:8888}" + export AIJZ_AI_PUBLISH="${AIJZ_AI_PUBLISH:-127.0.0.1:8001}" + export AIJZ_PG_PUBLISH="${AIJZ_PG_PUBLISH:-127.0.0.1:5432}" + export AIJZ_ENABLE_HOST_NGINX="${AIJZ_ENABLE_HOST_NGINX:-0}" + export AIJZ_DOMAIN="${AIJZ_DOMAIN:-}" + export AIJZ_PUBLIC_BASE_URL="${AIJZ_PUBLIC_BASE_URL:-}" +} + +# 若 .env 设了 AIJZ_PUBLIC_BASE_URL,同步到 platform.docker.yaml(挂载文件,restart platform 即生效) +apply_public_base_url() { + local base="${AIJZ_PUBLIC_BASE_URL:-}" + local pf="$ROOT/platform/etc/platform.docker.yaml" + [ -n "$base" ] || return 0 + [ -f "$pf" ] || return 0 + base="${base%/}" + local storage_base="${base}/api/v1/storage" + if grep -q '^PublicBaseURL:' "$pf"; then + sed -i "s|^PublicBaseURL:.*|PublicBaseURL: \"${base}\"|" "$pf" + fi + if grep -q '^ PublicBase:' "$pf"; then + sed -i "s|^ PublicBase:.*| PublicBase: \"${storage_base}\"|" "$pf" + fi + echo "已同步 PublicBaseURL -> ${base}(platform/etc/platform.docker.yaml)" +} + +sync_ssl_certs() { + local domain="${AIJZ_DOMAIN:-}" + [ -n "$domain" ] || return 0 + local ssl_dir="/etc/ssl/aijianzhan/$domain" + run_sudo mkdir -p "$ssl_dir" + if [ -f "$ROOT/nginx/$domain.pem" ] && [ -f "$ROOT/nginx/$domain.key" ]; then + run_sudo cp -f "$ROOT/nginx/$domain.pem" "$ssl_dir/fullchain.pem" + run_sudo cp -f "$ROOT/nginx/$domain.key" "$ssl_dir/privkey.pem" + elif [ -f "$ROOT/nginx/fullchain.pem" ] && [ -f "$ROOT/nginx/privkey.pem" ]; then + run_sudo cp -f "$ROOT/nginx/fullchain.pem" "$ROOT/nginx/privkey.pem" "$ssl_dir/" + elif [ -f "$ROOT/nginx/$domain/fullchain.pem" ] && [ -f "$ROOT/nginx/$domain/privkey.pem" ]; then + run_sudo cp -f "$ROOT/nginx/$domain/fullchain.pem" "$ROOT/nginx/$domain/privkey.pem" "$ssl_dir/" + else + echo "提示: 未找到 nginx/ 下证书,宿主机 HTTPS 需手动放入 $ssl_dir" >&2 + return 0 + fi + run_sudo chmod 644 "$ssl_dir/fullchain.pem" 2>/dev/null || true + run_sudo chmod 600 "$ssl_dir/privkey.pem" 2>/dev/null || true + echo "证书已同步 -> $ssl_dir" +} + +host_nginx_online() { + command -v systemctl >/dev/null 2>&1 || return 1 + systemctl is-active nginx >/dev/null 2>&1 && return 0 + systemctl is-active nginx.service >/dev/null 2>&1 && return 0 + return 1 +} + +ensure_host_nginx_started() { + command -v nginx >/dev/null 2>&1 || { + echo "错误: 未安装 nginx,无法启用 AIJZ_ENABLE_HOST_NGINX" >&2 + exit 1 + } + if host_nginx_online; then + return 0 + fi + echo "宿主机 Nginx 未在线,尝试启动..." + run_sudo systemctl start nginx 2>/dev/null || run_sudo systemctl start nginx.service + run_sudo systemctl enable nginx 2>/dev/null || true + host_nginx_online || { + echo "错误: 无法启动宿主机 nginx" >&2 + exit 1 + } +} + +install_host_nginx_site_conf() { + local domain="${AIJZ_DOMAIN:-}" + [ -n "$domain" ] || return 0 + [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ] || return 0 + local tpl="$ROOT/nginx/aijz.host.conf" + local out="/etc/nginx/conf.d/aijz_${domain}.conf" + local web_port + web_port="$(publish_host_port "${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}")" + [ -f "$tpl" ] || { + echo "未找到 $tpl,跳过宿主机 Nginx 配置" >&2 + return 0 + } + mkdir -p "$ROOT/verify-root" + sync_ssl_certs + sed -e "s|__DOMAIN__|${domain}|g" \ + -e "s|__WEB_PORT__|${web_port}|g" \ + -e "s|__VERIFY_ROOT__|${ROOT}/verify-root|g" \ + "$tpl" | run_sudo tee "$out" >/dev/null + if ! run_sudo nginx -t 2>/dev/null; then + echo "错误: nginx -t 失败,请检查 $out" >&2 + exit 1 + fi + if host_nginx_online; then + run_sudo systemctl reload nginx 2>/dev/null && echo "宿主机 Nginx 已重载($out)" + else + ensure_host_nginx_started + fi +} + +reload_web_nginx() { + local cid + cid="$(compose_cmd ps -q web 2>/dev/null | head -1)" + if [ -z "$cid" ]; then + echo "web 容器未运行,跳过 reload" >&2 + return 1 + fi + run_sudo docker exec "$cid" nginx -t >/dev/null 2>&1 || { + echo "错误: 容器内 nginx -t 失败" >&2 + return 1 + } + run_sudo docker exec "$cid" nginx -s reload + echo "web 容器 nginx 已 reload(web/nginx.conf)" +} + +restart_service() { + local svc="$1" + compose_cmd restart "$svc" + echo "已 restart $svc" +} + +reload_host_nginx() { + [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ] || { + echo "AIJZ_ENABLE_HOST_NGINX 未启用,跳过宿主机 Nginx" >&2 + return 0 + } + install_host_nginx_site_conf +} + +reload_all_config() { + ensure_env_file + apply_public_base_url + reload_host_nginx || true + reload_web_nginx || true + restart_service platform + restart_service gateway + compose_cmd up -d --force-recreate ai >/dev/null 2>&1 || compose_cmd restart ai + echo "配置重载完成(未 rebuild 镜像、未重建 web/dist)" +} + +ensure_docker() { + if command -v docker >/dev/null 2>&1 && run_sudo docker info >/dev/null 2>&1; then + echo "Docker 已就绪." + return 0 + fi + if command -v docker >/dev/null 2>&1; then + echo "Docker 守护进程未连接,尝试启动..." + run_sudo systemctl start docker 2>/dev/null || run_sudo systemctl start podman 2>/dev/null || true + if run_sudo docker info >/dev/null 2>&1; then + echo "Docker 已就绪." + return 0 + fi + fi + echo "错误: Docker 不可用。请先安装并启动 docker 后再执行。" >&2 + exit 1 +} + +resolve_compose_cmd() { + if run_sudo docker compose version >/dev/null 2>&1; then + echo "docker compose" + return + fi + if command -v docker-compose >/dev/null 2>&1; then + echo "docker-compose" + return + fi + if [ -x /usr/local/bin/docker-compose ]; then + echo "/usr/local/bin/docker-compose" + return + fi + echo "" +} + +COMPOSE_CMD="" +compose_cmd() { + if [ -z "$COMPOSE_CMD" ]; then + COMPOSE_CMD="$(resolve_compose_cmd)" + fi + if [ -z "$COMPOSE_CMD" ]; then + echo "错误: 找不到 docker compose / docker-compose" >&2 + exit 1 + fi + # shellcheck disable=SC2086 + run_sudo env \ + DOCKER_BASE_REGISTRY="${DOCKER_BASE_REGISTRY}" \ + GOPROXY="${GOPROXY}" \ + AIJZ_WEB_PUBLISH="${AIJZ_WEB_PUBLISH}" \ + AIJZ_GATEWAY_PUBLISH="${AIJZ_GATEWAY_PUBLISH}" \ + AIJZ_PLATFORM_PUBLISH="${AIJZ_PLATFORM_PUBLISH}" \ + AIJZ_AI_PUBLISH="${AIJZ_AI_PUBLISH}" \ + AIJZ_PG_PUBLISH="${AIJZ_PG_PUBLISH}" \ + $COMPOSE_CMD "$@" +} + +export_defaults() { + export DOCKER_BASE_REGISTRY="${DOCKER_BASE_REGISTRY:-docker.m.daocloud.io/library}" + export GOPROXY="${GOPROXY:-https://goproxy.cn,direct}" + export REGISTRY_MIRROR="${REGISTRY_MIRROR:-docker.m.daocloud.io/library/}" +} + +# 从 "127.0.0.1:5173" 或 "5173" 取出宿主机端口号 +publish_host_port() { + local pub="$1" + echo "$pub" | awk -F: '{print $NF}' +} + +port_in_use() { + local port="$1" + if command -v ss >/dev/null 2>&1; then + run_sudo ss -tlnH "sport = :$port" 2>/dev/null | grep -q . && return 0 + ss -tlnH "sport = :$port" 2>/dev/null | grep -q . && return 0 + return 1 + fi + if command -v lsof >/dev/null 2>&1; then + lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 && return 0 + return 1 + fi + return 1 +} + +# 检查 AI 建站将占用的宿主机端口;若已被本项目容器占用则放行 +check_aijz_ports() { + local pairs=( + "web:$AIJZ_WEB_PUBLISH" + "gateway:$AIJZ_GATEWAY_PUBLISH" + "platform:$AIJZ_PLATFORM_PUBLISH" + "ai:$AIJZ_AI_PUBLISH" + "postgres:$AIJZ_PG_PUBLISH" + ) + echo "端口规划核对(相对 yh_web):AI建站 5173/8180/8888/8001/5432;宇恒 8088/9080/9081 + 宿主机 80/443。" + local name pub port conflict=0 + local self_running=0 + if compose_cmd ps -q 2>/dev/null | grep -q .; then + self_running=1 + fi + for item in "${pairs[@]}"; do + name="${item%%:*}" + pub="${item#*:}" + port="$(publish_host_port "$pub")" + [ -z "$port" ] && continue + if port_in_use "$port"; then + if [ "$self_running" -eq 1 ]; then + echo " 提示: 端口 $port($name)已占用,疑为本栈,继续。" + else + echo " 错误: 宿主机端口 $port 已被占用(服务 $name ← $pub)。" >&2 + echo " 请改 .env 中对应 AIJZ_*_PUBLISH,或停掉占用进程。" >&2 + conflict=1 + fi + else + echo " OK $name → $pub" + fi + done + [ "$conflict" -eq 0 ] || exit 1 +} + +# 构建 web/dist:优先本机 npm;否则用 Docker node 镜像 +build_web_dist() { + echo "构建 web dist ..." + if command -v npm >/dev/null 2>&1; then + ( + cd "$ROOT/web" + if [ -f package-lock.json ]; then + npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps + else + npm install --legacy-peer-deps + fi + npm run build + ) + else + echo "本机无 npm,使用 Docker node 构建..." + run_sudo docker run --rm \ + -v "$ROOT/web:/app" \ + -v "$ROOT/web/dist:/app/dist" \ + -w /app \ + "${REGISTRY_MIRROR}node:20-alpine" \ + sh -c "(npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps) && npm run build" + fi + if [ ! -f "$ROOT/web/dist/index.html" ]; then + echo "错误: web/dist/index.html 不存在,构建失败。" >&2 + exit 1 + fi + echo "web/dist 就绪." +} + +stack_up() { + local rebuild="${1:-0}" + export_defaults + ensure_runtime_dirs + ensure_env_file + apply_public_base_url + check_aijz_ports + build_web_dist + if [ "$rebuild" = "1" ]; then + compose_cmd up -d --build --force-recreate + else + if ! compose_cmd up -d; then + echo "compose up 失败,尝试 --build ..." + compose_cmd up -d --build + fi + fi + install_host_nginx_site_conf || true +} + +stack_down() { + compose_cmd down --remove-orphans 2>/dev/null || true +} + +healthcheck() { + local ok=0 i + local gw_port web_port + gw_port="$(publish_host_port "${AIJZ_GATEWAY_PUBLISH:-127.0.0.1:8180}")" + web_port="$(publish_host_port "${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}")" + echo "等待 gateway(:$gw_port) / web(:$web_port) ..." + for i in $(seq 1 60); do + if curl -fsS --max-time 2 "http://127.0.0.1:${gw_port}/gateway/health" >/dev/null 2>&1 \ + && curl -fsS --max-time 2 "http://127.0.0.1:${web_port}/" >/dev/null 2>&1; then + ok=1 + break + fi + sleep 2 + done + if [ "$ok" -eq 1 ]; then + echo "健康检查通过." + else + echo "警告: 健康检查超时,请查看: docker compose -f $ROOT/docker-compose.yml logs" >&2 + fi + compose_cmd exec -T postgres psql -U platform -d platform -c 'ALTER USER platform CREATEDB;' >/dev/null 2>&1 || true +} + +print_endpoints() { + local pub_line="" + if [ -n "${AIJZ_DOMAIN:-}" ] && [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ]; then + pub_line=" Public https://${AIJZ_DOMAIN}" + fi + cat </dev/null 2>&1; then + echo "错误: 当前不是 git 仓库,跳过拉取。" >&2 + return 1 + fi + git fetch origin --progress + if [ "${FORCE_GIT_RESET:-1}" = "1" ]; then + git reset --hard "origin/$branch" + else + git checkout "$branch" 2>/dev/null || true + git merge --ff-only "origin/$branch" + fi + echo "当前: $(git rev-parse --short HEAD) $(git log -1 --pretty=%s)" +} diff --git a/scripts/linux/common.sh b/scripts/linux/common.sh new file mode 100644 index 0000000..a1bda03 --- /dev/null +++ b/scripts/linux/common.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# 宇信达智建 — Linux 公共函数(宿主机物理目录模式,不依赖 Docker) +# shellcheck disable=SC2034 + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +RUNTIME="$ROOT/.runtime" +LOG_DIR="$RUNTIME/logs" +PID_DIR="$RUNTIME/pids" +VENV_DIR="$RUNTIME/venv" +PGDATA="$RUNTIME/postgres" +UPLOAD_DIR="$ROOT/platform/data/uploads" +PG_PORT="${AJZ_PG_PORT:-5432}" +PG_USER="${AJZ_PG_USER:-platform}" +PG_PASS="${AJZ_PG_PASS:-platform}" +PG_DB="${AJZ_PG_DB:-platform}" + +mkdir -p "$LOG_DIR" "$PID_DIR" "$UPLOAD_DIR" + +c_info() { printf '\033[36m==> %s\033[0m\n' "$*"; } +c_ok() { printf '\033[32m OK %s\033[0m\n' "$*"; } +c_warn() { printf '\033[33m !! %s\033[0m\n' "$*"; } +c_err() { printf '\033[31m ERR %s\033[0m\n' "$*" >&2; } + +have_cmd() { command -v "$1" >/dev/null 2>&1; } + +docker_ready() { + have_cmd docker || return 1 + docker info >/dev/null 2>&1 +} + +port_listen() { + local port="$1" + if have_cmd ss; then + ss -ltn "( sport = :$port )" 2>/dev/null | grep -q ":$port" + elif have_cmd netstat; then + netstat -ltn 2>/dev/null | grep -q "[.:]$port " + else + (echo >/dev/tcp/127.0.0.1/"$port") >/dev/null 2>&1 + fi +} + +wait_port() { + local port="$1" name="$2" sec="${3:-40}" + local i=0 + while (( i < sec * 2 )); do + if port_listen "$port"; then + c_ok "$name 端口 $port 已监听" + return 0 + fi + sleep 0.5 + ((i++)) || true + done + c_warn "$name 端口 $port 未在 ${sec}s 内就绪" + return 1 +} + +wait_http() { + local url="$1" name="$2" sec="${3:-40}" + local i=0 + while (( i < sec * 2 )); do + if curl -fsS --max-time 2 "$url" >/dev/null 2>&1; then + c_ok "$name 就绪 $url" + return 0 + fi + sleep 0.5 + ((i++)) || true + done + c_warn "$name 未在 ${sec}s 内就绪: $url" + return 1 +} + +detect_os() { + if [[ -f /etc/os-release ]]; then + # shellcheck source=/dev/null + . /etc/os-release + echo "${ID:-unknown}" + else + echo "unknown" + fi +} + +sudo_run() { + if [[ "$(id -u)" -eq 0 ]]; then + "$@" + elif have_cmd sudo; then + sudo "$@" + else + c_err "需要 root 或 sudo 才能安装系统包: $*" + return 1 + fi +} + +export ROOT RUNTIME LOG_DIR PID_DIR VENV_DIR PGDATA UPLOAD_DIR +export PG_PORT PG_USER PG_PASS PG_DB diff --git a/scripts/linux/setup-host.sh b/scripts/linux/setup-host.sh new file mode 100644 index 0000000..fa254b7 --- /dev/null +++ b/scripts/linux/setup-host.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +# 在宿主机物理目录自动配置环境(无 Docker) +# 数据落盘:$ROOT/.runtime/{postgres,venv,logs,pids} 与 platform/data/uploads +# shellcheck disable=SC1091 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=common.sh +source "$SCRIPT_DIR/common.sh" + +c_info "根目录(物理): $ROOT" +c_info "运行时目录: $RUNTIME" +c_info "上传目录: $UPLOAD_DIR" + +need_pkgs=() +have_cmd curl || need_pkgs+=(curl) +have_cmd git || need_pkgs+=(git) +have_cmd go || need_pkgs+=(golang-go) +have_cmd python3 || need_pkgs+=(python3) +have_cmd pip3 || need_pkgs+=(python3-pip) +python3 -c "import venv" 2>/dev/null || need_pkgs+=(python3-venv) +have_cmd node || need_pkgs+=(nodejs) +have_cmd npm || need_pkgs+=(npm) +have_cmd psql || need_pkgs+=(postgresql-client) +have_cmd initdb || have_cmd pg_ctl || need_pkgs+=(postgresql) + +install_pkgs() { + local os + os="$(detect_os)" + c_info "安装系统包 (OS=$os): ${need_pkgs[*]}" + case "$os" in + ubuntu|debian|linuxmint|pop) + sudo_run apt-get update -y + local pkgs=() + for p in "${need_pkgs[@]}"; do + case "$p" in + golang-go) pkgs+=(golang-go) ;; + python3) pkgs+=(python3 python3-venv python3-dev) ;; + python3-pip) pkgs+=(python3-pip) ;; + python3-venv) pkgs+=(python3-venv) ;; + nodejs) pkgs+=(nodejs) ;; + npm) pkgs+=(npm) ;; + postgresql) pkgs+=(postgresql postgresql-contrib) ;; + postgresql-client) pkgs+=(postgresql-client) ;; + *) pkgs+=("$p") ;; + esac + done + sudo_run apt-get install -y "${pkgs[@]}" + ;; + fedora|rhel|centos|rocky|almalinux) + local pkgs=() + for p in "${need_pkgs[@]}"; do + case "$p" in + golang-go) pkgs+=(golang) ;; + python3-pip) pkgs+=(python3-pip) ;; + python3-venv) pkgs+=(python3) ;; + postgresql) pkgs+=(postgresql-server postgresql) ;; + postgresql-client) pkgs+=(postgresql) ;; + *) pkgs+=("$p") ;; + esac + done + if have_cmd dnf; then sudo_run dnf install -y "${pkgs[@]}" + else sudo_run yum install -y "${pkgs[@]}"; fi + ;; + arch|manjaro) + local pkgs=() + for p in "${need_pkgs[@]}"; do + case "$p" in + golang-go) pkgs+=(go) ;; + python3) pkgs+=(python) ;; + python3-pip) pkgs+=(python-pip) ;; + python3-venv) ;; + postgresql-client) pkgs+=(postgresql) ;; + *) pkgs+=("$p") ;; + esac + done + sudo_run pacman -Sy --noconfirm "${pkgs[@]}" + ;; + *) + c_err "未识别发行版 $os,请手动安装: go python3 node npm postgresql curl" + return 1 + ;; + esac +} + +if ((${#need_pkgs[@]} > 0)); then + install_pkgs +else + c_ok "系统依赖已满足" +fi + +missing=() +for c in go python3 curl; do have_cmd "$c" || missing+=("$c"); done +have_cmd node || have_cmd nodejs || missing+=("node") +have_cmd npm || missing+=("npm") +if ((${#missing[@]} > 0)); then + c_err "仍缺少命令: ${missing[*]}" + exit 1 +fi +c_ok "$(go version)" + +c_info "配置 Python venv → $VENV_DIR" +if [[ ! -d "$VENV_DIR" ]]; then + python3 -m venv "$VENV_DIR" +fi +# shellcheck source=/dev/null +source "$VENV_DIR/bin/activate" +pip install -U pip wheel >/dev/null +pip install -r "$ROOT/ai-service/requirements.txt" +pip install -r "$ROOT/agent_sdk/requirements.txt" +c_ok "venv 依赖已安装" + +c_info "安装前端依赖 → $ROOT/web/node_modules" +if [[ ! -d "$ROOT/web/node_modules" ]]; then + (cd "$ROOT/web" && npm install) +else + c_ok "web/node_modules 已存在" +fi + +find_pg_bin() { + local name="$1" + if have_cmd "$name"; then command -v "$name"; return; fi + local d + for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do + if [[ -x "$d/$name" ]]; then echo "$d/$name"; return; fi + done + return 1 +} + +INITDB="$(find_pg_bin initdb || true)" +PG_CTL="$(find_pg_bin pg_ctl || true)" +PSQL="$(find_pg_bin psql || true)" + +ensure_local_postgres() { + if [[ -z "$INITDB" || -z "$PG_CTL" || -z "$PSQL" ]]; then + c_warn "未找到 initdb/pg_ctl/psql,跳过本地库初始化(请自备 Postgres)" + return 0 + fi + + # 已有可连库则复用 + if PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1" >/dev/null 2>&1; then + c_ok "复用已有 Postgres $PG_USER@$PG_DB:$PG_PORT" + PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \ + -c "ALTER USER $PG_USER CREATEDB;" >/dev/null 2>&1 || true + return 0 + fi + + mkdir -p "$PGDATA" "$RUNTIME/pg_sockets" + if [[ ! -f "$PGDATA/PG_VERSION" ]]; then + c_info "initdb → $PGDATA (物理目录)" + "$INITDB" -D "$PGDATA" --auth=trust -U postgres + { + echo "" + echo "listen_addresses = '127.0.0.1'" + echo "port = $PG_PORT" + echo "unix_socket_directories = '$RUNTIME/pg_sockets'" + } >> "$PGDATA/postgresql.conf" + fi + + if ! "$PG_CTL" -D "$PGDATA" status >/dev/null 2>&1; then + c_info "启动本地 Postgres" + "$PG_CTL" -D "$PGDATA" -l "$LOG_DIR/postgres.log" -o "-k $RUNTIME/pg_sockets" start + sleep 1 + fi + + local sock="$RUNTIME/pg_sockets" + "$PSQL" -h "$sock" -U postgres -d postgres -v ON_ERROR_STOP=1 </dev/null 2>&1; then + # 补充密码认证 + cat > "$PGDATA/pg_hba.conf" </dev/null + c_ok "Postgres 就绪 (物理 data=$PGDATA)" +} + +ensure_local_postgres + +c_info "编译 platform / gateway" +(cd "$ROOT/platform" && go build -o platform .) +(cd "$ROOT/gateway" && go build -o gateway .) +c_ok "二进制已生成" + +if [[ ! -f "$ROOT/ai-service/sample_inventory.xlsx" ]]; then + (cd "$ROOT/ai-service" && "$VENV_DIR/bin/python" make_sample_excel.py) || true +fi + +date -Iseconds > "$RUNTIME/setup.ok" +cat > "$RUNTIME/env.sh" </dev/null; then + c_warn "$name 已在运行 pid=$(cat "$pidfile")" + return 0 + fi + c_info "启动 $name" + nohup "$@" >>"$logfile" 2>&1 & + echo $! >"$pidfile" + c_ok "$name pid=$(cat "$pidfile") 日志 $logfile" +} + +ensure_postgres_running() { + local pg_ctl="" + pg_ctl="$(command -v pg_ctl 2>/dev/null || true)" + if [[ -z "$pg_ctl" ]]; then + for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do + [[ -x "$d/pg_ctl" ]] && pg_ctl="$d/pg_ctl" && break + done + fi + if [[ -n "$pg_ctl" && -f "$PGDATA/PG_VERSION" ]]; then + if ! "$pg_ctl" -D "$PGDATA" status >/dev/null 2>&1; then + c_info "启动物理目录 Postgres → $PGDATA" + mkdir -p "$RUNTIME/pg_sockets" + "$pg_ctl" -D "$PGDATA" -l "$LOG_DIR/postgres.log" -o "-k $RUNTIME/pg_sockets" start + sleep 1 + fi + fi +} + +c_warn "宿主机模式:数据落在 $RUNTIME" +if [[ ! -f "$RUNTIME/setup.ok" || "$REBUILD" -eq 1 ]]; then + bash "$SCRIPT_DIR/setup-host.sh" +fi +# shellcheck source=/dev/null +[[ -f "$RUNTIME/env.sh" ]] && source "$RUNTIME/env.sh" + +ensure_postgres_running + +if [[ "$REBUILD" -eq 1 || ! -x "$ROOT/platform/platform" ]]; then + (cd "$ROOT/platform" && go build -o platform .) +fi +if [[ "$REBUILD" -eq 1 || ! -x "$ROOT/gateway/gateway" ]]; then + (cd "$ROOT/gateway" && go build -o gateway .) +fi + +PY="$VENV_DIR/bin/python" +[[ -x "$PY" ]] || PY="$(command -v python3)" + +start_bg platform "$LOG_DIR/platform.log" \ + bash -c "cd '$ROOT/platform' && exec ./platform -f etc/platform.yaml" +start_bg ai "$LOG_DIR/ai.log" \ + bash -c "cd '$ROOT/ai-service' && exec '$PY' -m uvicorn app:app --host 127.0.0.1 --port 8001" +start_bg gateway "$LOG_DIR/gateway.log" \ + bash -c "cd '$ROOT/gateway' && exec ./gateway -f etc/gateway.yaml" +start_bg web "$LOG_DIR/web.log" \ + bash -c "cd '$ROOT/web' && exec npm run dev -- --host 127.0.0.1 --port 5173" + +wait_port 8888 platform 45 || true +wait_http "http://127.0.0.1:8001/health" ai-service 45 || true +wait_http "http://127.0.0.1:8180/gateway/health" gateway 45 || true +wait_port 5173 web 60 || true + +cat </dev/null 2>&1 || true +fi diff --git a/scripts/linux/stop-host.sh b/scripts/linux/stop-host.sh new file mode 100644 index 0000000..6f7db98 --- /dev/null +++ b/scripts/linux/stop-host.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# 停止宿主机模式进程 +# shellcheck disable=SC1091 +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +# shellcheck source=common.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh" + +c_info "停止宿主机模式服务..." + +stop_pidfile() { + local name="$1" + local f="$PID_DIR/$name.pid" + if [[ -f "$f" ]]; then + local pid + pid="$(cat "$f")" + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + sleep 0.3 + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$f" + fi +} + +for n in web gateway ai platform; do + stop_pidfile "$n" +done + +for port in 5173 8180 8001 8888; do + if have_cmd fuser; then + fuser -k "${port}/tcp" >/dev/null 2>&1 || true + elif have_cmd lsof; then + pids="$(lsof -t -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)" + [[ -n "$pids" ]] && kill $pids 2>/dev/null || true + fi +done + +if [[ "${1:-}" == "--with-db" ]]; then + pg_ctl_bin="$(command -v pg_ctl 2>/dev/null || true)" + if [[ -z "$pg_ctl_bin" ]]; then + for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do + [[ -x "$d/pg_ctl" ]] && pg_ctl_bin="$d/pg_ctl" && break + done + fi + if [[ -n "$pg_ctl_bin" && -f "$PGDATA/PG_VERSION" ]]; then + "$pg_ctl_bin" -D "$PGDATA" stop -m fast || true + fi +fi + +c_ok "已停止。数据仍在: $RUNTIME" diff --git a/scripts/probe_dashscope.py b/scripts/probe_dashscope.py new file mode 100644 index 0000000..d981235 --- /dev/null +++ b/scripts/probe_dashscope.py @@ -0,0 +1,187 @@ +"""Probe DashScope / 通义千问 connectivity and latency.""" +from __future__ import annotations + +import base64 +import os +import socket +import ssl +import sys +import time +from pathlib import Path + +import httpx + +ROOT = Path(__file__).resolve().parents[1] + + +def load_env() -> None: + for envp in (ROOT / ".env", ROOT / "ai-service" / ".env"): + if not envp.exists(): + continue + for line in envp.read_text(encoding="utf-8", errors="replace").splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + k, v = k.strip(), v.strip().strip('"').strip("'") + if k and k not in os.environ: + os.environ[k] = v + + +def post(base: str, key: str, payload: dict, timeout: float, label: str) -> tuple[float, int | None]: + t0 = time.time() + try: + with httpx.Client(timeout=timeout) as c: + r = c.post( + f"{base}/chat/completions", + headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, + json=payload, + ) + dt = time.time() - t0 + print(f"[{label}] status={r.status_code} time={dt:.1f}s bytes_in={len(r.content)}") + if r.status_code >= 400: + print(f"[{label}] body {r.text[:500].replace(chr(10), ' ')}") + else: + j = r.json() + txt = j["choices"][0]["message"]["content"] + if isinstance(txt, list): + txt = "".join((x.get("text") if isinstance(x, dict) else str(x)) for x in txt) + print(f"[{label}] ok content_len={len(str(txt))} preview={str(txt)[:100]!r}") + return dt, r.status_code + except Exception as e: + dt = time.time() - t0 + print(f"[{label}] FAIL after {dt:.1f}s: {type(e).__name__}: {e}") + return dt, None + + +def main() -> int: + load_env() + key = (os.getenv("DASHSCOPE_API_KEY") or "").strip() + base = (os.getenv("DASHSCOPE_BASE_URL") or "https://dashscope.aliyuncs.com/compatible-mode/v1").rstrip("/") + model = (os.getenv("VISION_MODEL") or "qwen3.6-plus").strip() + print(f"base={base}") + print(f"model={model}") + print(f"key_set={bool(key)} key_len={len(key)}") + if not key: + print("NO DASHSCOPE_API_KEY") + return 1 + + host = "dashscope.aliyuncs.com" + t0 = time.time() + try: + ips = socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM) + print(f"DNS ok {time.time() - t0:.2f}s -> {sorted({x[4][0] for x in ips})[:4]}") + except Exception as e: + print(f"DNS FAIL {type(e).__name__}: {e}") + + t0 = time.time() + try: + ctx = ssl.create_default_context() + with socket.create_connection((host, 443), timeout=10) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as ssock: + print(f"TLS ok {time.time() - t0:.2f}s protocol={ssock.version()}") + except Exception as e: + print(f"TLS FAIL {type(e).__name__}: {e}") + + # 1) text-only + post( + base, + key, + { + "model": model, + "temperature": 0, + "messages": [{"role": "user", "content": "只回复:通义可达"}], + "max_tokens": 32, + }, + 30.0, + "text-only", + ) + + # 2) tiny image + png_b64 = ( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" + ) + post( + base, + key, + { + "model": model, + "temperature": 0, + "messages": [ + { + "role": "user", + "content": [ + {"type": "text", "text": "这张图是什么颜色?一句话。"}, + {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{png_b64}"}}, + ], + } + ], + "max_tokens": 64, + }, + 60.0, + "tiny-image", + ) + + # 3) real refs + refs = list((ROOT / "test" / "refs").glob("*.png"))[:2] + if not refs: + refs = list((ROOT / "test").rglob("board_*.png"))[:2] + print("refs", [f"{p.name}:{p.stat().st_size}" for p in refs]) + if refs: + content: list[dict] = [{"type": "text", "text": "用一句话描述这些界面截图里最显眼的标题。"}] + total = 0 + for p in refs: + raw = p.read_bytes() + total += len(raw) + b64 = base64.b64encode(raw).decode("ascii") + content.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}) + print(f"ref payload images_bytes={total} b64_approx={total * 4 // 3}") + post( + base, + key, + { + "model": model, + "temperature": 0, + "messages": [{"role": "user", "content": content}], + "max_tokens": 128, + }, + 150.0, + "real-refs", + ) + + # long prompt + 2 images like fidelity scoring (heavier) + long_text = ("请逐区对照并打分。" + "区" * 800)[:2000] + content2 = [{"type": "text", "text": long_text}] + content[1:] + post( + base, + key, + { + "model": model, + "temperature": 0, + "messages": [{"role": "user", "content": content2}], + "max_tokens": 512, + }, + 150.0, + "fidelity-like", + ) + + # 4) alternate model text + if model != "qwen-vl-plus": + post( + base, + key, + { + "model": "qwen-vl-plus", + "temperature": 0, + "messages": [{"role": "user", "content": "只回复:vl-plus可达"}], + "max_tokens": 32, + }, + 30.0, + "alt-qwen-vl-plus-text", + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/restart_ai_only.py b/scripts/restart_ai_only.py new file mode 100644 index 0000000..9f626b6 --- /dev/null +++ b/scripts/restart_ai_only.py @@ -0,0 +1,120 @@ +"""Kill port 8001 holders, then start ai-service.""" +from __future__ import annotations + +import os +import subprocess +import sys +import time +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +LOG = ROOT / ".runtime" / "logs" +LOG.mkdir(parents=True, exist_ok=True) + + +def pids_on_port(port: int) -> list[int]: + out = subprocess.check_output(["netstat", "-ano"], text=True, errors="replace") + pids: set[int] = set() + needle = f":{port}" + for line in out.splitlines(): + if needle not in line: + continue + if "LISTENING" not in line.upper(): + continue + parts = line.split() + try: + pids.add(int(parts[-1])) + except ValueError: + pass + return sorted(pids) + + +def kill_pid(pid: int) -> None: + if pid <= 0: + return + subprocess.run(["taskkill", "/F", "/T", "/PID", str(pid)], check=False, capture_output=True) + + +def main() -> None: + print("stopping old ai...") + # stop watchdog first if present + wd = LOG / "watchdog.pid" + if wd.is_file(): + try: + kill_pid(int(wd.read_text(encoding="ascii").strip())) + except ValueError: + pass + wd.unlink(missing_ok=True) + + for name in ("ai-service.pid",): + pf = LOG / name + if pf.is_file(): + try: + kill_pid(int(pf.read_text(encoding="ascii").strip())) + except ValueError: + pass + + for pid in pids_on_port(8001): + print(f" free 8001 pid={pid}") + kill_pid(pid) + time.sleep(2) + + env = os.environ.copy() + env["WEB_BASE"] = "http://127.0.0.1:5173" + env["PLATFORM_BASE"] = "http://127.0.0.1:8888" + env["GATEWAY_BASE"] = "http://127.0.0.1:8180" + env["GENERATE_LOG_DIR"] = str(LOG / "generate") + env["FIDELITY_SHOT_DIR"] = str(ROOT / ".runtime" / "fidelity_shots") + + stamp = time.strftime("%H%M%S") + out_path = LOG / f"ai-service.{stamp}.out.log" + err_path = LOG / f"ai-service.{stamp}.err.log" + out_f = out_path.open("w", encoding="utf-8") + err_f = err_path.open("w", encoding="utf-8") + # also point stable names via copies later; use unique to avoid locks + (LOG / "ai-service.out.log.path").write_text(str(out_path), encoding="utf-8") + + flags = getattr(subprocess, "CREATE_NO_WINDOW", 0) + p = subprocess.Popen( + [ + sys.executable, + "-m", + "uvicorn", + "app:app", + "--host", + "127.0.0.1", + "--port", + "8001", + "--reload", + ], + cwd=str(ROOT / "ai-service"), + env=env, + stdout=out_f, + stderr=err_f, + creationflags=flags, + ) + (LOG / "ai-service.pid").write_text(str(p.pid), encoding="ascii") + print(f"started ai-service pid={p.pid}") + print(f" logs: {out_path.name} / {err_path.name}") + + for i in range(45): + time.sleep(1) + try: + with urllib.request.urlopen("http://127.0.0.1:8001/docs", timeout=2) as r: + if r.status == 200: + print("OK AI http://127.0.0.1:8001") + return + except Exception as e: + if i % 5 == 4: + print(f" waiting... ({type(e).__name__})") + # show err log snippet + try: + print(err_path.read_text(encoding="utf-8", errors="replace")[-400:]) + except Exception: + pass + raise SystemExit("AI failed to become ready on :8001") + + +if __name__ == "__main__": + main() diff --git a/start-host.ps1 b/start-host.ps1 new file mode 100644 index 0000000..940c163 --- /dev/null +++ b/start-host.ps1 @@ -0,0 +1,199 @@ +# native process mode (called by start.ps1 when Docker is unavailable) +param( + [switch]$Rebuild, + [switch]$NoBrowser, + [switch]$ShowConsole # show service windows (debug); default: hidden +) + +$ErrorActionPreference = "Stop" +$Root = $PSScriptRoot +Set-Location -LiteralPath $Root +$LogDir = Join-Path $Root ".runtime\logs" +New-Item -ItemType Directory -Force -Path $LogDir | Out-Null + +# Always wipe previous native/docker leftovers before binding ports +Write-Host "==> Pre-clean before native start" -ForegroundColor DarkGray +& (Join-Path $Root "scripts\Stop-Stack.ps1") -Quiet +Start-Sleep -Seconds 1 + +Write-Host "==> Native mode: need Go / Python / Node / PostgreSQL" -ForegroundColor Yellow +if (-not $ShowConsole) { + Write-Host " services run hidden; logs -> .runtime\logs\" -ForegroundColor DarkGray +} + +function Clear-Port([int]$port) { + $conns = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue + foreach ($c in $conns) { + $procId = $c.OwningProcess + if ($procId -gt 0) { + Write-Host " free port $port (pid=$procId)" + & taskkill.exe /F /T /PID $procId 2>$null | Out-Null + Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue + } + } +} + +Write-Host "==> Clearing ports 8888/8001/8180/5173" +foreach ($p in 8888, 8001, 8180, 5173) { Clear-Port $p } +Start-Sleep -Seconds 1 + +function Reset-LogFile([string]$path) { + try { + if (Test-Path -LiteralPath $path) { + # 句柄未释放时 Set-Content 会失败:先挪走旧文件 + $prev = "$path.prev" + Remove-Item -LiteralPath $prev -Force -ErrorAction SilentlyContinue + Move-Item -LiteralPath $path -Destination $prev -Force -ErrorAction Stop + } + Set-Content -LiteralPath $path -Value "" -Encoding UTF8 + return $path + } catch { + $alt = ($path -replace '\.log$', ".$(Get-Date -Format 'HHmmss').log") + Set-Content -LiteralPath $alt -Value "" -Encoding UTF8 + return $alt + } +} + +function Start-Svc($name, $workDir, $filePath, $argumentList) { + $outLog = Reset-LogFile (Join-Path $LogDir "$name.out.log") + $errLog = Reset-LogFile (Join-Path $LogDir "$name.err.log") + $pidFile = Join-Path $LogDir "$name.pid" + + if ($ShowConsole) { + $argStr = "" + if ($argumentList) { + $parts = @() + foreach ($a in $argumentList) { + if ($a -match '\s') { $parts += ('"' + $a + '"') } else { $parts += $a } + } + $argStr = $parts -join ' ' + } + $cmd = "Set-Location -LiteralPath '$workDir'; Write-Host '[$name]' -ForegroundColor Cyan; & '$filePath' $argStr" + $p = Start-Process -FilePath "powershell.exe" -ArgumentList @( + "-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $cmd + ) -WorkingDirectory $workDir -PassThru + } else { + $p = Start-Process -FilePath $filePath ` + -ArgumentList $argumentList ` + -WorkingDirectory $workDir ` + -WindowStyle Hidden ` + -RedirectStandardOutput $outLog ` + -RedirectStandardError $errLog ` + -PassThru + } + if ($p) { Set-Content -LiteralPath $pidFile -Value $p.Id -Encoding ASCII } + Write-Host " started $name (pid=$($p.Id))" -ForegroundColor DarkGray +} + +$plat = Join-Path $Root "platform\platform.exe" +$gw = Join-Path $Root "gateway\gateway.exe" +# 默认重编译,避免改 Go 源码后 restart 仍跑旧 exe +$needPlat = $Rebuild -or -not (Test-Path $plat) +$needGw = $Rebuild -or -not (Test-Path $gw) +if (-not $needPlat -and (Test-Path $plat)) { + $exeTime = (Get-Item $plat).LastWriteTime + $newer = Get-ChildItem -Path (Join-Path $Root "platform") -Recurse -Include *.go -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -gt $exeTime } | + Select-Object -First 1 + if ($newer) { $needPlat = $true } +} +if (-not $needGw -and (Test-Path $gw)) { + $exeTime = (Get-Item $gw).LastWriteTime + $newer = Get-ChildItem -Path (Join-Path $Root "gateway") -Recurse -Include *.go -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -gt $exeTime } | + Select-Object -First 1 + if ($newer) { $needGw = $true } +} +if ($needPlat) { + Write-Host " building platform.exe ..." -ForegroundColor DarkGray + Push-Location (Join-Path $Root "platform") + go build -o platform.exe . + Pop-Location +} +if ($needGw) { + Write-Host " building gateway.exe ..." -ForegroundColor DarkGray + Push-Location (Join-Path $Root "gateway") + go build -o gateway.exe . + Pop-Location +} + +Write-Host "==> Ensure web frontend (vite /#/preview)" -ForegroundColor DarkGray +& (Join-Path $Root "scripts\Ensure-Web.ps1") +# $? 判断 PS 脚本成败;勿单靠 $LASTEXITCODE(会残留 docker/compose 等 native 失败码) +if (-not $?) { throw "Ensure-Web failed" } + +$pyCmd = Get-Command python -ErrorAction SilentlyContinue +if (-not $pyCmd) { $pyCmd = Get-Command py -ErrorAction SilentlyContinue } +if (-not $pyCmd) { throw "python not found" } +$py = $pyCmd.Source + +$npmCmd = Get-Command npm.cmd -ErrorAction SilentlyContinue +if (-not $npmCmd) { $npmCmd = Get-Command npm -ErrorAction SilentlyContinue } +if (-not $npmCmd) { throw "npm not found" } + +# AI 截图直达本机 Vite +$env:WEB_BASE = "http://127.0.0.1:5173" +$env:PLATFORM_BASE = "http://127.0.0.1:8888" +$env:GATEWAY_BASE = "http://127.0.0.1:8180" +$env:GENERATE_LOG_DIR = Join-Path $Root ".runtime\logs\generate" +$env:FIDELITY_SHOT_DIR = Join-Path $Root ".runtime\fidelity_shots" + +Start-Svc "platform" (Join-Path $Root "platform") $plat @("-f", "etc/platform.yaml") +Start-Sleep -Seconds 2 +Start-Svc "ai-service" (Join-Path $Root "ai-service") $py @("-m", "uvicorn", "app:app", "--host", "127.0.0.1", "--port", "8001", "--reload") +Start-Sleep -Seconds 1 +Start-Svc "gateway" (Join-Path $Root "gateway") $gw @("-f", "etc/gateway.yaml") +Start-Sleep -Seconds 1 +Start-Svc "web" (Join-Path $Root "web") $npmCmd.Source @("run", "dev", "--", "--host", "127.0.0.1", "--port", "5173", "--strictPort") + +Write-Host "==> Waiting for services..." +$deadline = (Get-Date).AddSeconds(45) +do { + $webOk = $false + $gwOk = $false + try { + $r = Invoke-WebRequest -Uri "http://127.0.0.1:5173/" -UseBasicParsing -TimeoutSec 2 + if ($r.StatusCode -lt 500) { $webOk = $true } + } catch {} + try { + $r = Invoke-WebRequest -Uri "http://127.0.0.1:8180/gateway/health" -UseBasicParsing -TimeoutSec 2 + if ($r.StatusCode -lt 500) { $gwOk = $true } + } catch {} + if ($webOk -and $gwOk) { break } + Start-Sleep -Seconds 1 +} while ((Get-Date) -lt $deadline) + +if ($webOk -and $gwOk) { + Write-Host "OK Web http://127.0.0.1:5173 Gateway http://127.0.0.1:8180 account demo/demo123" -ForegroundColor Green + Write-Host " logs $LogDir" -ForegroundColor DarkGray +} else { + Write-Host "WARN some services not ready yet. Check logs:" -ForegroundColor Yellow + Write-Host " $LogDir" + Write-Host " web=$webOk gateway=$gwOk" + Write-Host " (debug with visible windows: .\start-host.ps1 -ShowConsole)" +} + +# 后台看门狗:ai-service / gateway 挂掉自动拉起 +$watchPidFile = Join-Path $LogDir "watchdog.pid" +if (Test-Path $watchPidFile) { + $oldWatch = Get-Content $watchPidFile -ErrorAction SilentlyContinue + if ($oldWatch) { Stop-Process -Id ([int]$oldWatch) -Force -ErrorAction SilentlyContinue } +} +$watchScript = Join-Path $Root "watch-services.ps1" +$watchOut = Join-Path $LogDir "watchdog.out.log" +$watchErr = Join-Path $LogDir "watchdog.err.log" +Set-Content -LiteralPath $watchOut -Value "" -Encoding UTF8 +Set-Content -LiteralPath $watchErr -Value "" -Encoding UTF8 +$wp = Start-Process -FilePath "powershell.exe" ` + -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $watchScript) ` + -WorkingDirectory $Root ` + -WindowStyle Hidden ` + -RedirectStandardOutput $watchOut ` + -RedirectStandardError $watchErr ` + -PassThru +if ($wp) { + Set-Content -LiteralPath $watchPidFile -Value $wp.Id -Encoding ASCII + Write-Host " started watchdog (pid=$($wp.Id)) - auto-restarts ai-service if down" -ForegroundColor DarkGray +} + +if (-not $NoBrowser) { Start-Process "http://127.0.0.1:5173" } \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..5d08e07 --- /dev/null +++ b/start.bat @@ -0,0 +1,4 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0start.ps1" %* \ No newline at end of file diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..922d84f --- /dev/null +++ b/start.ps1 @@ -0,0 +1,175 @@ +# aijianzhan one-click start (Docker Compose by default; auto-starts Docker Desktop) +param( + [switch]$Rebuild, + [switch]$UseHost, + [switch]$NoBrowser, + [switch]$ShowConsole, # native mode only: show service console windows + [int]$DockerWaitSec = 120 +) + +$ErrorActionPreference = "Stop" +$Root = $PSScriptRoot +Set-Location -LiteralPath $Root + +function Write-Step([string]$msg) { Write-Host ""; Write-Host "==> $msg" -ForegroundColor Cyan } +function Write-Ok([string]$msg) { Write-Host " OK $msg" -ForegroundColor Green } +function Write-Warn([string]$msg) { Write-Host " !! $msg" -ForegroundColor Yellow } + +function Test-DockerCli { + try { + $null = Get-Command docker -ErrorAction Stop + return $true + } catch { + return $false + } +} + +function Test-DockerReady { + if (-not (Test-DockerCli)) { return $false } + try { + $old = $ErrorActionPreference + $ErrorActionPreference = "Continue" + $null = & docker info 2>&1 + $ok = ($LASTEXITCODE -eq 0) + $ErrorActionPreference = $old + return $ok + } catch { + return $false + } +} + +function Get-DockerDesktopPath { + $pf86 = [Environment]::GetEnvironmentVariable("ProgramFiles(x86)") + $candidates = @( + (Join-Path $env:ProgramFiles "Docker\Docker\Docker Desktop.exe"), + $(if ($pf86) { Join-Path $pf86 "Docker\Docker\Docker Desktop.exe" } else { $null }), + (Join-Path $env:LOCALAPPDATA "Docker\Docker Desktop.exe") + ) + foreach ($p in $candidates) { + if ($p -and (Test-Path -LiteralPath $p)) { return $p } + } + return $null +} + +function Start-DockerDesktopIfNeeded { + if (Test-DockerReady) { return $true } + + if (-not (Test-DockerCli)) { + Write-Warn "docker CLI not found; install Docker Desktop first" + return $false + } + + $exe = Get-DockerDesktopPath + $proc = Get-Process -Name "Docker Desktop","com.docker.backend","com.docker.service" -ErrorAction SilentlyContinue + if (-not $proc) { + if (-not $exe) { + Write-Warn "Docker Desktop.exe not found" + return $false + } + Write-Step "Starting Docker Desktop" + Start-Process -FilePath $exe | Out-Null + Write-Ok "Docker Desktop launched; waiting for engine..." + } else { + Write-Step "Docker Desktop process found; waiting for engine..." + } + + $started = Get-Date + $deadline = $started.AddSeconds([Math]::Max(30, $DockerWaitSec)) + $n = 0 + while ((Get-Date) -lt $deadline) { + if (Test-DockerReady) { + Write-Ok "Docker engine ready" + return $true + } + $n++ + if ($n % 5 -eq 0) { + $elapsed = [int]((Get-Date) - $started).TotalSeconds + Write-Host (" ...waiting Docker ({0}s / {1}s)" -f $elapsed, $DockerWaitSec) -ForegroundColor DarkGray + } + Start-Sleep -Seconds 2 + } + Write-Warn ("Docker wait timeout ({0}s)" -f $DockerWaitSec) + return $false +} + +function Start-NativeFallback([string]$reason) { + Write-Warn $reason + Write-Step "Fallback to native mode (start-host.ps1)" + & (Join-Path $Root "start-host.ps1") -Rebuild:$Rebuild -NoBrowser:$NoBrowser -ShowConsole:$ShowConsole + exit $LASTEXITCODE +} + +if ($UseHost) { + Start-NativeFallback "UseHost specified" +} + +$dockerReady = Start-DockerDesktopIfNeeded +if (-not $dockerReady) { + Start-NativeFallback "Docker not ready; fallback native. Fix Docker then rerun .\start.ps1" +} + +Write-Step "Apply China Docker registry mirrors" +& (Join-Path $Root "scripts\Apply-DockerMirrors.ps1") -NoRestartHint + +New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\pgdata") | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\uploads") | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\logs") | Out-Null + +# Wipe native leftovers so Docker and host never fight over the same ports +Write-Step "Pre-clean ports / native leftovers" +& (Join-Path $Root "scripts\Stop-Stack.ps1") -Quiet + +Write-Step "Build web dist (auto, includes /#/preview)" +& (Join-Path $Root "scripts\Ensure-Web.ps1") -BuildDist +if (-not $?) { + Start-NativeFallback "web dist build failed; fallback native (vite)." +} + +# Prefer DaoCloud-prefixed library images (see docker-compose.yml / Dockerfiles) +if (-not $env:DOCKER_BASE_REGISTRY) { + $env:DOCKER_BASE_REGISTRY = "docker.m.daocloud.io/library" +} + +Write-Step "Docker Compose up (data -> .runtime)" +# Prefer reuse of local images; --build only when -Rebuild (Hub often fails in CN) +$composeArgs = @("compose", "up", "-d") +if ($Rebuild) { $composeArgs += "--build" } +& docker @composeArgs +if ($LASTEXITCODE -ne 0) { + Write-Warn "compose up failed; retry once with --build" + & docker compose up -d --build +} +if ($LASTEXITCODE -ne 0) { + # Hub / network failures are common in CN; keep the stack usable via native mode + Start-NativeFallback "docker compose failed (Docker Hub/network). Auto fallback to native mode." +} + +Write-Step "Waiting for gateway / web" +$ok = $false +for ($i = 0; $i -lt 60; $i++) { + try { + $h = Invoke-WebRequest -Uri "http://127.0.0.1:8180/gateway/health" -UseBasicParsing -TimeoutSec 2 + $w = Invoke-WebRequest -Uri "http://127.0.0.1:5173/" -UseBasicParsing -TimeoutSec 2 + if ($h.StatusCode -lt 500 -and $w.StatusCode -lt 500) { $ok = $true; break } + } catch { + # retry + } + Start-Sleep -Seconds 2 +} +if ($ok) { Write-Ok "services ready" } else { Write-Warn "health check timeout, see: docker compose logs" } + +$sql = 'ALTER USER platform CREATEDB;' +& docker compose exec -T postgres psql -U platform -d platform -c $sql 2>&1 | Out-Null + +Write-Host "" +Write-Host "========================================" -ForegroundColor Green +Write-Host " Web http://127.0.0.1:5173" +Write-Host " Gateway http://127.0.0.1:8180" +Write-Host " Account demo / demo123" +Write-Host " Data $Root\.runtime" +Write-Host " Stop .\stop.ps1" +Write-Host "========================================" -ForegroundColor Green + +if (-not $NoBrowser) { + Start-Process "http://127.0.0.1:5173" +} diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..974325d --- /dev/null +++ b/start.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# AI 建站 — Linux 一键启动(Docker Compose) +# 用法:cd 项目根 && ./start.sh [--rebuild] +# 行尾:LF +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" +# shellcheck disable=SC1091 +. "$ROOT/scripts/lib-aijz-deploy.sh" + +REBUILD=0 +for a in "$@"; do + case "$a" in + --rebuild|-r) REBUILD=1 ;; + -h|--help) + echo "用法: $0 [--rebuild]" + exit 0 + ;; + esac +done + +echo "========================================" +echo " AI 建站 启动" +echo " 路径: $ROOT" +echo "========================================" + +ensure_docker +stack_up "$REBUILD" +healthcheck +print_endpoints diff --git a/stop.bat b/stop.bat new file mode 100644 index 0000000..23a6aff --- /dev/null +++ b/stop.bat @@ -0,0 +1,5 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0stop.ps1" +pause \ No newline at end of file diff --git a/stop.ps1 b/stop.ps1 new file mode 100644 index 0000000..292515f --- /dev/null +++ b/stop.ps1 @@ -0,0 +1,6 @@ +# Stop all stack processes (Docker + native). Thin wrapper around scripts\Stop-Stack.ps1. +$ErrorActionPreference = "Continue" +$Root = $PSScriptRoot +Set-Location -LiteralPath $Root +& (Join-Path $Root "scripts\Stop-Stack.ps1") +exit $LASTEXITCODE diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..66f3fbd --- /dev/null +++ b/stop.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# AI 建站 — 停止 Docker 栈 +# 用法:./stop.sh +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" +# shellcheck disable=SC1091 +. "$ROOT/scripts/lib-aijz-deploy.sh" + +echo "停止 AI 建站 ($ROOT) ..." +ensure_docker +stack_down +echo "已停止." diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..bcb1fb9 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +.ai-code-tracker.json +_out/ diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..4b7818f --- /dev/null +++ b/test/README.md @@ -0,0 +1,29 @@ +# test 联调素材(无代码) + +本目录**只放某一业务的示例素材**,用于验证通用系统能否按上传内容还原。 +**系统本身不绑定任何行业**;文案、图例、字段、坐标方向一律来自蓝图 / 上传,不写死在产品代码里。 + +``` +test/ + prompts/ 需求说明(研发对照) + refs/ 参考截图 + 页面 HTML + data/ 可上传数据 / 抓包 + _out/ 临时验收截图(可删) +``` + +## 控制台怎么用 + +登录 → 上传下列素材 → 生成 → 发布 → 打开应用。换另一套业务时,只换素材,无需改系统代码。 + +| 素材 | 路径 | +|------|------| +| 数据 | `data/whm_articles.xlsx` / `whm_articles.csv` / `whm_articles.json`(企业官网文章样例,可导入模块 `whm`) | +| 其它数据 | `data/url.config` 或 `data/chart_source.json` | +| 截图 | `refs/board_a.png`、`refs/board_b.png` | +| 页面源码 | `refs/nav.html`、`refs/dashboard.html` | +| 说明(可选) | `prompts/prompt.txt` + `prompts/prompt_1.txt` | + +### 往已发布模块灌数 + +登录控制台 → 打开模块 `whm` → 文章列表 →「导入 Excel」→ 选 `data/whm_articles.xlsx`(或 `.csv`)。 +字段:`title` / `content` / `status`(草稿|已发布)/ `created_at`。 diff --git a/test/data/chart_source.json b/test/data/chart_source.json new file mode 100644 index 0000000..32ec310 --- /dev/null +++ b/test/data/chart_source.json @@ -0,0 +1,883 @@ +{ + "mcljChartData": { + "dkilo": ["514828_300698_DK", "514829_300734_DK", "514830_300766_DK", "514831_300799_DK", "514835_300832_DK", "514837_300865_DK", "474547_300898_DK", "471871_300943_DK", "513972_301015_DK", "513973_301059_DK", "520277_301092_DK", "474548_301125_DK", "515289_301158_DK", "520278_301191_DK", "520279_301223_DK", "551801_301256_DK", "530674_301289_DK", "535370_301322_DK", "515291_301354_DK", "515292_301387_DK", "515293_301420_DK", "515294_301453_DK", "515295_301486_DK", "522064_301518_DK", "527773_301551_DK", "551804_301584_DK", "540715_301617_DK", "524474_301650_DK", "535716_301682_DK", "543638_301715_DK", "536840_301740_DK", "536843_301765_DK", "545257_301798_DK", "526971_301830_DK", "537055_301863_DK", "543639_301896_DK", "515411_301929_DK", "515412_301962_DK", "515413_301994_DK", "551806_302027_DK", "551807_302060_DK", "524740_302094_DK", "914312_302099_DK", "914313_302110_DK", "914314_302130_DK", "914315_302180_DK", "914316_302230_DK", "914317_302280_DK", "914318_302330_DK", "914319_302380_DK", "914320_302430_DK", "914321_302480_DK", "914322_302530_DK", "914323_302580_DK", "914324_302630_DK", "914325_302645_DK", "801545_302650_DK", "914327_302655_DK", "914328_302685_DK", "914329_302709_DK", "880328_302714_DK", "914330_302719_DK", "914331_302745_DK", "914332_302795_DK", "904671_302845_DK", "904673_302895_DK", "904674_302927_DK", "880379_302932_DK", "904675_302937_DK", "904676_302987_DK", "904677_303037_DK", "914333_303087_DK", "909913_303137_DK", "909914_303187_DK", "914334_303219_DK", "914335_303265_DK", "914336_303275_DK", "914337_303300_DK", "914338_303350_DK", "914339_303400_DK", "914340_303450_DK", "914341_303500_DK", "921813_303558_DK", "880747_303563_DK", "921814_303568_DK", "921817_303618_DK", "921815_303668_DK", "921816_303718_DK", "921818_303768_DK", "930557_303818_DK", "930556_303868_DK", "930554_303918_DK", "930558_303968_DK", "932880_304034_DK", "932881_304084_DK", "932882_304134_DK", "932883_304145_DK", "856137_304150_DK", "932884_304150_DK", "932885_304155_DK", "932886_304180_DK", "932887_304230_DK", "932888_304280_DK", "932889_304330_DK", "932890_304380_DK", "932891_304430_DK", "932892_304480_DK", "932893_304530_DK", "932894_304576_DK", "932895_304596_DK", "932896_304605_DK", "515800_304614_DK", "515802_304646_DK", "515805_304679_DK", "515809_304712_DK", "515814_304745_DK", "515817_304778_DK", "474325_304810_DK", "474327_304843_DK", "515824_304876_DK", "515969_304909_DK", "517117_304941_DK", "515970_304974_DK", "515971_305007_DK", "516020_305040_DK", "536111_305072_DK", "516022_305105_DK", "516023_305138_DK", "516025_305171_DK", "516026_305203_DK", "516222_305236_DK", "523347_305269_DK", "516223_305302_DK", "516224_305335_DK", "523348_305368_DK", "523350_305400_DK", "541307_305433_DK", "548090_305466_DK", "548092_305499_DK", "533409_305532_DK", "540943_305565_DK", "533410_305597_DK", "520281_305630_DK", "915016_305637_DK", "915017_305647_DK", "915018_305677_DK", "915019_305727_DK", "915020_305777_DK", "915021_305827_DK", "845160_305832_DK", "915022_305837_DK", "915023_305887_DK", "915024_305937_DK", "915025_305987_DK", "915026_306037_DK", "915027_306087_DK", "915028_306137_DK", "915029_306187_DK", "915030_306237_DK", "915031_306287_DK", "915032_306337_DK", "915033_306387_DK", "915035_306422_DK", "880749_306427_DK", "915036_306432_DK", "915037_306482_DK", "915038_306532_DK", "915039_306582_DK", "915040_306615_DK", "801548_306620_DK", "915045_306625_DK", "915046_306675_DK", "915047_306725_DK", "924385_306730_DK", "924386_306745_DK", "924388_306765_DK", "915048_306775_DK", "915049_306825_DK", "915050_306875_DK", "915051_306909_DK", "915052_306929_DK", "915053_306938_DK", "516538_306944_DK", "516539_306977_DK", "516540_307010_DK", "516541_307042_DK", "516542_307075_DK", "516543_307108_DK", "516544_307141_DK", "516545_307173_DK", "516546_307206_DK", "516547_307239_DK", "516548_307272_DK", "516549_307305_DK", "516551_307337_DK", "522600_307370_DK", "529620_307403_DK", "471859_307436_DK", "471860_307468_DK", "516845_307501_DK", "516847_307534_DK", "516849_307567_DK", "516865_307599_DK", "471865_307632_DK", "471866_307665_DK", "471868_307698_DK", "477235_307731_DK", "914687_307738_DK", "914688_307746_DK", "914689_307767_DK", "914690_307817_DK", "914691_307867_DK", "914692_307917_DK", "914693_307967_DK", "914703_308017_DK", "914756_308037_DK", "923313_308042_DK", "880752_308042_DK", "914757_308047_DK", "914758_308097_DK", "914759_308147_DK", "914760_308197_DK", "914761_308247_DK", "914762_308297_DK", "914763_308347_DK", "914764_308397_DK", "914765_308447_DK", "914766_308497_DK", "914767_308547_DK", "914768_308597_DK", "914769_308647_DK", "914770_308697_DK", "914771_308721_DK", "914772_308741_DK", "914773_308750_DK", "517002_308758_DK", "521699_308803_DK", "517003_308883_DK", "474704_308927_DK", "517004_308960_DK", "547172_308993_DK", "538108_309026_DK", "517005_309059_DK", "520282_309091_DK", "517007_309124_DK", "517008_309157_DK", "517010_309190_DK", "528068_309223_DK", "517011_309255_DK", "517012_309288_DK", "517013_309321_DK", "914718_309328_DK", "914719_309337_DK", "914720_309357_DK", "914721_309407_DK", "914722_309457_DK", "914723_309507_DK", "914724_309557_DK", "914725_309607_DK", "914726_309630_DK", "914727_309672_DK", "845161_309677_DK", "918492_309677_DK", "914729_309682_DK", "914730_381115_DK", "914732_381165_DK", "914733_381215_DK", "914734_381282_DK", "914736_381308_DK", "845180_381313_DK", "914737_381318_DK", "906126_381400_DK", "906127_381450_DK", "906128_381500_DK", "906129_381550_DK", "914514_381600_DK", "914515_381650_DK", "914516_381700_DK", "914517_381738_DK", "914518_381758_DK", "914519_381767_DK", "520449_381773_DK", "520450_381806_DK", "541308_381838_DK", "550469_381872_DK", "765317_381904_DK", "765319_381937_DK", "765320_381970_DK", "794927_382003_DK", "765327_382030_DK"], + "cjl": [{ + "value": "-1.71", + "color": "#8000FF" + }, { + "value": "-3.11", + "color": "#8000FF" + }, { + "value": "-0.42", + "color": "#8000FF" + }, { + "value": "-1.83", + "color": "#8000FF" + }, { + "value": "-2.35", + "color": "#8000FF" + }, { + "value": "-0.32", + "color": "#8000FF" + }, { + "value": "-2.96", + "color": "#8000FF" + }, { + "value": "-2.62", + "color": "#8000FF" + }, { + "value": "-1.43", + "color": "#8000FF" + }, { + "value": "-2.01", + "color": "#8000FF" + }, { + "value": "-1.59", + "color": "#8000FF" + }, { + "value": "-2.14", + "color": "#8000FF" + }, { + "value": "-1.06", + "color": "#8000FF" + }, { + "value": "-2.72", + "color": "#8000FF" + }, { + "value": "-2.29", + "color": "#8000FF" + }, { + "value": "-2.3", + "color": "#8000FF" + }, { + "value": "-2.31", + "color": "#8000FF" + }, { + "value": "-0.81", + "color": "#8000FF" + }, { + "value": "-2.85", + "color": "#8000FF" + }, { + "value": "-3.06", + "color": "#8000FF" + }, { + "value": "-2.26", + "color": "#8000FF" + }, { + "value": "-1.78", + "color": "#8000FF" + }, { + "value": "-0.19", + "color": "#8000FF" + }, { + "value": "-2.11", + "color": "#8000FF" + }, { + "value": "-2.18", + "color": "#8000FF" + }, { + "value": "-1.86", + "color": "#8000FF" + }, { + "value": "-2.78", + "color": "#8000FF" + }, { + "value": "0.66", + "color": "#8000FF" + }, { + "value": "-1.53", + "color": "#8000FF" + }, { + "value": "-0.72", + "color": "#8000FF" + }, { + "value": "-2.22", + "color": "#8000FF" + }, { + "value": "-0.19", + "color": "#8000FF" + }, { + "value": "-1.99", + "color": "#8000FF" + }, { + "value": "-2.15", + "color": "#8000FF" + }, { + "value": "-0.88", + "color": "#8000FF" + }, { + "value": "-2.19", + "color": "#8000FF" + }, { + "value": "-1.32", + "color": "#8000FF" + }, { + "value": "-1.46", + "color": "#8000FF" + }, { + "value": "-1.78", + "color": "#8000FF" + }, { + "value": "-1.25", + "color": "#8000FF" + }, { + "value": "-1.91", + "color": "#8000FF" + }, { + "value": "-0.88", + "color": "#8000FF" + }, { + "value": "0.63", + "color": "#214080" + }, { + "value": "-0.79", + "color": "#214080" + }, { + "value": "1.51", + "color": "#214080" + }, { + "value": "-0.06", + "color": "#214080" + }, { + "value": "-0.36", + "color": "#214080" + }, { + "value": "-1.8", + "color": "#214080" + }, { + "value": "1.14", + "color": "#214080" + }, { + "value": "0.26", + "color": "#214080" + }, { + "value": "1.25", + "color": "#214080" + }, { + "value": "3.98", + "color": "#214080" + }, { + "value": "1.01", + "color": "#214080" + }, { + "value": "1.56", + "color": "#214080" + }, { + "value": "0.95", + "color": "#214080" + }, { + "value": "0.49", + "color": "#214080" + }, { + "value": "-3.36", + "color": "#8000FF" + }, { + "value": "-2.43", + "color": "#214080" + }, { + "value": "-0.33", + "color": "#214080" + }, { + "value": "0.9", + "color": "#214080" + }, { + "value": "-2.18", + "color": "#8000FF" + }, { + "value": "1.49", + "color": "#214080" + }, { + "value": "0.78", + "color": "#214080" + }, { + "value": "0.62", + "color": "#214080" + }, { + "value": "-0.39", + "color": "#214080" + }, { + "value": "-0.26", + "color": "#214080" + }, { + "value": "-2.36", + "color": "#214080" + }, { + "value": "-2.84", + "color": "#8000FF" + }, { + "value": "-1", + "color": "#214080" + }, { + "value": "-1.15", + "color": "#214080" + }, { + "value": "-1.82", + "color": "#214080" + }, { + "value": "-0.39", + "color": "#214080" + }, { + "value": "-2.99", + "color": "#214080" + }, { + "value": "-2.07", + "color": "#214080" + }, { + "value": "-1.01", + "color": "#214080" + }, { + "value": "1.38", + "color": "#214080" + }, { + "value": "-1.14", + "color": "#214080" + }, { + "value": "0.3", + "color": "#214080" + }, { + "value": "-0.52", + "color": "#214080" + }, { + "value": "-1.4", + "color": "#214080" + }, { + "value": "-1.02", + "color": "#214080" + }, { + "value": "-2.08", + "color": "#214080" + }, { + "value": "2.52", + "color": "#214080" + }, { + "value": "-1.82", + "color": "#8000FF" + }, { + "value": "-1.19", + "color": "#214080" + }, { + "value": "-0.88", + "color": "#214080" + }, { + "value": "-1.5", + "color": "#214080" + }, { + "value": "0.56", + "color": "#214080" + }, { + "value": "-0.82", + "color": "#214080" + }, { + "value": "-0.01", + "color": "#214080" + }, { + "value": "-0.51", + "color": "#214080" + }, { + "value": "0.31", + "color": "#214080" + }, { + "value": "-0.01", + "color": "#214080" + }, { + "value": "-0.37", + "color": "#214080" + }, { + "value": "-0.92", + "color": "#214080" + }, { + "value": "-0.65", + "color": "#214080" + }, { + "value": "0.18", + "color": "#214080" + }, { + "value": "-0.68", + "color": "#8000FF" + }, { + "value": "1.39", + "color": "#214080" + }, { + "value": "-1.02", + "color": "#214080" + }, { + "value": "-1.2", + "color": "#214080" + }, { + "value": "0.46", + "color": "#214080" + }, { + "value": "-0.02", + "color": "#214080" + }, { + "value": "0.79", + "color": "#214080" + }, { + "value": "-0.3", + "color": "#214080" + }, { + "value": "-0.52", + "color": "#214080" + }, { + "value": "-0.2", + "color": "#214080" + }, { + "value": "-0.91", + "color": "#214080" + }, { + "value": "0.14", + "color": "#214080" + }, { + "value": "-0.22", + "color": "#214080" + }, { + "value": "-1.03", + "color": "#214080" + }, { + "value": "-2.39", + "color": "#8000FF" + }, { + "value": "-3", + "color": "#8000FF" + }, { + "value": "-1.35", + "color": "#8000FF" + }, { + "value": "-1.35", + "color": "#8000FF" + }, { + "value": "-2.13", + "color": "#8000FF" + }, { + "value": "0.25", + "color": "#8000FF" + }, { + "value": "-1.08", + "color": "#8000FF" + }, { + "value": "-1.91", + "color": "#8000FF" + }, { + "value": "-1.79", + "color": "#8000FF" + }, { + "value": "-1.8", + "color": "#8000FF" + }, { + "value": "-0.15", + "color": "#8000FF" + }, { + "value": "-1.41", + "color": "#8000FF" + }, { + "value": "-2.19", + "color": "#8000FF" + }, { + "value": "-2.33", + "color": "#8000FF" + }, { + "value": "-0.91", + "color": "#8000FF" + }, { + "value": "-2.32", + "color": "#8000FF" + }, { + "value": "-1.1", + "color": "#8000FF" + }, { + "value": "-1.41", + "color": "#8000FF" + }, { + "value": "-2.2", + "color": "#8000FF" + }, { + "value": "-2.58", + "color": "#8000FF" + }, { + "value": "-1.48", + "color": "#8000FF" + }, { + "value": "-1.9", + "color": "#8000FF" + }, { + "value": "-1.54", + "color": "#8000FF" + }, { + "value": "-1.91", + "color": "#8000FF" + }, { + "value": "-1.53", + "color": "#8000FF" + }, { + "value": "-0.09", + "color": "#8000FF" + }, { + "value": "0.67", + "color": "#8000FF" + }, { + "value": "-2.31", + "color": "#8000FF" + }, { + "value": "-2.28", + "color": "#8000FF" + }, { + "value": "0.17", + "color": "#8000FF" + }, { + "value": "-0.62", + "color": "#8000FF" + }, { + "value": "-2.45", + "color": "#8000FF" + }, { + "value": "-0.7", + "color": "#214080" + }, { + "value": "0.05", + "color": "#214080" + }, { + "value": "-0.44", + "color": "#214080" + }, { + "value": "0.05", + "color": "#214080" + }, { + "value": "1.15", + "color": "#214080" + }, { + "value": "0.3", + "color": "#214080" + }, { + "value": "-2.77", + "color": "#8000FF" + }, { + "value": "0.16", + "color": "#214080" + }, { + "value": "0.6", + "color": "#214080" + }, { + "value": "0.9", + "color": "#214080" + }, { + "value": "-1.19", + "color": "#214080" + }, { + "value": "0.71", + "color": "#214080" + }, { + "value": "-1.66", + "color": "#214080" + }, { + "value": "-0.38", + "color": "#214080" + }, { + "value": "-0.69", + "color": "#214080" + }, { + "value": "-0.36", + "color": "#214080" + }, { + "value": "-1", + "color": "#214080" + }, { + "value": "-1.53", + "color": "#214080" + }, { + "value": "-1.8", + "color": "#214080" + }, { + "value": "-3.52", + "color": "#214080" + }, { + "value": "-0.51", + "color": "#8000FF" + }, { + "value": "-1.31", + "color": "#214080" + }, { + "value": "-0.87", + "color": "#214080" + }, { + "value": "-1.04", + "color": "#214080" + }, { + "value": "-2.02", + "color": "#214080" + }, { + "value": "-1.5", + "color": "#214080" + }, { + "value": "-4.07", + "color": "#8000FF" + }, { + "value": "-2.04", + "color": "#214080" + }, { + "value": "-1.7", + "color": "#214080" + }, { + "value": "0.04", + "color": "#214080" + }, { + "value": "-1.27", + "color": "#214080" + }, { + "value": "0.03", + "color": "#214080" + }, { + "value": "-0.17", + "color": "#214080" + }, { + "value": "1.13", + "color": "#214080" + }, { + "value": "-0.37", + "color": "#214080" + }, { + "value": "-1.13", + "color": "#214080" + }, { + "value": "-1.12", + "color": "#214080" + }, { + "value": "0.17", + "color": "#214080" + }, { + "value": "0.34", + "color": "#214080" + }, { + "value": "-3.34", + "color": "#8000FF" + }, { + "value": "-2.76", + "color": "#8000FF" + }, { + "value": "-1.6", + "color": "#8000FF" + }, { + "value": "-2.41", + "color": "#8000FF" + }, { + "value": "-1.64", + "color": "#8000FF" + }, { + "value": "-0.92", + "color": "#8000FF" + }, { + "value": "0.23", + "color": "#8000FF" + }, { + "value": "-1.11", + "color": "#8000FF" + }, { + "value": "-2.46", + "color": "#8000FF" + }, { + "value": "-2.48", + "color": "#8000FF" + }, { + "value": "-2.6", + "color": "#8000FF" + }, { + "value": "-2.77", + "color": "#8000FF" + }, { + "value": "-3.24", + "color": "#8000FF" + }, { + "value": "-2.16", + "color": "#8000FF" + }, { + "value": "-2.08", + "color": "#8000FF" + }, { + "value": "-1.83", + "color": "#8000FF" + }, { + "value": "-1.72", + "color": "#8000FF" + }, { + "value": "-0.62", + "color": "#8000FF" + }, { + "value": "-1.19", + "color": "#8000FF" + }, { + "value": "-2.04", + "color": "#8000FF" + }, { + "value": "-0.11", + "color": "#8000FF" + }, { + "value": "-2.76", + "color": "#8000FF" + }, { + "value": "-2.38", + "color": "#8000FF" + }, { + "value": "-3.31", + "color": "#8000FF" + }, { + "value": "-1.96", + "color": "#8000FF" + }, { + "value": "-0.9", + "color": "#214080" + }, { + "value": "-0.6", + "color": "#214080" + }, { + "value": "-0.45", + "color": "#214080" + }, { + "value": "-1.32", + "color": "#214080" + }, { + "value": "-1.1", + "color": "#214080" + }, { + "value": "-0.19", + "color": "#214080" + }, { + "value": "-1.25", + "color": "#214080" + }, { + "value": "-0.14", + "color": "#214080" + }, { + "value": "-0.99", + "color": "#214080" + }, { + "value": "-0.28", + "color": "#214080" + }, { + "value": "-1.63", + "color": "#8000FF" + }, { + "value": "-1.23", + "color": "#214080" + }, { + "value": "0.3", + "color": "#214080" + }, { + "value": "-0.57", + "color": "#214080" + }, { + "value": "-2.62", + "color": "#214080" + }, { + "value": "-0.87", + "color": "#214080" + }, { + "value": "-0.72", + "color": "#214080" + }, { + "value": "-1.44", + "color": "#214080" + }, { + "value": "-1.66", + "color": "#214080" + }, { + "value": "-0.68", + "color": "#214080" + }, { + "value": "-1.28", + "color": "#214080" + }, { + "value": "-0.72", + "color": "#214080" + }, { + "value": "-2.31", + "color": "#214080" + }, { + "value": "0.12", + "color": "#214080" + }, { + "value": "-0.6", + "color": "#214080" + }, { + "value": "-2.71", + "color": "#214080" + }, { + "value": "-0.24", + "color": "#214080" + }, { + "value": "-1.23", + "color": "#214080" + }, { + "value": "-3.06", + "color": "#8000FF" + }, { + "value": "-1.05", + "color": "#8000FF" + }, { + "value": "0.59", + "color": "#8000FF" + }, { + "value": "-1.79", + "color": "#8000FF" + }, { + "value": "-2.24", + "color": "#8000FF" + }, { + "value": "-2.1", + "color": "#8000FF" + }, { + "value": "-1.81", + "color": "#8000FF" + }, { + "value": "-2.05", + "color": "#8000FF" + }, { + "value": "-1.35", + "color": "#8000FF" + }, { + "value": "-1.66", + "color": "#8000FF" + }, { + "value": "0.11", + "color": "#8000FF" + }, { + "value": "-1.66", + "color": "#8000FF" + }, { + "value": "-1.33", + "color": "#8000FF" + }, { + "value": "0.36", + "color": "#8000FF" + }, { + "value": "0.45", + "color": "#8000FF" + }, { + "value": "-1.95", + "color": "#8000FF" + }, { + "value": "-0.57", + "color": "#214080" + }, { + "value": "-0.97", + "color": "#214080" + }, { + "value": "-0.12", + "color": "#214080" + }, { + "value": "-0.16", + "color": "#214080" + }, { + "value": "-0.05", + "color": "#214080" + }, { + "value": "-1.1", + "color": "#214080" + }, { + "value": "-0.54", + "color": "#214080" + }, { + "value": "-0.63", + "color": "#214080" + }, { + "value": "-0.32", + "color": "#214080" + }, { + "value": "-0.07", + "color": "#214080" + }, { + "value": "-3.05", + "color": "#8000FF" + }, { + "value": "-0.65", + "color": "#214080" + }, { + "value": "0.42", + "color": "#214080" + }, { + "value": "-0.76", + "color": "#214080" + }, { + "value": "0.07", + "color": "#214080" + }, { + "value": "0.15", + "color": "#214080" + }, { + "value": "-0.55", + "color": "#214080" + }, { + "value": "-0.57", + "color": "#214080" + }, { + "value": "-0.36", + "color": "#8000FF" + }, { + "value": "-1.09", + "color": "#214080" + }, { + "value": "-0.66", + "color": "#214080" + }, { + "value": "-2.32", + "color": "#214080" + }, { + "value": "0.19", + "color": "#214080" + }, { + "value": "-1.25", + "color": "#214080" + }, { + "value": "-0.73", + "color": "#214080" + }, { + "value": "-0.69", + "color": "#214080" + }, { + "value": "-1.08", + "color": "#214080" + }, { + "value": "-0.04", + "color": "#214080" + }, { + "value": "0.55", + "color": "#214080" + }, { + "value": "-0.62", + "color": "#214080" + }, { + "value": "-0.72", + "color": "#8000FF" + }, { + "value": "-1.53", + "color": "#8000FF" + }, { + "value": "-1.69", + "color": "#8000FF" + }, { + "value": "-0.53", + "color": "#8000FF" + }, { + "value": "-1.72", + "color": "#8000FF" + }, { + "value": "-2.05", + "color": "#8000FF" + }, { + "value": "-0.91", + "color": "#8000FF" + }, { + "value": "-1.01", + "color": "#8000FF" + }, { + "value": "-1.97", + "color": "#8000FF" + }], + "sjcjl": [-20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -15, -10.1, -12.4, -40.5, -31.7, -17.9, -15.7, -17.7, -23, -32.1, -33.8, -27.9, -15, -15, -15, -33.9, -33.9, -33.9, -15, -15, -15, -32.7, -22.7, -22.7, -9, -12.4, -12.4, -12.4, -16.1, -12.1, -12.2, -11.9, -12.2, -13, -15, -15, -12.7, -16.7, -14.6, -16.4, -15.8, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -20, -15, -15, -15, -40.9, -44.3, -39.9, -38.8, -41.5, -43, -38.8, -41.9, -42.2, -43, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -15, -15, -15, -15, -15, -15, -15, -15, -30.3, -30.3, -29.3, -27.4, -24, -18.2, -20.3, -19.3, -21.3, -21.2, -23.2, -23.1, -19.7, -17.1, -24.1, -24.1, -20.6, -14, -13.6, -13.6, -19, -19, -19, -34.7, -25.8, -15, -15, -15, -14.6, -13.2, -14.2, -24, -17.1, -15.5, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -30.3, -30.3, -22.2, -36.3, -16.1, -18.6, -17.4, -35.1, -38.9, -20, -38.9, -20, -28.9, -33.7, -23.4, -42.3, -36.7, -34.2, -18.1, -22.9, -24.3, -25.7, -12.8, -13.4, -12.3, -11.9, -11.5, -11.6, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, -24.9, -24.8, -23.2, -23.3, -23.2, -23.3, -27.8, -27.8, -23.1, -24.5, -24.5, -24.5, -24.5, -25.6, -25.6, -32.3, -15, -15, -15, -15, -14.9, -26.3, -32.8, -35.4, -29.4, -23, -27.6, -31.2, -15, -33.3, -20, -20, -20, -20, -20, -20, -20, -20, -20] + }, + "cljdChartData": { + "workinfo_kilo": ["30_300698_514828_DK", "30_300734_514829_DK", "30_300766_514830_DK", "30_300799_514831_DK", "30_300832_514835_DK", "30_300865_514837_DK", "30_300898_474547_DK", "30_300943_471871_DK", "30_301015_513972_DK", "30_301059_513973_DK", "30_301092_520277_DK", "30_301125_474548_DK", "30_301158_515289_DK", "30_301191_520278_DK", "30_301223_520279_DK", "30_301256_551801_DK", "30_301289_530674_DK", "30_301322_535370_DK", "30_301354_515291_DK", "30_301387_515292_DK", "30_301420_515293_DK", "30_301453_515294_DK", "30_301486_515295_DK", "30_301518_522064_DK", "30_301551_527773_DK", "30_301584_551804_DK", "30_301617_540715_DK", "30_301650_524474_DK", "30_301682_535716_DK", "30_301715_543638_DK", "30_301740_536840_DK", "30_301765_536843_DK", "30_301798_545257_DK", "30_301830_526971_DK", "30_301863_537055_DK", "30_301896_543639_DK", "30_301929_515411_DK", "30_301962_515412_DK", "30_301994_515413_DK", "30_302027_551806_DK", "30_302060_551807_DK", "30_302094_524740_DK", "7_302099_914312_DK", "7_302110_914313_DK", "7_302130_914314_DK", "7_302180_914315_DK", "7_302230_914316_DK", "7_302280_914317_DK", "7_302330_914318_DK", "7_302380_914319_DK", "7_302430_914320_DK", "7_302480_914321_DK", "7_302530_914322_DK", "7_302580_914323_DK", "7_302630_914324_DK", "7_302645_914325_DK", "7_302650_801545_DK", "7_302655_914327_DK", "7_302685_914328_DK", "7_302709_914329_DK", "7_302714_880328_DK", "7_302719_914330_DK", "7_302745_914331_DK", "7_302795_914332_DK", "30_302845_904671_DK", "30_302895_904673_DK", "30_302927_904674_DK", "7_302932_880379_DK", "30_302937_904675_DK", "30_302987_904676_DK", "30_303037_904677_DK", "1_303087_914333_DK", "14_303137_909913_DK", "14_303187_909914_DK", "1_303219_914334_DK", "1_303265_914335_DK", "1_303275_914336_DK", "1_303300_914337_DK", "1_303350_914338_DK", "1_303400_914339_DK", "1_303450_914340_DK", "1_303500_914341_DK", "1_303558_921813_DK", "7_303563_880747_DK", "1_303568_921814_DK", "1_303618_921817_DK", "1_303668_921815_DK", "1_303718_921816_DK", "1_303768_921818_DK", "1_303818_930557_DK", "1_303868_930556_DK", "1_303918_930554_DK", "1_303968_930558_DK", "1_304034_932880_DK", "1_304084_932881_DK", "1_304134_932882_DK", "1_304145_932883_DK", "1_304150_932884_DK", "7_304150_856137_DK", "1_304155_932885_DK", "1_304180_932886_DK", "1_304230_932887_DK", "1_304280_932888_DK", "1_304330_932889_DK", "1_304380_932890_DK", "1_304430_932891_DK", "1_304480_932892_DK", "1_304530_932893_DK", "1_304576_932894_DK", "1_304596_932895_DK", "1_304605_932896_DK", "30_304614_515800_DK", "30_304646_515802_DK", "30_304679_515805_DK", "30_304712_515809_DK", "30_304745_515814_DK", "30_304778_515817_DK", "30_304810_474325_DK", "30_304843_474327_DK", "30_304876_515824_DK", "30_304909_515969_DK", "30_304941_517117_DK", "30_304974_515970_DK", "30_305007_515971_DK", "30_305040_516020_DK", "30_305072_536111_DK", "30_305105_516022_DK", "30_305138_516023_DK", "30_305171_516025_DK", "30_305203_516026_DK", "30_305236_516222_DK", "30_305269_523347_DK", "30_305302_516223_DK", "30_305335_516224_DK", "30_305368_523348_DK", "30_305400_523350_DK", "30_305433_541307_DK", "30_305466_548090_DK", "30_305499_548092_DK", "30_305532_533409_DK", "30_305565_540943_DK", "30_305597_533410_DK", "30_305630_520281_DK", "7_305637_915016_DK", "7_305647_915017_DK", "7_305677_915018_DK", "7_305727_915019_DK", "7_305777_915020_DK", "7_305827_915021_DK", "7_305832_845160_DK", "7_305837_915022_DK", "7_305887_915023_DK", "7_305937_915024_DK", "7_305987_915025_DK", "7_306037_915026_DK", "7_306087_915027_DK", "7_306137_915028_DK", "7_306187_915029_DK", "7_306237_915030_DK", "7_306287_915031_DK", "7_306337_915032_DK", "7_306387_915033_DK", "7_306422_915035_DK", "30_306427_880749_DK", "7_306432_915036_DK", "7_306482_915037_DK", "7_306532_915038_DK", "7_306582_915039_DK", "7_306615_915040_DK", "7_306620_801548_DK", "7_306625_915045_DK", "7_306675_915046_DK", "7_306725_915047_DK", "7_306730_924385_DK", "7_306745_924386_DK", "7_306765_924388_DK", "7_306775_915048_DK", "7_306825_915049_DK", "7_306875_915050_DK", "7_306909_915051_DK", "7_306929_915052_DK", "7_306938_915053_DK", "30_306944_516538_DK", "30_306977_516539_DK", "30_307010_516540_DK", "30_307042_516541_DK", "30_307075_516542_DK", "30_307108_516543_DK", "30_307141_516544_DK", "30_307173_516545_DK", "30_307206_516546_DK", "30_307239_516547_DK", "30_307272_516548_DK", "30_307305_516549_DK", "30_307337_516551_DK", "30_307370_522600_DK", "30_307403_529620_DK", "30_307436_471859_DK", "30_307468_471860_DK", "30_307501_516845_DK", "30_307534_516847_DK", "30_307567_516849_DK", "30_307599_516865_DK", "30_307632_471865_DK", "30_307665_471866_DK", "30_307698_471868_DK", "30_307731_477235_DK", "7_307738_914687_DK", "7_307746_914688_DK", "7_307767_914689_DK", "7_307817_914690_DK", "7_307867_914691_DK", "7_307917_914692_DK", "7_307967_914693_DK", "7_308017_914703_DK", "7_308037_914756_DK", "7_308042_880752_DK", "7_308042_923313_DK", "7_308047_914757_DK", "7_308097_914758_DK", "7_308147_914759_DK", "7_308197_914760_DK", "7_308247_914761_DK", "7_308297_914762_DK", "7_308347_914763_DK", "7_308397_914764_DK", "7_308447_914765_DK", "7_308497_914766_DK", "7_308547_914767_DK", "7_308597_914768_DK", "7_308647_914769_DK", "7_308697_914770_DK", "7_308721_914771_DK", "7_308741_914772_DK", "7_308750_914773_DK", "30_308758_517002_DK", "30_308803_521699_DK", "30_308883_517003_DK", "30_308927_474704_DK", "30_308960_517004_DK", "30_308993_547172_DK", "30_309026_538108_DK", "30_309059_517005_DK", "30_309091_520282_DK", "30_309124_517007_DK", "30_309157_517008_DK", "30_309190_517010_DK", "30_309223_528068_DK", "30_309255_517011_DK", "30_309288_517012_DK", "30_309321_517013_DK", "14_309328_914718_DK", "14_309337_914719_DK", "14_309357_914720_DK", "14_309407_914721_DK", "14_309457_914722_DK", "14_309507_914723_DK", "14_309557_914724_DK", "14_309607_914725_DK", "14_309630_914726_DK", "14_309672_914727_DK", "14_309677_918492_DK", "14_309677_845161_DK", "14_309682_914729_DK", "14_381115_914730_DK", "14_381165_914732_DK", "14_381215_914733_DK", "14_381282_914734_DK", "14_381308_914736_DK", "30_381313_845180_DK", "14_381318_914737_DK", "14_381400_906126_DK", "14_381450_906127_DK", "14_381500_906128_DK", "14_381550_906129_DK", "14_381600_914514_DK", "14_381650_914515_DK", "14_381700_914516_DK", "14_381738_914517_DK", "14_381758_914518_DK", "14_381767_914519_DK", "30_381773_520449_DK", "30_381806_520450_DK", "30_381838_541308_DK", "30_381872_550469_DK", "30_381904_765317_DK", "30_381937_765319_DK", "30_381970_765320_DK", "30_382003_794927_DK", "30_382030_765327_DK"], + "beforeDay": ["6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "86.66666666666667", "86.66666666666667", "86.66666666666667", "86.66666666666667", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "85.71428571428571", "71.42857142857143", "71.42857142857143", "71.42857142857143", "85.71428571428571", "71.42857142857143", "71.42857142857143", "71.42857142857143", "6.666666666666667", "6.666666666666667", "6.666666666666667", "85.71428571428571", "6.666666666666667", "6.666666666666667", "6.666666666666667", "0", "64.28571428571429", "64.28571428571429", "0", "0", "0", "0", "0", "0", "0", "0", "0", "85.71428571428571", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "85.71428571428571", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "85.71428571428571", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "14.285714285714285", "0", "76.66666666666667", "0", "0", "0", "0", "0", "0", "0", "0", "0", "42.857142857142854", "42.857142857142854", "42.857142857142854", "0", "42.857142857142854", "42.857142857142854", "42.857142857142854", "42.857142857142854", "42.857142857142854", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "90.0", "90.0", "90.0", "90.0", "90.0", "90.0", "90.0", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "85.71428571428571", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "0", "0", "0", "0", "0", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "3.3333333333333335", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "96.66666666666667", "71.42857142857143", "64.28571428571429", "64.28571428571429", "64.28571428571429", "64.28571428571429", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667", "6.666666666666667"], + "nextDay": ["93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "3.3333333333333335", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "13.333333333333334", "13.333333333333334", "13.333333333333334", "13.333333333333334", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "14.285714285714285", "28.57142857142857", "28.57142857142857", "28.57142857142857", "14.285714285714285", "28.57142857142857", "28.57142857142857", "28.57142857142857", "93.33333333333333", "93.33333333333333", "93.33333333333333", "14.285714285714285", "93.33333333333333", "93.33333333333333", "93.33333333333333", "100.0001", "35.714285714285715", "35.714285714285715", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "14.285714285714285", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "14.285714285714285", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "14.285714285714285", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "85.71428571428571", "100.0001", "23.333333333333332", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "57.14285714285714", "57.14285714285714", "57.14285714285714", "100.0001", "57.14285714285714", "57.14285714285714", "57.14285714285714", "57.14285714285714", "57.14285714285714", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "10.0", "10.0", "10.0", "10.0", "10.0", "10.0", "10.0", "96.66666666666667", "96.66666666666667", "96.66666666666667", "96.66666666666667", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "14.285714285714285", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "100.0001", "100.0001", "100.0001", "100.0001", "100.0001", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "96.66666666666667", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "7.142857142857142", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "28.57142857142857", "3.3333333333333335", "28.57142857142857", "35.714285714285715", "35.714285714285715", "35.714285714285715", "35.714285714285715", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "71.42857142857143", "96.66666666666667", "96.66666666666667", "96.66666666666667", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333", "93.33333333333333"], + "frequency": ["-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.2", "-21.2", "-21.2", "-21.2", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.1", "-21.3", "-21.3", "-21.3", "-21.1", "-21.3", "-21.3", "-21.3", "-21.2", "-21.2", "-21.2", "-21.1", "-21.2", "-21.2", "-21.2", "-21.1", "-21.3", "-21.3", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.0", "-21.0", "-21.0", "-21.0", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.0", "-21.0", "-21.0", "-21.2", "-21.2", "-21.2", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.3", "-21.4", "-21.3", "-21.3", "-21.3", "-21.3", "-21.3", "-21.1", "-21.3", "-21.3", "-21.3", "-21.1", "-21.1", "-21.1", "-21.3", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.0", "-21.1", "-21.1", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.1", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-21.2", "-20.8", "-21.2", "-21.2", "-21.2", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.3", "-21.3", "-21.3", "-21.3", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1", "-21.0", "-21.1", "-21.1", "-21.1", "-21.1", "-21.1"] + } +} \ No newline at end of file diff --git a/test/data/settlement.xlsx b/test/data/settlement.xlsx new file mode 100644 index 0000000..bdc4767 Binary files /dev/null and b/test/data/settlement.xlsx differ diff --git a/test/data/url.config b/test/data/url.config new file mode 100644 index 0000000..045ac05 --- /dev/null +++ b/test/data/url.config @@ -0,0 +1,3506 @@ +key https://apps.r93535.com/cjgc/riskbase/left!newleftmenu.action?d=0.7457344606838715 +value + +key https://apps.r93535.com/cjgc//riskcontrol/subsidence/statisticsindex!hightchartCjlAndJianDu.action?d=0.6372218026465649&idType=0&id=11364&isgdd=0 +value { + "mcljChartData": { + "dkilo": [ + "513626_788200_D2K", + "513627_788233_D2K", + "513628_788265_D2K", + "513629_788298_D2K", + "513630_788331_D2K", + "513631_788364_D2K", + "513632_788396_D2K", + "513633_788429_D2K", + "513634_788462_D2K", + "513635_788486_D2K", + "513636_788511_D2K", + "513637_788544_D2K", + "513638_788576_D2K", + "513639_788609_D2K", + "513640_788642_D2K", + "513641_788675_D2K", + "513642_788707_D2K", + "513643_788740_D2K", + "513644_788773_D2K", + "513645_788805_D2K", + "513646_788838_D2K", + "513647_788871_D2K", + "513648_788904_D2K", + "513649_788936_D2K", + "513650_788969_D2K", + "513651_789002_D2K", + "513652_789035_D2K", + "513653_789067_D2K", + "513654_789100_D2K", + "513655_789133_D2K", + "513656_789165_D2K", + "513657_789198_D2K", + "513658_789231_D2K", + "513659_789264_D2K", + "513660_789296_D2K", + "513661_789329_D2K", + "513662_789362_D2K", + "513663_789394_D2K", + "513664_789427_D2K", + "513665_789460_D2K", + "513666_789493_D2K", + "513667_789525_D2K", + "513668_789558_D2K", + "513669_789591_D2K", + "513670_789623_D2K", + "513671_789656_D2K", + "513672_789689_D2K", + "513673_789721_D2K", + "513674_789754_D2K", + "513675_789787_D2K", + "513676_789820_D2K", + "513677_789852_D2K", + "513678_789885_D2K", + "513679_789918_D2K", + "513680_789951_D2K", + "513681_789983_D2K", + "513682_790016_D2K", + "907789_790023_D2K", + "907790_790032_D2K", + "907791_790052_D2K", + "907792_790090_D2K", + "907793_790130_D2K", + "907794_790170_D2K", + "907795_790210_D2K", + "907796_790250_D2K", + "907797_790270_D2K", + "907798_790279_D2K", + "513683_790285_D2K", + "513684_790318_D2K", + "513685_790351_D2K", + "513686_790384_D2K", + "641445_790417_D2K", + "513688_790449_D2K", + "513689_790482_D2K", + "513690_790515_D2K", + "513691_790548_D2K", + "513692_790581_D2K", + "513693_790613_D2K", + "513694_790646_D2K", + "513695_790679_D2K", + "513696_790711_D2K", + "513697_790744_D2K", + "513698_790777_D2K", + "513699_790810_D2K", + "513700_790842_D2K", + "513701_790875_D2K", + "513702_790908_D2K", + "513703_790940_D2K", + "513704_790973_D2K", + "513705_791006_D2K", + "513706_791038_D2K", + "513707_791071_D2K", + "513708_791104_D2K", + "513709_791137_D2K", + "513710_791169_D2K", + "513711_791202_D2K", + "513712_791235_D2K", + "513713_791267_D2K", + "513714_791300_D2K", + "513715_791333_D2K", + "513716_791365_D2K", + "513717_791398_D2K", + "513718_791431_D2K", + "513719_791464_D2K", + "513720_791496_D2K", + "513721_791529_D2K", + "513722_791562_D2K", + "513723_791594_D2K", + "513724_791627_D2K", + "513725_791660_D2K", + "513726_791693_D2K", + "513727_791725_D2K", + "513728_791758_D2K", + "513729_791791_D2K", + "513730_791823_D2K", + "513731_791856_D2K", + "513732_791889_D2K", + "620544_791921_D2K", + "620545_791954_D2K", + "513735_791987_D2K", + "513736_792020_D2K", + "620546_792052_D2K", + "620547_792085_D2K", + "513739_792118_D2K", + "513740_792150_D2K", + "513741_792183_D2K", + "513742_792216_D2K", + "513743_792249_D2K", + "513744_792281_D2K", + "513745_792314_D2K", + "513746_792347_D2K", + "513747_792379_D2K", + "513748_792412_D2K", + "513749_792445_D2K", + "513750_792477_D2K", + "513751_792510_D2K", + "513752_792543_D2K", + "513753_792576_D2K", + "513754_792608_D2K", + "513755_792641_D2K", + "513756_792674_D2K", + "513757_792706_D2K", + "513758_792739_D2K", + "513759_792772_D2K", + "513760_792804_D2K", + "513761_792837_D2K", + "513762_792870_D2K", + "513763_792903_D2K", + "513764_792935_D2K", + "513765_792968_D2K", + "513766_793001_D2K", + "513767_793034_D2K", + "513768_793066_D2K", + "513769_793099_D2K", + "513770_793132_D2K", + "513771_793165_D2K", + "513772_793197_D2K", + "513773_793230_D2K", + "513774_793263_D2K", + "513775_793295_D2K", + "513776_793328_D2K", + "513777_793361_D2K", + "513778_793394_D2K", + "513779_793426_D2K", + "513780_793459_D2K", + "513781_793492_D2K", + "513782_793525_D2K", + "513783_793557_D2K", + "513784_793590_D2K", + "513785_793623_D2K", + "513786_793656_D2K", + "513787_793688_D2K", + "513788_793721_D2K", + "513789_793754_D2K", + "513790_793787_D2K", + "513791_793819_D2K", + "513792_793852_D2K", + "513793_793885_D2K", + "513794_793917_D2K", + "513795_793950_D2K", + "513796_793983_D2K", + "513797_794016_D2K", + "513798_794048_D2K", + "513799_794081_D2K", + "513800_794114_D2K", + "513801_794147_D2K", + "585042_794179_D2K", + "513803_794212_D2K", + "513804_794245_D2K", + "513805_794277_D2K", + "513806_794310_D2K", + "513807_794343_D2K", + "513808_794376_D2K", + "513809_794408_D2K", + "513810_794441_D2K", + "513811_794474_D2K", + "513812_794506_D2K", + "513813_794539_D2K", + "513814_794572_D2K", + "513815_794605_D2K", + "513816_794637_D2K", + "513817_794670_D2K", + "513818_794703_D2K", + "513819_794736_D2K", + "513820_794769_D2K", + "513821_794801_D2K", + "513822_794834_D2K", + "513823_794867_D2K", + "513824_794900_D2K", + "513825_794933_D2K", + "513826_794965_D2K", + "513827_794998_D2K", + "513828_795031_D2K", + "513829_795064_D2K", + "513830_795097_D2K", + "513831_795129_D2K", + "513832_795162_D2K", + "513833_795195_D2K", + "513834_795228_D2K", + "513835_795261_D2K", + "513836_795293_D2K", + "513837_795326_D2K", + "915843_795333_D2K", + "906118_795340_D2K", + "915844_795347_D2K", + "915845_795354_D2K", + "915847_795361_D2K", + "915849_795368_D2K", + "915851_795375_D2K", + "915852_795382_D2K", + "906119_795389_D2K", + "915853_795396_D2K", + "915854_795403_D2K", + "513838_795412_D2K", + "513839_795445_D2K", + "513840_795477_D2K", + "513841_795510_D2K", + "513842_795543_D2K", + "513843_795576_D2K", + "513844_795609_D2K", + "513845_795641_D2K", + "513846_795674_D2K", + "513847_795707_D2K", + "513848_795740_D2K", + "513849_795773_D2K", + "513850_795805_D2K", + "513851_795838_D2K", + "513852_795871_D2K", + "513853_795904_D2K", + "513854_795936_D2K", + "513855_795969_D2K", + "513856_796002_D2K", + "513857_796035_D2K", + "513858_796067_D2K", + "513859_796100_D2K", + "513860_796133_D2K", + "513861_796165_D2K", + "513862_796198_D2K", + "513863_796231_D2K", + "513864_796264_D2K", + "513865_796296_D2K", + "513866_796329_D2K", + "513867_796362_D2K", + "513868_796394_D2K", + "513869_796427_D2K", + "513870_796460_D2K", + "513871_796493_D2K", + "513872_796525_D2K", + "513873_796558_D2K", + "513874_796591_D2K", + "513875_796624_D2K", + "513876_796656_D2K", + "513877_796689_D2K", + "513878_796722_D2K", + "513879_796754_D2K", + "513880_796787_D2K", + "905676_796794_D2K", + "905677_796803_D2K", + "905678_796823_D2K", + "905680_796843_D2K", + "905682_796863_D2K", + "905683_796883_D2K", + "905684_796914_D2K", + "905686_796934_D2K", + "905687_796943_D2K", + "513881_796950_D2K", + "513882_796974_D2K", + "513883_797007_D2K", + "513884_797040_D2K", + "513885_797073_D2K", + "513886_797105_D2K", + "513887_797138_D2K", + "513888_797171_D2K", + "513889_797203_D2K", + "513890_797236_D2K", + "513891_797269_D2K", + "513892_797302_D2K", + "513893_797334_D2K", + "513894_797367_D2K", + "513895_797400_D2K", + "513896_797432_D2K", + "513897_797465_D2K", + "513898_797498_D2K", + "513899_797531_D2K", + "513900_797563_D2K", + "513901_797596_D2K", + "513902_797629_D2K", + "513903_797661_D2K", + "513904_797694_D2K", + "513905_797727_D2K", + "513906_797760_D2K", + "513907_797793_D2K", + "513908_797825_D2K", + "513909_797858_D2K", + "513910_797891_D2K", + "513911_797924_D2K", + "513912_797956_D2K", + "513913_797989_D2K", + "513914_798022_D2K", + "513915_798055_D2K", + "513916_798088_D2K", + "513917_798120_D2K", + "513918_798153_D2K", + "513919_798186_D2K", + "513920_798219_D2K", + "513921_798251_D2K", + "513922_798284_D2K", + "513923_798317_D2K", + "513924_798350_D2K", + "513925_798382_D2K", + "513926_798415_D2K", + "513927_798448_D2K", + "513928_798481_D2K", + "513929_798513_D2K", + "513930_798546_D2K", + "513931_798579_D2K", + "513932_798612_D2K", + "513933_798644_D2K", + "513934_798677_D2K", + "513935_798710_D2K", + "513936_798742_D2K", + "513937_798775_D2K", + "513938_798808_D2K", + "513939_798840_D2K", + "513940_798873_D2K", + "513941_798906_D2K", + "513942_798939_D2K" + ], + "cjl": [ + { + "value": "-1.52", + "color": "#8000FF" + }, + { + "value": "-0.03", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "0.79", + "color": "#8000FF" + }, + { + "value": "-0.61", + "color": "#8000FF" + }, + { + "value": "-1.86", + "color": "#8000FF" + }, + { + "value": "0.15", + "color": "#8000FF" + }, + { + "value": "-1.99", + "color": "#8000FF" + }, + { + "value": "-0.51", + "color": "#8000FF" + }, + { + "value": "-0.67", + "color": "#8000FF" + }, + { + "value": "-2.04", + "color": "#8000FF" + }, + { + "value": "0.07", + "color": "#8000FF" + }, + { + "value": "-1.01", + "color": "#8000FF" + }, + { + "value": "-1.85", + "color": "#8000FF" + }, + { + "value": "-2.23", + "color": "#8000FF" + }, + { + "value": "-1.96", + "color": "#8000FF" + }, + { + "value": "-2.34", + "color": "#8000FF" + }, + { + "value": "-1.45", + "color": "#8000FF" + }, + { + "value": "-1.73", + "color": "#8000FF" + }, + { + "value": "-0.57", + "color": "#8000FF" + }, + { + "value": "-0.47", + "color": "#8000FF" + }, + { + "value": "-1.92", + "color": "#8000FF" + }, + { + "value": "-0.39", + "color": "#8000FF" + }, + { + "value": "-1.53", + "color": "#8000FF" + }, + { + "value": "-1.67", + "color": "#8000FF" + }, + { + "value": "-3", + "color": "#8000FF" + }, + { + "value": "-0.44", + "color": "#8000FF" + }, + { + "value": "-2.69", + "color": "#8000FF" + }, + { + "value": "-1", + "color": "#8000FF" + }, + { + "value": "-0.97", + "color": "#8000FF" + }, + { + "value": "-1.98", + "color": "#8000FF" + }, + { + "value": "-0.51", + "color": "#8000FF" + }, + { + "value": "-0.32", + "color": "#8000FF" + }, + { + "value": "-1.46", + "color": "#8000FF" + }, + { + "value": "-0.79", + "color": "#8000FF" + }, + { + "value": "0.43", + "color": "#8000FF" + }, + { + "value": "-0.69", + "color": "#8000FF" + }, + { + "value": "-0.43", + "color": "#8000FF" + }, + { + "value": "-0.88", + "color": "#8000FF" + }, + { + "value": "-2.07", + "color": "#8000FF" + }, + { + "value": "-1.55", + "color": "#8000FF" + }, + { + "value": "-1.51", + "color": "#8000FF" + }, + { + "value": "1.45", + "color": "#8000FF" + }, + { + "value": "0.04", + "color": "#8000FF" + }, + { + "value": "0.09", + "color": "#8000FF" + }, + { + "value": "-0.57", + "color": "#8000FF" + }, + { + "value": "1.25", + "color": "#8000FF" + }, + { + "value": "0.04", + "color": "#8000FF" + }, + { + "value": "2.06", + "color": "#8000FF" + }, + { + "value": "0.46", + "color": "#8000FF" + }, + { + "value": "-1.25", + "color": "#8000FF" + }, + { + "value": "-0.98", + "color": "#8000FF" + }, + { + "value": "-0.3", + "color": "#8000FF" + }, + { + "value": "-0.02", + "color": "#8000FF" + }, + { + "value": "0.56", + "color": "#8000FF" + }, + { + "value": "-1.35", + "color": "#8000FF" + }, + { + "value": "-0.69", + "color": "#8000FF" + }, + { + "value": "0.25", + "color": "#214080" + }, + { + "value": "0.5", + "color": "#214080" + }, + { + "value": "0.98", + "color": "#214080" + }, + { + "value": "0.83", + "color": "#214080" + }, + { + "value": "0.67", + "color": "#214080" + }, + { + "value": "1.2", + "color": "#214080" + }, + { + "value": "-0.66", + "color": "#214080" + }, + { + "value": "-1.78", + "color": "#214080" + }, + { + "value": "-0.99", + "color": "#214080" + }, + { + "value": "-1.43", + "color": "#214080" + }, + { + "value": "-0.79", + "color": "#8000FF" + }, + { + "value": "-1.38", + "color": "#8000FF" + }, + { + "value": "-1.33", + "color": "#8000FF" + }, + { + "value": "-2.09", + "color": "#8000FF" + }, + { + "value": "0.45", + "color": "#8000FF" + }, + { + "value": "0.96", + "color": "#8000FF" + }, + { + "value": "-0.05", + "color": "#8000FF" + }, + { + "value": "-3.11", + "color": "#8000FF" + }, + { + "value": "-3.76", + "color": "#8000FF" + }, + { + "value": "-1.79", + "color": "#8000FF" + }, + { + "value": "-4.3", + "color": "#8000FF" + }, + { + "value": "-3.87", + "color": "#8000FF" + }, + { + "value": "-4.06", + "color": "#8000FF" + }, + { + "value": "-2.34", + "color": "#8000FF" + }, + { + "value": "-3.41", + "color": "#8000FF" + }, + { + "value": "-3.06", + "color": "#8000FF" + }, + { + "value": "-2", + "color": "#8000FF" + }, + { + "value": "0.55", + "color": "#8000FF" + }, + { + "value": "-4.08", + "color": "#8000FF" + }, + { + "value": "-0.14", + "color": "#8000FF" + }, + { + "value": "-4.13", + "color": "#8000FF" + }, + { + "value": "-0.38", + "color": "#8000FF" + }, + { + "value": "0.06", + "color": "#8000FF" + }, + { + "value": "0.37", + "color": "#8000FF" + }, + { + "value": "0.08", + "color": "#8000FF" + }, + { + "value": "-0.37", + "color": "#8000FF" + }, + { + "value": "-0.37", + "color": "#8000FF" + }, + { + "value": "-0.2", + "color": "#8000FF" + }, + { + "value": "-1.61", + "color": "#8000FF" + }, + { + "value": "-1.59", + "color": "#8000FF" + }, + { + "value": "-0.53", + "color": "#8000FF" + }, + { + "value": "0.53", + "color": "#8000FF" + }, + { + "value": "1.22", + "color": "#8000FF" + }, + { + "value": "0.01", + "color": "#8000FF" + }, + { + "value": "-2.28", + "color": "#8000FF" + }, + { + "value": "-1.05", + "color": "#8000FF" + }, + { + "value": "-2.45", + "color": "#8000FF" + }, + { + "value": "-1.24", + "color": "#8000FF" + }, + { + "value": "-1.27", + "color": "#8000FF" + }, + { + "value": "-3.08", + "color": "#8000FF" + }, + { + "value": "-2.38", + "color": "#8000FF" + }, + { + "value": "-2.4", + "color": "#8000FF" + }, + { + "value": "-2.15", + "color": "#8000FF" + }, + { + "value": "-2.15", + "color": "#8000FF" + }, + { + "value": "-2.04", + "color": "#8000FF" + }, + { + "value": "-2.41", + "color": "#8000FF" + }, + { + "value": "-1.51", + "color": "#8000FF" + }, + { + "value": "-2.6", + "color": "#8000FF" + }, + { + "value": "1.3", + "color": "#8000FF" + }, + { + "value": "-0.62", + "color": "#8000FF" + }, + { + "value": "0.24", + "color": "#8000FF" + }, + { + "value": "-1.67", + "color": "#8000FF" + }, + { + "value": "-0.95", + "color": "#8000FF" + }, + { + "value": "-1.97", + "color": "#8000FF" + }, + { + "value": "-0.12", + "color": "#8000FF" + }, + { + "value": "-2.44", + "color": "#8000FF" + }, + { + "value": "-1.76", + "color": "#8000FF" + }, + { + "value": "-0.62", + "color": "#8000FF" + }, + { + "value": "-0.39", + "color": "#8000FF" + }, + { + "value": "-1", + "color": "#8000FF" + }, + { + "value": "-1.21", + "color": "#8000FF" + }, + { + "value": "-0.3", + "color": "#8000FF" + }, + { + "value": "-0.87", + "color": "#8000FF" + }, + { + "value": "-1.53", + "color": "#8000FF" + }, + { + "value": "-1.74", + "color": "#8000FF" + }, + { + "value": "-0.68", + "color": "#8000FF" + }, + { + "value": "-1.4", + "color": "#8000FF" + }, + { + "value": "-0.46", + "color": "#8000FF" + }, + { + "value": "0.1", + "color": "#8000FF" + }, + { + "value": "0.16", + "color": "#8000FF" + }, + { + "value": "-0.73", + "color": "#8000FF" + }, + { + "value": "-1.4", + "color": "#8000FF" + }, + { + "value": "-1.77", + "color": "#8000FF" + }, + { + "value": "-0.89", + "color": "#8000FF" + }, + { + "value": "-1.37", + "color": "#8000FF" + }, + { + "value": "0.34", + "color": "#8000FF" + }, + { + "value": "0.51", + "color": "#8000FF" + }, + { + "value": "-1.16", + "color": "#8000FF" + }, + { + "value": "-1.54", + "color": "#8000FF" + }, + { + "value": "-3.36", + "color": "#8000FF" + }, + { + "value": "-2.34", + "color": "#8000FF" + }, + { + "value": "-1.23", + "color": "#8000FF" + }, + { + "value": "-2.68", + "color": "#8000FF" + }, + { + "value": "-1.91", + "color": "#8000FF" + }, + { + "value": "-3.06", + "color": "#8000FF" + }, + { + "value": "-2.24", + "color": "#8000FF" + }, + { + "value": "-2.21", + "color": "#8000FF" + }, + { + "value": "-0.2", + "color": "#8000FF" + }, + { + "value": "-2.29", + "color": "#8000FF" + }, + { + "value": "-2.63", + "color": "#8000FF" + }, + { + "value": "-2.57", + "color": "#8000FF" + }, + { + "value": "-2.15", + "color": "#8000FF" + }, + { + "value": "-1.37", + "color": "#8000FF" + }, + { + "value": "-2.12", + "color": "#8000FF" + }, + { + "value": "-0.67", + "color": "#8000FF" + }, + { + "value": "-1.66", + "color": "#8000FF" + }, + { + "value": "-0.77", + "color": "#8000FF" + }, + { + "value": "-0.96", + "color": "#8000FF" + }, + { + "value": "-1.72", + "color": "#8000FF" + }, + { + "value": "-0.51", + "color": "#8000FF" + }, + { + "value": "-2.29", + "color": "#8000FF" + }, + { + "value": "-2.82", + "color": "#8000FF" + }, + { + "value": "-1.61", + "color": "#8000FF" + }, + { + "value": "-1.19", + "color": "#8000FF" + }, + { + "value": "-0.68", + "color": "#8000FF" + }, + { + "value": "1.35", + "color": "#8000FF" + }, + { + "value": "-1.97", + "color": "#8000FF" + }, + { + "value": "-0.51", + "color": "#8000FF" + }, + { + "value": "0.34", + "color": "#8000FF" + }, + { + "value": "-0.08", + "color": "#8000FF" + }, + { + "value": "2.18", + "color": "#8000FF" + }, + { + "value": "-0.08", + "color": "#8000FF" + }, + { + "value": "-0.78", + "color": "#8000FF" + }, + { + "value": "-1.15", + "color": "#8000FF" + }, + { + "value": "0.04", + "color": "#8000FF" + }, + { + "value": "-0.62", + "color": "#8000FF" + }, + { + "value": "0.55", + "color": "#8000FF" + }, + { + "value": "0.42", + "color": "#8000FF" + }, + { + "value": "0.05", + "color": "#8000FF" + }, + { + "value": "-1.15", + "color": "#8000FF" + }, + { + "value": "-1.71", + "color": "#8000FF" + }, + { + "value": "0.15", + "color": "#8000FF" + }, + { + "value": "-0.62", + "color": "#8000FF" + }, + { + "value": "0.8", + "color": "#8000FF" + }, + { + "value": "-1.78", + "color": "#8000FF" + }, + { + "value": "-1.85", + "color": "#8000FF" + }, + { + "value": "-1.39", + "color": "#8000FF" + }, + { + "value": "-1.49", + "color": "#8000FF" + }, + { + "value": "-0.75", + "color": "#8000FF" + }, + { + "value": "-1.53", + "color": "#8000FF" + }, + { + "value": "-0.66", + "color": "#8000FF" + }, + { + "value": "-1.11", + "color": "#8000FF" + }, + { + "value": "-1.91", + "color": "#8000FF" + }, + { + "value": "-1.91", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "-0.09", + "color": "#8000FF" + }, + { + "value": "-0.55", + "color": "#8000FF" + }, + { + "value": "-0.64", + "color": "#8000FF" + }, + { + "value": "-0.48", + "color": "#8000FF" + }, + { + "value": "-1.21", + "color": "#8000FF" + }, + { + "value": "-1.43", + "color": "#8000FF" + }, + { + "value": "-1.17", + "color": "#8000FF" + }, + { + "value": "-3.8", + "color": "#8000FF" + }, + { + "value": "-3.94", + "color": "#8000FF" + }, + { + "value": "-5.82", + "color": "#8000FF" + }, + { + "value": "-1.65", + "color": "#8000FF" + }, + { + "value": "-1.12", + "color": "#8000FF" + }, + { + "value": "-2.84", + "color": "#8000FF" + }, + { + "value": "-2.49", + "color": "#8000FF" + }, + { + "value": "-1.89", + "color": "#8000FF" + }, + { + "value": "2.03", + "color": "#8000FF" + }, + { + "value": "-0.02", + "color": "#8000FF" + }, + { + "value": "0.9", + "color": "#8000FF" + }, + { + "value": "0.27", + "color": "#8000FF" + }, + { + "value": "-3.01", + "color": "#8000FF" + }, + { + "value": "-0.36", + "color": "#214080" + }, + { + "value": "-2.15", + "color": "#214080" + }, + { + "value": "-0.85", + "color": "#214080" + }, + { + "value": "-0.92", + "color": "#214080" + }, + { + "value": "-0.89", + "color": "#214080" + }, + { + "value": "-0.6", + "color": "#214080" + }, + { + "value": "-0.7", + "color": "#214080" + }, + { + "value": "-1.18", + "color": "#214080" + }, + { + "value": "-1.32", + "color": "#214080" + }, + { + "value": "0.07", + "color": "#214080" + }, + { + "value": "-1.18", + "color": "#214080" + }, + { + "value": "-0.14", + "color": "#8000FF" + }, + { + "value": "-2.09", + "color": "#8000FF" + }, + { + "value": "0.38", + "color": "#8000FF" + }, + { + "value": "-1.25", + "color": "#8000FF" + }, + { + "value": "-1.91", + "color": "#8000FF" + }, + { + "value": "-1.68", + "color": "#8000FF" + }, + { + "value": "-0.81", + "color": "#8000FF" + }, + { + "value": "-2.04", + "color": "#8000FF" + }, + { + "value": "-0.62", + "color": "#8000FF" + }, + { + "value": "-2.51", + "color": "#8000FF" + }, + { + "value": "-1.67", + "color": "#8000FF" + }, + { + "value": "-0.67", + "color": "#8000FF" + }, + { + "value": "-0.3", + "color": "#8000FF" + }, + { + "value": "0.07", + "color": "#8000FF" + }, + { + "value": "-1.54", + "color": "#8000FF" + }, + { + "value": "-2.13", + "color": "#8000FF" + }, + { + "value": "-2.44", + "color": "#8000FF" + }, + { + "value": "-0.02", + "color": "#8000FF" + }, + { + "value": "-1.72", + "color": "#8000FF" + }, + { + "value": "-3.5", + "color": "#8000FF" + }, + { + "value": "-0.88", + "color": "#8000FF" + }, + { + "value": "-2.72", + "color": "#8000FF" + }, + { + "value": "-2.07", + "color": "#8000FF" + }, + { + "value": "-1.06", + "color": "#8000FF" + }, + { + "value": "0.38", + "color": "#8000FF" + }, + { + "value": "0.01", + "color": "#8000FF" + }, + { + "value": "-0.74", + "color": "#8000FF" + }, + { + "value": "-1.31", + "color": "#8000FF" + }, + { + "value": "-0.81", + "color": "#8000FF" + }, + { + "value": "-4.2", + "color": "#8000FF" + }, + { + "value": "-1.34", + "color": "#8000FF" + }, + { + "value": "0.11", + "color": "#8000FF" + }, + { + "value": "-1.14", + "color": "#8000FF" + }, + { + "value": "0.4", + "color": "#8000FF" + }, + { + "value": "-0.57", + "color": "#8000FF" + }, + { + "value": "0.76", + "color": "#8000FF" + }, + { + "value": "-1.88", + "color": "#8000FF" + }, + { + "value": "0.19", + "color": "#8000FF" + }, + { + "value": "-0.71", + "color": "#8000FF" + }, + { + "value": "0.79", + "color": "#8000FF" + }, + { + "value": "-0.83", + "color": "#8000FF" + }, + { + "value": "-0.56", + "color": "#8000FF" + }, + { + "value": "-2.77", + "color": "#8000FF" + }, + { + "value": "-0.58", + "color": "#214080" + }, + { + "value": "-0.47", + "color": "#214080" + }, + { + "value": "0.15", + "color": "#214080" + }, + { + "value": "0.68", + "color": "#214080" + }, + { + "value": "-0.74", + "color": "#214080" + }, + { + "value": "-1.01", + "color": "#214080" + }, + { + "value": "-1.42", + "color": "#214080" + }, + { + "value": "-2.84", + "color": "#214080" + }, + { + "value": "0.47", + "color": "#214080" + }, + { + "value": "-0.78", + "color": "#8000FF" + }, + { + "value": "-1.16", + "color": "#8000FF" + }, + { + "value": "-0.19", + "color": "#8000FF" + }, + { + "value": "0.01", + "color": "#8000FF" + }, + { + "value": "-0.18", + "color": "#8000FF" + }, + { + "value": "-0.07", + "color": "#8000FF" + }, + { + "value": "-1.35", + "color": "#8000FF" + }, + { + "value": "-1.58", + "color": "#8000FF" + }, + { + "value": "-0.93", + "color": "#8000FF" + }, + { + "value": "-0.44", + "color": "#8000FF" + }, + { + "value": "-1.67", + "color": "#8000FF" + }, + { + "value": "-1.78", + "color": "#8000FF" + }, + { + "value": "0.09", + "color": "#8000FF" + }, + { + "value": "0.05", + "color": "#8000FF" + }, + { + "value": "0.12", + "color": "#8000FF" + }, + { + "value": "0.38", + "color": "#8000FF" + }, + { + "value": "1.85", + "color": "#8000FF" + }, + { + "value": "0.91", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "-1.05", + "color": "#8000FF" + }, + { + "value": "0.29", + "color": "#8000FF" + }, + { + "value": "0.43", + "color": "#8000FF" + }, + { + "value": "-0.87", + "color": "#8000FF" + }, + { + "value": "-1.72", + "color": "#8000FF" + }, + { + "value": "-1.82", + "color": "#8000FF" + }, + { + "value": "-2.04", + "color": "#8000FF" + }, + { + "value": "-1.09", + "color": "#8000FF" + }, + { + "value": "-1.93", + "color": "#8000FF" + }, + { + "value": "-2.38", + "color": "#8000FF" + }, + { + "value": "-0.64", + "color": "#8000FF" + }, + { + "value": "-0.66", + "color": "#8000FF" + }, + { + "value": "-1.31", + "color": "#8000FF" + }, + { + "value": "0.19", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "0.65", + "color": "#8000FF" + }, + { + "value": "-0.01", + "color": "#8000FF" + }, + { + "value": "0.16", + "color": "#8000FF" + }, + { + "value": "-0.18", + "color": "#8000FF" + }, + { + "value": "-0.21", + "color": "#8000FF" + }, + { + "value": "-0.02", + "color": "#8000FF" + }, + { + "value": "-0.04", + "color": "#8000FF" + }, + { + "value": "-0.13", + "color": "#8000FF" + }, + { + "value": "0.1", + "color": "#8000FF" + }, + { + "value": "-0.08", + "color": "#8000FF" + }, + { + "value": "-0.07", + "color": "#8000FF" + }, + { + "value": "1.57", + "color": "#8000FF" + }, + { + "value": "-2.62", + "color": "#8000FF" + }, + { + "value": "-2.37", + "color": "#8000FF" + }, + { + "value": "0.14", + "color": "#8000FF" + }, + { + "value": "-1.76", + "color": "#8000FF" + }, + { + "value": "-0.1", + "color": "#8000FF" + }, + { + "value": "-1.45", + "color": "#8000FF" + }, + { + "value": "-0.13", + "color": "#8000FF" + }, + { + "value": "-0.76", + "color": "#8000FF" + }, + { + "value": "-1.08", + "color": "#8000FF" + }, + { + "value": "-0.96", + "color": "#8000FF" + }, + { + "value": "-0.32", + "color": "#8000FF" + }, + { + "value": "-0.17", + "color": "#8000FF" + }, + { + "value": "-0.11", + "color": "#8000FF" + }, + { + "value": "-1.67", + "color": "#8000FF" + }, + { + "value": "-0", + "color": "#8000FF" + } + ], + "sjcjl": [ + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -15, + -15, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20, + -20 + ] + }, + "cljdChartData": { + "workinfo_kilo": [ + "30_788200_513626_D2K", + "30_788233_513627_D2K", + "30_788265_513628_D2K", + "30_788298_513629_D2K", + "30_788331_513630_D2K", + "30_788364_513631_D2K", + "30_788396_513632_D2K", + "30_788429_513633_D2K", + "30_788462_513634_D2K", + "30_788486_513635_D2K", + "30_788511_513636_D2K", + "30_788544_513637_D2K", + "30_788576_513638_D2K", + "30_788609_513639_D2K", + "30_788642_513640_D2K", + "30_788675_513641_D2K", + "30_788707_513642_D2K", + "30_788740_513643_D2K", + "30_788773_513644_D2K", + "30_788805_513645_D2K", + "30_788838_513646_D2K", + "30_788871_513647_D2K", + "30_788904_513648_D2K", + "30_788936_513649_D2K", + "30_788969_513650_D2K", + "30_789002_513651_D2K", + "30_789035_513652_D2K", + "30_789067_513653_D2K", + "30_789100_513654_D2K", + "30_789133_513655_D2K", + "30_789165_513656_D2K", + "30_789198_513657_D2K", + "30_789231_513658_D2K", + "30_789264_513659_D2K", + "30_789296_513660_D2K", + "30_789329_513661_D2K", + "30_789362_513662_D2K", + "30_789394_513663_D2K", + "30_789427_513664_D2K", + "30_789460_513665_D2K", + "30_789493_513666_D2K", + "30_789525_513667_D2K", + "30_789558_513668_D2K", + "30_789591_513669_D2K", + "30_789623_513670_D2K", + "30_789656_513671_D2K", + "30_789689_513672_D2K", + "30_789721_513673_D2K", + "30_789754_513674_D2K", + "30_789787_513675_D2K", + "30_789820_513676_D2K", + "30_789852_513677_D2K", + "30_789885_513678_D2K", + "30_789918_513679_D2K", + "30_789951_513680_D2K", + "30_789983_513681_D2K", + "30_790016_513682_D2K", + "7_790023_907789_D2K", + "7_790032_907790_D2K", + "7_790052_907791_D2K", + "7_790090_907792_D2K", + "7_790130_907793_D2K", + "7_790170_907794_D2K", + "7_790210_907795_D2K", + "7_790250_907796_D2K", + "7_790270_907797_D2K", + "7_790279_907798_D2K", + "30_790285_513683_D2K", + "30_790318_513684_D2K", + "30_790351_513685_D2K", + "30_790384_513686_D2K", + "30_790417_641445_D2K", + "30_790449_513688_D2K", + "30_790482_513689_D2K", + "30_790515_513690_D2K", + "30_790548_513691_D2K", + "30_790581_513692_D2K", + "30_790613_513693_D2K", + "30_790646_513694_D2K", + "30_790679_513695_D2K", + "30_790711_513696_D2K", + "30_790744_513697_D2K", + "30_790777_513698_D2K", + "30_790810_513699_D2K", + "30_790842_513700_D2K", + "30_790875_513701_D2K", + "30_790908_513702_D2K", + "30_790940_513703_D2K", + "30_790973_513704_D2K", + "30_791006_513705_D2K", + "30_791038_513706_D2K", + "30_791071_513707_D2K", + "30_791104_513708_D2K", + "30_791137_513709_D2K", + "30_791169_513710_D2K", + "30_791202_513711_D2K", + "30_791235_513712_D2K", + "30_791267_513713_D2K", + "30_791300_513714_D2K", + "30_791333_513715_D2K", + "30_791365_513716_D2K", + "30_791398_513717_D2K", + "30_791431_513718_D2K", + "30_791464_513719_D2K", + "30_791496_513720_D2K", + "30_791529_513721_D2K", + "30_791562_513722_D2K", + "30_791594_513723_D2K", + "30_791627_513724_D2K", + "30_791660_513725_D2K", + "30_791693_513726_D2K", + "30_791725_513727_D2K", + "30_791758_513728_D2K", + "30_791791_513729_D2K", + "30_791823_513730_D2K", + "30_791856_513731_D2K", + "30_791889_513732_D2K", + "30_791921_620544_D2K", + "30_791954_620545_D2K", + "30_791987_513735_D2K", + "30_792020_513736_D2K", + "30_792052_620546_D2K", + "30_792085_620547_D2K", + "30_792118_513739_D2K", + "30_792150_513740_D2K", + "30_792183_513741_D2K", + "30_792216_513742_D2K", + "30_792249_513743_D2K", + "30_792281_513744_D2K", + "30_792314_513745_D2K", + "30_792347_513746_D2K", + "30_792379_513747_D2K", + "30_792412_513748_D2K", + "30_792445_513749_D2K", + "30_792477_513750_D2K", + "30_792510_513751_D2K", + "30_792543_513752_D2K", + "30_792576_513753_D2K", + "30_792608_513754_D2K", + "30_792641_513755_D2K", + "30_792674_513756_D2K", + "30_792706_513757_D2K", + "30_792739_513758_D2K", + "30_792772_513759_D2K", + "30_792804_513760_D2K", + "30_792837_513761_D2K", + "30_792870_513762_D2K", + "30_792903_513763_D2K", + "30_792935_513764_D2K", + "30_792968_513765_D2K", + "30_793001_513766_D2K", + "30_793034_513767_D2K", + "30_793066_513768_D2K", + "30_793099_513769_D2K", + "30_793132_513770_D2K", + "30_793165_513771_D2K", + "30_793197_513772_D2K", + "30_793230_513773_D2K", + "30_793263_513774_D2K", + "30_793295_513775_D2K", + "30_793328_513776_D2K", + "30_793361_513777_D2K", + "30_793394_513778_D2K", + "30_793426_513779_D2K", + "30_793459_513780_D2K", + "30_793492_513781_D2K", + "30_793525_513782_D2K", + "30_793557_513783_D2K", + "30_793590_513784_D2K", + "30_793623_513785_D2K", + "30_793656_513786_D2K", + "30_793688_513787_D2K", + "30_793721_513788_D2K", + "30_793754_513789_D2K", + "30_793787_513790_D2K", + "30_793819_513791_D2K", + "30_793852_513792_D2K", + "30_793885_513793_D2K", + "30_793917_513794_D2K", + "30_793950_513795_D2K", + "30_793983_513796_D2K", + "30_794016_513797_D2K", + "30_794048_513798_D2K", + "30_794081_513799_D2K", + "30_794114_513800_D2K", + "30_794147_513801_D2K", + "30_794179_585042_D2K", + "30_794212_513803_D2K", + "30_794245_513804_D2K", + "30_794277_513805_D2K", + "30_794310_513806_D2K", + "30_794343_513807_D2K", + "30_794376_513808_D2K", + "30_794408_513809_D2K", + "30_794441_513810_D2K", + "30_794474_513811_D2K", + "30_794506_513812_D2K", + "30_794539_513813_D2K", + "30_794572_513814_D2K", + "30_794605_513815_D2K", + "30_794637_513816_D2K", + "30_794670_513817_D2K", + "30_794703_513818_D2K", + "30_794736_513819_D2K", + "30_794769_513820_D2K", + "30_794801_513821_D2K", + "30_794834_513822_D2K", + "30_794867_513823_D2K", + "30_794900_513824_D2K", + "30_794933_513825_D2K", + "30_794965_513826_D2K", + "30_794998_513827_D2K", + "30_795031_513828_D2K", + "30_795064_513829_D2K", + "30_795097_513830_D2K", + "1_795129_513831_D2K", + "1_795162_513832_D2K", + "1_795195_513833_D2K", + "1_795228_513834_D2K", + "1_795261_513835_D2K", + "1_795293_513836_D2K", + "1_795326_513837_D2K", + "14_795333_915843_D2K", + "14_795340_906118_D2K", + "14_795347_915844_D2K", + "14_795354_915845_D2K", + "14_795361_915847_D2K", + "14_795368_915849_D2K", + "14_795375_915851_D2K", + "14_795382_915852_D2K", + "14_795389_906119_D2K", + "14_795396_915853_D2K", + "14_795403_915854_D2K", + "7_795412_513838_D2K", + "7_795445_513839_D2K", + "7_795477_513840_D2K", + "7_795510_513841_D2K", + "7_795543_513842_D2K", + "7_795576_513843_D2K", + "7_795609_513844_D2K", + "7_795641_513845_D2K", + "7_795674_513846_D2K", + "7_795707_513847_D2K", + "7_795740_513848_D2K", + "7_795773_513849_D2K", + "7_795805_513850_D2K", + "7_795838_513851_D2K", + "7_795871_513852_D2K", + "7_795904_513853_D2K", + "7_795936_513854_D2K", + "7_795969_513855_D2K", + "7_796002_513856_D2K", + "7_796035_513857_D2K", + "7_796067_513858_D2K", + "7_796100_513859_D2K", + "7_796133_513860_D2K", + "7_796165_513861_D2K", + "7_796198_513862_D2K", + "7_796231_513863_D2K", + "7_796264_513864_D2K", + "7_796296_513865_D2K", + "7_796329_513866_D2K", + "7_796362_513867_D2K", + "7_796394_513868_D2K", + "7_796427_513869_D2K", + "7_796460_513870_D2K", + "7_796493_513871_D2K", + "7_796525_513872_D2K", + "7_796558_513873_D2K", + "7_796591_513874_D2K", + "7_796624_513875_D2K", + "7_796656_513876_D2K", + "7_796689_513877_D2K", + "7_796722_513878_D2K", + "7_796754_513879_D2K", + "7_796787_513880_D2K", + "7_796794_905676_D2K", + "7_796803_905677_D2K", + "7_796823_905678_D2K", + "7_796843_905680_D2K", + "7_796863_905682_D2K", + "7_796883_905683_D2K", + "7_796914_905684_D2K", + "7_796934_905686_D2K", + "7_796943_905687_D2K", + "30_796950_513881_D2K", + "30_796974_513882_D2K", + "30_797007_513883_D2K", + "30_797040_513884_D2K", + "30_797073_513885_D2K", + "30_797105_513886_D2K", + "30_797138_513887_D2K", + "30_797171_513888_D2K", + "30_797203_513889_D2K", + "30_797236_513890_D2K", + "30_797269_513891_D2K", + "30_797302_513892_D2K", + "30_797334_513893_D2K", + "30_797367_513894_D2K", + "30_797400_513895_D2K", + "30_797432_513896_D2K", + "30_797465_513897_D2K", + "30_797498_513898_D2K", + "30_797531_513899_D2K", + "30_797563_513900_D2K", + "30_797596_513901_D2K", + "30_797629_513902_D2K", + "30_797661_513903_D2K", + "30_797694_513904_D2K", + "30_797727_513905_D2K", + "30_797760_513906_D2K", + "30_797793_513907_D2K", + "30_797825_513908_D2K", + "30_797858_513909_D2K", + "30_797891_513910_D2K", + "30_797924_513911_D2K", + "30_797956_513912_D2K", + "30_797989_513913_D2K", + "30_798022_513914_D2K", + "30_798055_513915_D2K", + "30_798088_513916_D2K", + "30_798120_513917_D2K", + "30_798153_513918_D2K", + "30_798186_513919_D2K", + "30_798219_513920_D2K", + "30_798251_513921_D2K", + "30_798284_513922_D2K", + "30_798317_513923_D2K", + "30_798350_513924_D2K", + "30_798382_513925_D2K", + "30_798415_513926_D2K", + "30_798448_513927_D2K", + "30_798481_513928_D2K", + "30_798513_513929_D2K", + "30_798546_513930_D2K", + "30_798579_513931_D2K", + "30_798612_513932_D2K", + "30_798644_513933_D2K", + "30_798677_513934_D2K", + "30_798710_513935_D2K", + "30_798742_513936_D2K", + "30_798775_513937_D2K", + "30_798808_513938_D2K", + "30_798840_513939_D2K", + "30_798873_513940_D2K", + "30_798906_513941_D2K", + "30_798939_513942_D2K" + ], + "beforeDay": [ + "0", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "63.33333333333333", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "6.666666666666667", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "42.857142857142854", + "42.857142857142854", + "42.857142857142854", + "42.857142857142854", + "42.857142857142854", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "85.71428571428571", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "53.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "3.3333333333333335", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332" + ], + "nextDay": [ + "100.0002", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "83.33333333333334", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "90.0", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "36.666666666666664", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "86.66666666666667", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "13.333333333333334", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "10.0", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "16.666666666666664", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "23.333333333333332", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "93.33333333333333", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "57.14285714285714", + "57.14285714285714", + "57.14285714285714", + "57.14285714285714", + "57.14285714285714", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "14.285714285714285", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "100.0001", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "71.42857142857143", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "28.57142857142857", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "43.333333333333336", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "46.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "56.666666666666664", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "96.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667", + "76.66666666666667" + ], + "frequency": [ + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.3", + "-21.5", + "-21.5", + "-21.3", + "-21.3", + "-21.2", + "-21.3", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.2", + "-21.2", + "-21.2", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.1", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.3", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.3", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.3", + "-21.2", + "-21.1", + "-21.1", + "-21.0", + "-21.0", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.0", + "-21.0", + "-21.0", + "-21.0", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.4", + "-21.5", + "-21.5", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.3", + "-21.4", + "-21.4", + "-21.3", + "-21.3", + "-21.2", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.2", + "-21.1", + "-21.1", + "-21.1", + "-21.1", + "-21.2", + "-21.2", + "-21.2", + "-21.2", + "-21.4", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.1", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.5", + "-21.2", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.4", + "-21.2", + "-21.2", + "-21.2", + "-21.5", + "-21.5", + "-21.5", + "-21.4", + "-21.5" + ] + } +} + +key https://apps.r93535.com/cjgc//riskcontrol/subsidence/statisticsindex!aaa.action?projectSectionId=11364 +value { + "yqwccds": 0, + "wfcgzjds": 0, + "xzcxcds": 0, + "bdlength": null, + "nodisposecount": 0 +} + +key https://apps.r93535.com/cjgc//riskcontrol/subsidence/statisticsindex!hightchartGonghouCjlCX.action?d=0.9220071265776393&type=0&id=11364&isgdd=0 +value diff --git a/test/data/whm_articles.csv b/test/data/whm_articles.csv new file mode 100644 index 0000000..68673ab --- /dev/null +++ b/test/data/whm_articles.csv @@ -0,0 +1,9 @@ +title,content,status,created_at +公司简介,我们是一家专注数字化转型的科技企业,服务覆盖官网建设、内容运营与品牌传播。,已发布,2026-07-01 09:00:00 +产品发布:智能建站 2.0,全新版本支持模块化发布、草稿预览与 Excel 批量导入,帮助运营团队更快上线页面。,已发布,2026-07-10 14:30:00 +客户案例:华东制造企业官网升级,通过内容管理模块,客户将资讯更新效率提升 3 倍,并统一了品牌视觉规范。,已发布,2026-07-15 11:20:00 +招聘:前端工程师,负责业务后台与生成式页面渲染,要求熟悉 React / TypeScript。,草稿,2026-07-18 16:05:00 +行业洞察:2026 官网内容运营趋势,结构化内容、可复用模块与 AI 辅助撰稿正在成为企业官网标配能力。,草稿,2026-07-20 10:12:00 +活动预告:线上产品开放日,本周五 14:00 直播演示一键生成与发布流程,欢迎报名。,已发布,2026-07-22 08:45:00 +服务条款更新说明,我们对隐私政策与服务条款进行了修订,请查阅最新版本。,已发布,2026-07-23 17:00:00 +未命名草稿,待补充正文与配图后发布。,草稿,2026-07-24 09:30:00 diff --git a/test/data/whm_articles.json b/test/data/whm_articles.json new file mode 100644 index 0000000..4f90ca4 --- /dev/null +++ b/test/data/whm_articles.json @@ -0,0 +1,53 @@ +{ + "entity": "article", + "items": [ + { + "title": "公司简介", + "content": "我们是一家专注数字化转型的科技企业,服务覆盖官网建设、内容运营与品牌传播。", + "status": "已发布", + "created_at": "2026-07-01 09:00:00" + }, + { + "title": "产品发布:智能建站 2.0", + "content": "全新版本支持模块化发布、草稿预览与 Excel 批量导入,帮助运营团队更快上线页面。", + "status": "已发布", + "created_at": "2026-07-10 14:30:00" + }, + { + "title": "客户案例:华东制造企业官网升级", + "content": "通过内容管理模块,客户将资讯更新效率提升 3 倍,并统一了品牌视觉规范。", + "status": "已发布", + "created_at": "2026-07-15 11:20:00" + }, + { + "title": "招聘:前端工程师", + "content": "负责业务后台与生成式页面渲染,要求熟悉 React / TypeScript。", + "status": "草稿", + "created_at": "2026-07-18 16:05:00" + }, + { + "title": "行业洞察:2026 官网内容运营趋势", + "content": "结构化内容、可复用模块与 AI 辅助撰稿正在成为企业官网标配能力。", + "status": "草稿", + "created_at": "2026-07-20 10:12:00" + }, + { + "title": "活动预告:线上产品开放日", + "content": "本周五 14:00 直播演示一键生成与发布流程,欢迎报名。", + "status": "已发布", + "created_at": "2026-07-22 08:45:00" + }, + { + "title": "服务条款更新说明", + "content": "我们对隐私政策与服务条款进行了修订,请查阅最新版本。", + "status": "已发布", + "created_at": "2026-07-23 17:00:00" + }, + { + "title": "未命名草稿", + "content": "待补充正文与配图后发布。", + "status": "草稿", + "created_at": "2026-07-24 09:30:00" + } + ] +} \ No newline at end of file diff --git a/test/data/whm_articles.xlsx b/test/data/whm_articles.xlsx new file mode 100644 index 0000000..d840e29 Binary files /dev/null and b/test/data/whm_articles.xlsx differ diff --git a/test/prompts/prompt.txt b/test/prompts/prompt.txt new file mode 100644 index 0000000..18ffc34 --- /dev/null +++ b/test/prompts/prompt.txt @@ -0,0 +1,100 @@ +# AI 建站 — 通用生成提示词(框架层 · 研发对照) +# +# 控制台用户不必粘贴本文件。同等规则由 ai-service/generation_rules.py 在生成时自动注入。 +# 用法:框架(本文件)+ 可选领域说明(如 prompts/prompt_1.txt)+ 数据 + 截图 + 可选 HTML。 +# 素材见 test/;全部操作在系统控制台完成,test 目录不含脚本。 + +你是业务前端生成器。 +输入:用户需求 + 可选数据(Excel/CSV/JSON)+ 可选界面截图 + 可选页面 HTML/MHTML。 +输出:可发布的 App Blueprint(entities / apis / pages / widgets),以及按蓝图渲染的业务页。 + +## 1. 分层(禁止写死行业) + +- 本文件只约束「怎么生成」,不绑定任何行业文案。 +- 行业细节(字段含义、筛选选项、图表系列名、告警规则、业务标题)只来自: + 用户文字 / 领域说明 / 数据表头与枚举 / 截图 / HTML。 +- 没有材料时:用通用标题(列表 / 新增 / 看板 / 趋势图 / 状态 / 明细),禁止臆造路基、桥梁、沉降等词。 + +## 2. 视觉真源优先级 + +有截图或 HTML,且用户未声明「不要还原截图」时: +- `meta.ui_preset = screenshot_faithful` +- dashboard `layout.preset = screenshot_faithful` +- **文案与控件名**:HTML > 用户文字 > 截图理解 +- **分区与配色**:截图 > HTML +- 未点名修改处必须与原图一致;禁止改成绿主题通用 CRUD 壳。 + +无截图无 HTML:严格按用户文字 + 数据字段生成,勿套默认「记录列表 / 概览」。 + +## 2.1 生成闭环(有截图时服务端自动跑) + +1. **视觉模型**理解参考截图 → 结构化摘录 +2. **代码模型**生成/润色蓝图(写入 meta.ui 等) +3. 用蓝图生成「界面计划」+ 可选壳层预览截图 +4. **视觉模型**对照原图打分并列出差异(fails) +5. **代码模型**按 fails 改蓝图 → 回到第 3 步 +6. 直到还原度 ≥ 95%(或达到最大轮次) + +目标分与轮次:环境变量 `FIDELITY_TARGET`(默认 95)、`FIDELITY_MAX_ROUNDS`(默认 4);`FIDELITY_LOOP=0` 可关闭。 + +## 3. 蓝图里必须写全的展示元信息(meta) + +生成时尽量填齐(有则写,无则空,禁止编造): + +| 字段 | 含义 | +|------|------| +| `meta.name` | 系统名称(顶栏居中) | +| `meta.platform_title` | 平台抬头(顶栏左侧) | +| `meta.project_context` | 筛选条左侧工程/业务上下文 | +| `meta.ui_preset` | `screenshot_faithful` 或 `default` | +| `meta.ui.nav_items` | 次级导航页签原文(可多于实际 pages) | +| `meta.ui.shell_links` | 顶栏右侧链(如 欢迎您 / 系统首页 / 退出 / 帮助) | +| `meta.ui.filter_radios` / `section_options` | 筛选单选选项(不含「全部」) | +| `meta.ui.radio_field` | 单选绑定字段(常为分类 enum 字段) | +| `meta.ui.select_field` | 下拉绑定字段(如工点/门店) | +| `meta.ui.filter_hint` | 筛选条右侧提示(常为红色短句) | +| `meta.ui.chart_side_label` / `strip_side_label` / `table_side_label` | 左侧竖排分区标签 | +| `meta.ui.table_title` | 表区横标题 | +| `meta.ui.stats_left_label` / `stats_right_label` | 统计文案(如 断面/测点、分组/记录) | +| `meta.ui.table_headers` | 表头原文(优先于字段名) | + +用户文字里推荐用固定句式(便于解析): + +``` +平台抬头:…… +系统名称:…… +推荐 slug:`snake_case` +工程上下文:…… +单选:全部 / A / B / C +操作:新增、刷新、导入 Excel、导出 Excel +点击……查看…… +``` + +## 4. 数据 → 表结构 + +- Excel/CSV:表头 → 字段;类型推断;**低基数**才做 enum。 +- JSON: + - `list[object]` → 一张表 + - 多个 `list[object]` 顶层键 → 多表 + - 并行数组图表 JSON → 先展成行表,再按共用键拆关联表 +- 标识符一律 `snake_case`。 +- 高基数字段(编号、编码、颜色、时间戳)不要做成 enum。 +- 全量导入:识别到的行数应可完整导入;校验失败只记错误,不中断整批。 + +## 5. 页面与 widgets + +- REST:list/get/create/update/delete/import/export。 +- 列表:`columns` + `filters` + `actions` + `action_labels`(按钮原文)。 +- 看板 widgets 按材料取舍,常见组合: + - `line_chart` / `bar_chart`(多系列 metrics + x_field) + - `status_strip`(label_field / value_field;双段条时加 secondary_field + variant=stacked_days + cycle_days) + - `table`(columns + 可选 filter_field/op/value) + - `kpi` / `pie_chart`(无截图忠实要求时可用;有截图主区时勿用 KPI 顶栏破坏布局) +- 字段 `label` 用中文业务名;图表图例显示 label,不显示裸字段名。 + +## 6. 禁止 + +- 没有材料时编造行业专用字段或筛选项。 +- 忽略截图/HTML 另起一套后台壳。 +- 把所有应用统一成同一种列表模板。 +- 发布后丢掉 `meta.ui*`(平台必须原样保留这些字段)。 diff --git a/test/prompts/prompt_1.txt b/test/prompts/prompt_1.txt new file mode 100644 index 0000000..8bbeda0 --- /dev/null +++ b/test/prompts/prompt_1.txt @@ -0,0 +1,110 @@ +# 领域说明包:沉降变形观测信息系统(prompt_1) +# +# 与 prompts/prompt.txt 通用框架配合:先框架,再本文件。 +# 在系统控制台粘贴本文件(或等价要点),并上传: +# data/settlement.xlsx 或 data/url.config + refs 截图 + refs/*.html +# 解析与发布均在系统内完成,勿依赖 test 下本地脚本。 + +## 参考截图状态(必读) + +- `refs/board_a.png` 与 `refs/board_b.png` 为**同一看板页**的两种状态,不是两个不同页面。 +- 一张为**展开态**(主图 + 按时测量监督条 + 超限表均可见),一张为**收起态**(监督条/侧栏或底部区收起后的布局)。 +- 还原时按**同一页面**理解:合并两图信息,以**展开态为完整布局基准**;收起态仅作交互/控件对照,勿拆成两个独立页面或两套布局。 + +平台抬头:铁路工程管理平台 +系统名称:沉降变形观测信息系统 +推荐 slug:`settlement_observation_system` +工程上下文:川藏铁路雅林段 CZXZZQ-14标(断面/测点数量以实际上传数据为准) +单选:全部 / 路基 / 桥梁 / 隧道 / 过渡段 +操作:新增、刷新、导入 Excel、导出 Excel +点击沉降观测点查看测量信息 + +## 数据 + +- 可接受:Excel / CSV / JSON(含 `mcljChartData` + `cljdChartData` 并行数组)。 +- 也可上传首页抓包 `url.config`(key=接口 URL,value=回包): + 1. `left!newleftmenu` → 次级导航 + 2. `hightchartCjlAndJianDu` → 主图+监督条数据(必填) + 3. `aaa` → 统计计数(可空/全 0) + 4. `hightchartGonghouCjlCX` → 工后超限表;**若 value 为空**,超限表由主图数据按 `exceed_mm>0` 推导,不报错中断。 +- 主表字段(snake_case,有则用、无则按表头推断): + `dkilo`, `chainage`, `point_code`, `section_type`, `worksite`, + `cjl_value`, `cjl_color`, `design_settlement_mm`, `cum_settlement_mm`, + `pred_settlement_mm`, `exceed_mm`, `exceed_days`, `supervise_days`, + `overdue_days`, `before_day`, `next_day`, `frequency`, `workinfo_kilo`, `status` +- 筛选绑定:单选 → `section_type`;下拉 → `worksite`。 +- 导入须全量;校验失败只记错误,不得中断整批。 + +## 展示页(screenshot_faithful,对齐参考截图) + +### 顶栏 +- 左:铁路工程管理平台 +- 中:沉降变形观测信息系统 +- 右:欢迎您、系统首页、退出、帮助(另保留返回控制台) + +### 次级导航(写入 meta.ui.nav_items,可多于实际功能页) +记录汇总、关注信息、断面测点、工作基点、预设水准线路、水准线路、设计沉降量、断链信息、观测人员、仪器设备、测点筛选; +并保证可进入:记录汇总(列表)、新增测点(表单)、沉降变形观测(看板)。 + +### 列表页 +- 筛选:全部 / 路基 / 桥梁 / 隧道 / 过渡段;工点下拉;状态可选。 +- 列:里程、工点、测点、断面类型、设计沉降、累积沉降、超限量、状态。 + +### 看板(纵向三区 + 左侧竖标签) +1. 纵断面沉降示意图:`line_chart`;X=`chainage`;系列=观测值/设计总沉降/累积沉降/预测沉降;可横向滚动。 +2. 按时测量监督(天):`status_strip`,**双段竖条**(不要单色数字格) + - label=`chainage`(条上不显示里程文字,里程在上图轴) + - `value_field`=`before_day`(上段灰色天数) + - `secondary_field`=`next_day`(下段绿色天数) + - `variant`=`stacked_days`,`cycle_days`=30 + - 说明:`before_day`/`next_day` 源数据多为百分比(和≈100),展示时换算成周期内天数,例如 14.29/85.71 → 上 4 / 下 26;停测格显示「停」。 + - 侧栏竖标签:按时测量监督(天) +3. 工点下的沉降量超限测点:`table`;列=工点、测点、设计总沉降量(mm)、累积沉降量(mm)、超限量(mm)、累计天数(天);`exceed_mm > 0`。 + - 侧栏竖标签:沉降量超限工点 + +筛选条展示:工程上下文 + 断面/测点统计 + 单选 + 工点下拉 + 红色提示「点击沉降观测点查看测量信息」。 + +## 规则 + +- 超限:`exceed_mm > 0` 或状态为「超限」。 +- 不要用「记录列表 / 概览 / 数据管理」替代上述业务标题。 +- 不要把里程、测点编号、颜色推断成 enum。 +- 有截图/HTML 时未点名修改处必须与原图一致。 + +## 截图视觉摘录(由 DashScope 视觉模型根据参考截图生成,生成时可与上文合并理解) + +> 两图为同页展开/收起态;以下文案以展开态为主,收起态仅补充可见文案与控件。 + +### 顶栏 +- 左侧平台抬头:铁路工程管理平台 Railway Engineering Management Platform +- 中间系统名:沉降变形观测信息系统 +- 右侧链接原文:欢迎您、退出、帮助 + +### 次级导航 +- 页签原文:沉降观测、记录汇总、关注信息、断面测点、工作基点、预设水准线路、水准线路、设计沉降量、断链信息、观测人员、仪器设备、测点筛选 + +### 筛选条 +- 工程上下文原文:川藏铁路雅林段 CZSCZQ-6 断面:290个 测点: 929个 +- 统计文案:断面:290个 测点: 929个 +- 单选选项原文:全部、路基、桥涵、隧道、过渡段 +- 下拉标签原文:工点: --请选择-- +- 右侧红色提示原文:点击沉降断面点查看测量信息 + +### 主图区 +- 左侧竖排标签原文:纵断面沉降示意图 +- 图例系列名原文:路基断面累计沉降量、桥涵断面累计沉降量、隧道断面累计沉降量、设计总沉降量曲线 +- 图表类型与坐标含义:折线/柱状混合图。纵轴为沉降量(-50mm 至 100mm),横轴为里程桩号(DK300+698 至 DK301+798)。 + +### 监督/状态条 +- 左侧竖排标签原文:按时测量监督 (天) +- 形态:**上灰下绿双段竖条**(非单色数字格);上段灰为已耗/逾期类天数,下段绿为剩余天数;可出现整格「停」 +- 数据字段:`before_day` + `next_day`(常为百分比,和≈100)→ `variant=stacked_days`,`cycle_days=30`(例 14.29/85.71 → 上4/下26) +- 条上是否显示里程文字:否(里程在上方主图横轴);条与主图滚动对齐 + +### 表格区 +- 左侧竖排标签:沉降量超限工点 (未) +- 表头横标题:工点下的沉降量超限测点 +- 列名原文:序号、工点、测点、设计总沉降量(mm)、累积沉降量(mm)、超限量(mm)、累计天数(天) + +### 操作按键 +- 操作:客服、电话、点击隐藏 diff --git a/test/refs/board_a.png b/test/refs/board_a.png new file mode 100644 index 0000000..d14c1b7 Binary files /dev/null and b/test/refs/board_a.png differ diff --git a/test/refs/board_b.png b/test/refs/board_b.png new file mode 100644 index 0000000..261bdd0 Binary files /dev/null and b/test/refs/board_b.png differ diff --git a/test/refs/dashboard.html b/test/refs/dashboard.html new file mode 100644 index 0000000..829bbfd --- /dev/null +++ b/test/refs/dashboard.html @@ -0,0 +1,1070 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
+
按时测量监督
+
+
+ + +
+
+
Created with Highcharts 4.0.3
+
+
+
+
+ + + + + + + + + + + + + + + + +
+ +
序号工点测点设计总沉降量(mm)累积沉降量(mm)超限量(mm)累计天数(天)
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/refs/nav.html b/test/refs/nav.html new file mode 100644 index 0000000..3f32da1 --- /dev/null +++ b/test/refs/nav.html @@ -0,0 +1,658 @@ + + + + + + + +沉降变形观测信息系统 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
电话
+
+
+ +
+
点击隐藏
+
+
+ +
+
+ + +
+ +
+ + + + 川藏铁路雅林段   + CZXZZQ-14B   + + 断面:347个  测点: + 1293个   + + +          + + + + + +
+ +
+ +
+
+ + + + + +
+
+ + + + + 咨询在线客服 +
\ No newline at end of file diff --git a/verify-root/.gitkeep b/verify-root/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/verify-root/.gitkeep @@ -0,0 +1 @@ + diff --git a/web/.dockerignore b/web/.dockerignore new file mode 100644 index 0000000..cae66fe --- /dev/null +++ b/web/.dockerignore @@ -0,0 +1,4 @@ +.git +node_modules +dist +*.md diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 0000000..2b2b969 --- /dev/null +++ b/web/.npmrc @@ -0,0 +1,7 @@ +# npm China mirror (npmmirror / Taobao) +registry=https://registry.npmmirror.com +disturl=https://npmmirror.com/mirrors/node +electron_mirror=https://npmmirror.com/mirrors/electron/ +sass_binary_site=https://npmmirror.com/mirrors/node-sass +puppeteer_download_host=https://npmmirror.com/mirrors +protractor_chromedriver=https://npmmirror.com/mirrors/chromedriver diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..fef158e --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,11 @@ +FROM node:22-bookworm AS build +WORKDIR /web +COPY package.json package-lock.json* ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /web/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/web/dist/assets/index-CtqUfnO-.css b/web/dist/assets/index-CtqUfnO-.css new file mode 100644 index 0000000..e481e53 --- /dev/null +++ b/web/dist/assets/index-CtqUfnO-.css @@ -0,0 +1 @@ +:root{--ink: #1a2421;--ink-soft: #3d4a45;--muted: #6b7a74;--line: #d5ddd8;--line-strong: #b8c4bd;--surface: #eef3f0;--surface-2: #ffffff;--brand: #0f5c45;--brand-soft: #e4f2ec;--accent: #c45c26;--danger: #b42318;--ok: #0f5c45;--shadow: 0 1px 0 rgba(26, 36, 33, .04);--font: "Manrope", "Segoe UI", sans-serif;--display: "Fraunces", "Times New Roman", serif;--radius: 10px}*{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:var(--font);color:var(--ink);background:var(--surface)}button,input,textarea,select{font:inherit;color:inherit}.auth-shell{min-height:100vh;display:grid;place-items:center;padding:32px 16px;position:relative;overflow:hidden}.auth-backdrop{position:absolute;top:0;right:0;bottom:0;left:0;background:radial-gradient(900px 480px at 12% 8%,rgba(15,92,69,.18),transparent 60%),radial-gradient(700px 420px at 88% 92%,rgba(196,92,38,.12),transparent 55%),linear-gradient(160deg,#f4f8f5,#e8efe9 45%,#dfe8e2)}.auth-card{position:relative;z-index:1;width:min(440px,100%);box-shadow:0 18px 48px #1a24211a!important;border-radius:16px!important}.auth-brand{font-family:var(--display)!important;color:var(--brand)!important;letter-spacing:-.03em;margin-bottom:4px!important}.auth-actions{display:grid;grid-template-columns:1fr 1fr;gap:12px}.console-root{min-height:100vh}.console-sider{border-inline-end:1px solid var(--line)!important;position:sticky!important;top:0;height:100vh;overflow:auto}.console-brand{display:flex;align-items:center;gap:12px;padding:20px 18px 12px;min-height:72px}.console-brand.is-collapsed{justify-content:center;padding-inline:12px}.console-brand-mark{width:36px;height:36px;border-radius:10px;background:linear-gradient(145deg,#147a5a,#0f5c45);color:#fff;display:grid;place-items:center;font-family:var(--display);font-weight:700;font-size:1.05rem;flex-shrink:0}.console-brand-text{display:flex;flex-direction:column;line-height:1.2;min-width:0}.console-brand-text strong{font-family:var(--display);font-size:1.05rem;color:var(--brand);letter-spacing:-.02em}.console-brand-text span{font-size:.75rem;color:var(--muted);margin-top:2px}.console-header{display:flex!important;align-items:center;justify-content:space-between;gap:16px;padding:0 20px!important;height:64px!important;line-height:64px!important;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:10}.console-user{font-weight:600;color:var(--ink-soft)}.console-content{padding:20px;min-height:calc(100vh - 64px)}.console-panel>.ant-card,.console-panel>.ant-row{animation:console-fade .28s ease}@keyframes console-fade{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.mono-block,.generate-log-body,.json-editor{font-family:ui-monospace,Cascadia Code,SF Mono,Consolas,monospace;font-size:12px;line-height:1.5;background:#f3f6f4;border:1px solid var(--line);border-radius:8px;padding:12px;overflow:auto;white-space:pre-wrap;word-break:break-all}.generate-log-body{max-height:280px}.json-editor{width:100%;resize:vertical;background:#fafcfb!important}.app{max-width:1180px;margin:0 auto;padding:28px 22px 72px}.topbar{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:8px}.brand{font-family:var(--display);font-size:clamp(2.1rem,4vw,2.75rem);font-weight:700;margin:0;letter-spacing:-.03em;color:var(--brand);line-height:1.1}.sub{color:var(--muted);margin:8px 0 0;font-size:.95rem}.meta-chip{display:inline-flex;gap:8px;flex-wrap:wrap;margin-top:10px;font-size:.82rem;color:var(--ink-soft)}.meta-chip span{border-bottom:1px solid var(--line);padding-bottom:2px}.tabs{display:flex;gap:0;flex-wrap:wrap;border-bottom:1px solid var(--line);margin:22px 0 18px}.tab{border:0;background:transparent;color:var(--muted);padding:12px 16px;margin-bottom:-1px;border-bottom:2px solid transparent;cursor:pointer;font-weight:600;font-size:.92rem}.tab:hover{color:var(--ink)}.tab.active{color:var(--brand);border-bottom-color:var(--brand)}.panel{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--radius);padding:22px;box-shadow:var(--shadow)}.section-title{font-family:var(--display);font-size:1.25rem;margin:0 0 14px;color:var(--ink)}.row{display:grid;gap:14px;margin-bottom:16px}.row.compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));align-items:end}label{display:grid;gap:7px;color:var(--muted);font-size:.8rem;font-weight:600;letter-spacing:.02em}.field-hint{font-weight:500;font-size:.75rem;color:var(--muted);opacity:.85;letter-spacing:0}input,textarea,select{width:100%;border-radius:8px;border:1px solid var(--line-strong);background:#fff;color:var(--ink);padding:10px 12px;transition:border-color .15s ease,box-shadow .15s ease}input:focus,textarea:focus,select:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px #0f5c451f}textarea{resize:vertical;min-height:96px;line-height:1.45}.file-field{position:relative}.file-field input[type=file]{padding:9px;background:var(--brand-soft);border-style:dashed;cursor:pointer}.actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:4px}.btn{border:0;border-radius:8px;padding:10px 16px;background:var(--brand);color:#fff;font-weight:700;cursor:pointer}.btn:hover{filter:brightness(1.05)}.btn.secondary{background:transparent;color:var(--ink);border:1px solid var(--line-strong)}.btn.secondary:hover{background:var(--brand-soft)}.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}.flash{border-radius:8px;padding:10px 12px;margin:0 0 14px;font-size:.92rem}.flash.err{background:#fef3f2;color:var(--danger);border:1px solid #fecdca;white-space:pre-wrap}.flash.ok{background:var(--brand-soft);color:var(--ok);border:1px solid #b7d8c8}.hint{color:var(--accent);font-size:.88rem;margin:12px 0 0;padding:0}.hint li{margin:4px 0}.generate-log{margin-top:12px}.generate-log-body{margin:0;max-height:220px;overflow:auto;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.75rem;line-height:1.45;white-space:pre-wrap;word-break:break-word;background:#f0f4f1;border:1px solid var(--line);border-radius:8px;padding:10px 12px;color:var(--ink-soft)}.mono{font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.8rem;word-break:break-all;background:#f0f4f1;border:1px solid var(--line);border-radius:8px;padding:12px;color:var(--ink-soft)}.grid2{display:grid;gap:18px}@media (min-width: 900px){.grid2{grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr)}}.table-wrap{overflow-x:auto;margin-top:8px;border:1px solid var(--line);border-radius:8px}table{width:100%;border-collapse:collapse;font-size:.88rem;min-width:720px}thead th{position:sticky;top:0;background:#eef4f0;color:var(--ink-soft);font-weight:700;font-size:.78rem;text-transform:none;letter-spacing:.01em;text-align:left;padding:12px 14px;border-bottom:1px solid var(--line-strong);white-space:nowrap}tbody td{padding:12px 14px;border-bottom:1px solid var(--line);color:var(--ink);vertical-align:top;max-width:220px}tbody tr:nth-child(2n){background:#fafcfb}tbody tr:hover{background:var(--brand-soft)}td.cell-muted{color:var(--muted);font-variant-numeric:tabular-nums}td.cell-num{font-variant-numeric:tabular-nums;text-align:right}td.cell-status{font-weight:600}.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:.78rem;background:#eef2ff;color:#3730a3}.badge.ok{background:#e4f2ec;color:var(--brand)}.badge.warn{background:#fff4e5;color:#9a5b00}.badge.bad{background:#fef3f2;color:var(--danger)}.grant-block{margin:16px 0 18px;padding:14px 16px;border:1px solid var(--line, #e5e7eb);background:linear-gradient(180deg,rgba(15,92,69,.03),transparent)}.agent-editor{margin-top:8px;padding:16px;border:1px solid var(--line, #e5e7eb);background:#fff}.agent-editor-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}tr.row-editing td{background:#f3faf6}.grant-title{margin:0 0 10px;font-size:.95rem;font-weight:650}.role-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}.role-card{text-align:left;border:1px solid var(--line, #e5e7eb);background:#fff;padding:10px 12px;cursor:pointer;display:flex;flex-direction:column;gap:4px}.role-card strong{font-size:.92rem}.role-card span{font-size:.78rem;color:var(--muted);line-height:1.35}.role-card.active{border-color:var(--brand);box-shadow:inset 0 0 0 1px var(--brand);background:#f3faf6}.perm-groups{display:grid;gap:12px}.perm-group-title{font-size:.8rem;color:var(--muted);margin-bottom:6px;font-weight:600}.perm-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:6px 12px}.perm-item{display:flex;align-items:flex-start;gap:8px;margin:0;padding:6px 8px;border:1px solid transparent;cursor:pointer}.perm-item:hover{background:#00000005}.perm-item input{margin-top:3px}.perm-item-main{display:flex;flex-direction:column;gap:2px;font-size:.88rem}.perm-item-main code{font-size:.72rem;color:var(--muted);background:transparent;padding:0;border:0}.slug-row{display:flex;gap:8px;align-items:center;margin-bottom:10px}.slug-row input{flex:1}.slug-chips{display:flex;flex-wrap:wrap;gap:8px}.slug-chip{border:1px solid var(--line, #e5e7eb);background:#fff;padding:4px 10px;font-size:.84rem;cursor:pointer}.slug-chip:hover{border-color:var(--danger, #b42318);color:var(--danger, #b42318)}.pages-line{color:var(--muted);font-size:.88rem;margin:0 0 12px}.login-shell{min-height:100vh;display:grid;place-items:center;padding:24px;background:radial-gradient(900px 420px at 15% 10%,rgba(15,92,69,.14),transparent 60%),radial-gradient(700px 380px at 90% 80%,rgba(196,92,38,.1),transparent 55%),var(--surface)}.login-panel{width:min(420px,100%);background:var(--surface-2);border:1px solid var(--line);border-radius:12px;padding:28px 26px;box-shadow:var(--shadow)}.login-panel .brand{margin-bottom:6px}.login-panel .sub{margin-bottom:22px}.empty{text-align:center;color:var(--muted);padding:36px 12px;font-size:.95rem}.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:8px 0 18px}.kpi{border:1px solid var(--line);border-radius:8px;padding:14px 16px;background:#fff}.kpi-title{color:var(--muted);font-size:.8rem;font-weight:600}.kpi-value{font-family:var(--display);font-size:1.7rem;font-weight:700;margin-top:6px;color:var(--brand)}.chart-grid{display:grid;gap:14px}@media (min-width: 860px){.chart-grid{grid-template-columns:1fr 1fr}}.chart-card{border:1px solid var(--line);border-radius:8px;padding:14px 16px;background:#fff}.chart-card h4{margin:0 0 12px;font-size:.95rem;color:var(--ink-soft)}.bar-row{display:grid;grid-template-columns:88px 1fr 40px;gap:8px;align-items:center;margin-bottom:8px}.bar-label{color:var(--muted);font-size:.82rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bar-track{height:8px;background:#e8eee9;border-radius:4px;overflow:hidden}.bar-fill{height:100%;border-radius:4px}.bar-num{text-align:right;font-size:.82rem;font-variant-numeric:tabular-nums}.pie-wrap{display:flex;gap:16px;align-items:center;flex-wrap:wrap}.pie{width:132px;height:132px;border-radius:50%;border:1px solid var(--line)}.pie-legend{list-style:none;margin:0;padding:0;color:var(--muted);font-size:.88rem}.pie-legend li{display:flex;align-items:center;gap:8px;margin-bottom:6px}.pie-legend i{width:10px;height:10px;border-radius:2px;display:inline-block}.line-chart{margin-top:14px}.line-chart-head{display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap}.line-legend{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:10px 14px;font-size:.82rem;color:var(--muted)}.line-legend li{display:flex;align-items:center;gap:6px}.line-legend i{width:14px;height:3px;border-radius:1px;display:inline-block}.line-chart-scroll{overflow-x:auto;margin-top:4px;border-top:1px solid var(--line);padding-top:8px;width:100%}.sf-line-wrap .line-chart-scroll{overflow-x:hidden;border-top:0;padding-top:0}.sf-line-wrap .line-chart-scroll svg,.sf-line-wrap .section-mark-chart svg{display:block;width:100%!important;max-width:none;height:auto;min-height:240px}.line-grid{stroke:#e6ebf2;stroke-width:1}.line-axis{fill:#7a8699;font-size:11px}.status-strip{margin-top:14px}.status-strip-row{display:flex;gap:3px;overflow-x:auto;padding-bottom:6px;align-items:stretch;min-height:88px}.status-cell{flex:0 0 52px;width:52px;min-height:80px;border-radius:2px;color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;padding:4px 2px 6px;font-size:.65rem;line-height:1.15;text-align:center}.status-cell .status-top{writing-mode:horizontal-tb;font-size:.58rem;word-break:break-all;max-height:2.4em;overflow:hidden;opacity:.95;margin-bottom:auto;padding-top:2px}.status-cell .status-val{font-size:.95rem;font-weight:700;margin-top:4px}.status-cell.tone-ok{background:#2f9e44}.status-cell.tone-warn{background:#868e96}.status-cell.tone-muted{background:#adb5bd}.status-strip-stacked .stacked-row{gap:2px;min-height:110px;height:110px;align-items:stretch}.status-stack{flex:0 0 28px;width:28px;height:100%;display:flex;flex-direction:column;border:1px solid #fff;box-sizing:border-box;overflow:hidden}.status-stack .stack-top{background:#9aa0a6;color:#fff;display:flex;align-items:center;justify-content:center;font-size:.72rem;font-weight:700;min-height:14px}.status-stack .stack-bot{background:#37b24d;color:#fff;display:flex;align-items:center;justify-content:center;font-size:.85rem;font-weight:700;min-height:18px}.status-stack.stop{background:#868e96;align-items:center;justify-content:center}.status-stack .stack-stop{color:#fff;font-size:1rem;font-weight:700}.sf-strip-wrap .status-strip-stacked{margin-top:0}.sf-strip-wrap .status-strip-stacked .stacked-row{overflow:hidden}.settle-dash .chart-card{margin-top:12px}.json-editor{min-height:420px;font-family:ui-monospace,Consolas,monospace;font-size:.8rem;line-height:1.45}.gen-app{max-width:1180px;margin:0 auto;padding:24px 22px 64px}.gen-header{display:flex;justify-content:space-between;gap:16px;align-items:flex-start;margin-bottom:18px}.gen-eyebrow{margin:0 0 6px;font-size:.78rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}.gen-title{font-family:var(--display);font-size:clamp(1.8rem,3.5vw,2.4rem);margin:0;color:var(--brand);letter-spacing:-.02em}.gen-desc{margin:8px 0 0;color:var(--muted);max-width:52ch;line-height:1.45}.gen-nav{display:flex;flex-wrap:wrap;gap:0;border-bottom:1px solid var(--line);margin-bottom:18px}.gen-nav-item{border:0;background:transparent;color:var(--muted);padding:12px 16px;margin-bottom:-1px;border-bottom:2px solid transparent;cursor:pointer;font-weight:600}.gen-nav-item.active{color:var(--brand);border-bottom-color:var(--brand)}.gen-panel{margin-top:4px}.gen-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px}.gen-filters{background:var(--brand-soft);border:1px solid var(--line);border-radius:8px;padding:12px;margin-bottom:14px}.gen-app-ops{max-width:1280px}.gen-header-ops{background:linear-gradient(90deg,#0b3a66,#145a9e 55%,#1a6fbf);color:#f4f8fc;border-radius:8px;padding:16px 18px;align-items:center}.gen-header-ops .gen-eyebrow{color:#f4f8fcbf}.gen-header-ops .gen-title{color:#fff;font-family:var(--font);font-size:1.35rem}.gen-header-ops .btn.secondary{background:#ffffff1f;color:#fff;border-color:#ffffff59}.gen-project-bar{display:flex;gap:16px;flex-wrap:wrap;margin:8px 0 0;font-size:.88rem;color:#f4f8fceb}.gen-project-stats{opacity:.85}.gen-nav-ops{background:#e8eef5;border:1px solid #c5d3e3;border-radius:6px;padding:4px}.gen-nav-ops .gen-nav-item.active{background:#0b3a66;color:#fff}.ops-section-radios{display:flex;flex-wrap:wrap;gap:8px;align-items:center;background:#eef3f8;border:1px solid #c5d3e3;border-radius:6px;padding:10px 12px;margin-bottom:14px}.ops-radio-label{font-size:.85rem;color:var(--muted);margin-right:4px}.ops-radio{border:1px solid #9db2c9;background:#fff;border-radius:999px;padding:4px 12px;font-size:.85rem;cursor:pointer}.ops-radio.active{background:#0b3a66;color:#fff;border-color:#0b3a66}.ops-extra-filter{display:flex;flex-direction:column;gap:4px;font-size:.82rem}.ops-dashboard .ops-dash-hint{color:var(--muted);font-size:.85rem;margin:-6px 0 12px}.ops-dashboard .kpi-grid{margin-bottom:8px}.sf-shell-bar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px;background:linear-gradient(90deg,#0a2f55,#124a7c 50%,#0a2f55);color:#fff;padding:10px 16px;border-radius:0;margin:-8px -8px 0}.sf-shell-left{font-size:.92rem;opacity:.95}.sf-shell-center{font-size:1.15rem;font-weight:700;letter-spacing:.02em;text-align:center}.sf-shell-right{display:flex;justify-content:flex-end;align-items:center;gap:12px}.sf-shell-user{font-size:.85rem;opacity:.9}.sf-shell-link{font-size:.82rem;opacity:.88;cursor:default;white-space:nowrap}.sf-shell-back{background:transparent;border:1px solid rgba(255,255,255,.45);color:#fff;border-radius:4px;padding:4px 12px;cursor:pointer}.gen-nav-ops .gen-nav-item.is-chrome{opacity:.75;cursor:default}.gen-nav-ops .gen-nav-item.is-chrome:hover{background:transparent;color:inherit}.sf-mini-context{background:#e7eef6;border:1px solid #b9c9dc;padding:8px 12px;font-size:.88rem;margin:8px 0}.gen-app-ops{max-width:none;width:100%;background:#eef2f6;padding:0;margin:0}.gen-app-ops .gen-nav-ops{background:#b8cfe6;border:0;border-bottom:1px solid #7a9abe;border-radius:0;margin:0;padding:0 4px;display:flex;flex-wrap:nowrap;overflow-x:auto;gap:0}.gen-app-ops .gen-nav-ops .gen-nav-item{border-radius:0;padding:9px 12px;font-size:.84rem;white-space:nowrap;background:transparent;border:0;color:#123354;border-bottom:3px solid transparent}.gen-app-ops .gen-nav-ops .gen-nav-item.active{background:#0b3a66;color:#fff;border-bottom-color:#0b3a66}.sf-shell-bar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px;background:#003a6b;color:#fff;padding:8px 18px;border-radius:0;margin:0}.sf-shell-left{font-size:.95rem;line-height:1.25}.sf-shell-left .sf-shell-en{display:block;font-size:.68rem;opacity:.8;letter-spacing:.02em}.sf-shell-center{font-size:1.25rem;font-weight:700;letter-spacing:.04em;text-align:center}.sf-float-dock{position:fixed;right:0;top:42%;z-index:40;display:flex;flex-direction:column;gap:6px}.sf-float-dock button{writing-mode:horizontal-tb;background:#0b5cab;color:#fff;border:0;border-radius:4px 0 0 4px;padding:8px 6px;font-size:.72rem;cursor:default;width:44px;line-height:1.2}.sf-root{background:#fff;border:0;min-height:calc(100vh - 96px)}.sf-filterbar{display:flex;flex-wrap:wrap;gap:14px;align-items:center;padding:8px 12px;background:#fff;border-bottom:1px solid #d5dee8}.sf-context{font-size:.88rem;color:#243447}.sf-leg-item.leg-line:before{height:3px;width:18px;vertical-align:2px;border-radius:1px}.sf-body{display:grid;grid-template-columns:34px 1fr;min-height:640px}.sf-rail{background:#0b3a66;color:#fff;display:flex;flex-direction:column}.sf-rail-seg{writing-mode:vertical-rl;text-orientation:upright;transform:none;display:flex;align-items:center;justify-content:center;letter-spacing:.18em;font-size:.78rem;border-bottom:1px solid rgba(255,255,255,.22);padding:8px 2px;white-space:nowrap;overflow:hidden}.sf-rail-seg.chart{flex:2.2}.sf-rail-seg.strip{flex:1.1}.sf-rail-seg.table{flex:1.4;border-bottom:0}.sf-main{display:flex;flex-direction:column;min-width:0}.sf-line-wrap{min-height:280px;padding:4px 8px 0}.sf-line-wrap .chart-card{border:0;box-shadow:none;margin:0;padding:0}.sf-scroll{width:100%;margin:2px 0 6px}.sf-strip-wrap{border-top:1px solid #d0d7e2;padding:0 6px 8px;min-height:130px}.sf-strip-cap{font-size:.8rem;color:#4a5a6a;padding:4px 4px 2px}.sf-table-panel{padding:0 10px 16px;border-top:1px solid #d0d7e2}.sf-table-title{text-align:center;color:#0b3a66;font-weight:600;padding:8px;font-size:.95rem}.sf-table th{background:#eef2f6}.status-strip-stacked .stacked-row{gap:1px;min-height:118px;height:118px;overflow:hidden}.status-stack{flex:1 1 0;min-width:22px;max-width:36px;width:auto;height:100%;display:flex;flex-direction:column;border:1px solid #fff;box-sizing:border-box;overflow:hidden}.status-stack .stack-top{background:#9aa0a6;color:#fff;display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:700}.status-stack .stack-bot{background:#37b24d;color:#fff;display:flex;align-items:center;justify-content:center;font-size:.78rem;font-weight:700}.status-stack.all-grey,.status-stack.stop{background:#868e96;align-items:center;justify-content:center}.status-stack.all-green{background:#37b24d;align-items:center;justify-content:center}.status-stack .stack-solo,.status-stack .stack-stop{color:#fff;font-weight:700;font-size:.9rem}.sf-root{background:#fff;border:1px solid #c5d0de}.sf-topbar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;background:#0b2f52;color:#fff;padding:8px 14px}.sf-top-left{font-size:.85rem}.sf-top-center{text-align:center;font-weight:700;font-size:1.05rem}.sf-top-right{text-align:right;font-size:.82rem;opacity:.9}.sf-filterbar{display:flex;flex-wrap:wrap;gap:12px;align-items:center;padding:10px 12px;background:#f7f9fc;border-bottom:1px solid #d5dee8}.sf-context{display:flex;gap:14px;align-items:center;font-size:.88rem}.sf-radios{display:flex;gap:14px;flex-wrap:wrap;align-items:center}.sf-radio{display:inline-flex;align-items:center;gap:4px;font-size:.86rem;padding:0;border-radius:0;cursor:pointer;color:#243447;background:transparent;font-weight:400}.sf-radio.on{background:transparent;color:#0b3a66;font-weight:500}.sf-radio input{accent-color:#0b3a66;margin:0 2px 0 0}.sf-select{font-size:.86rem;display:inline-flex;gap:4px;align-items:center;color:#243447}.sf-select select{min-width:120px;padding:2px 4px}.sf-hint{color:#d32f2f;font-size:.85rem;margin-left:auto}.sf-legend{display:flex;justify-content:center;gap:22px;padding:8px 12px 4px;font-size:.82rem;border-bottom:0}.sf-leg-item:before{content:"";display:inline-block;width:12px;height:12px;margin-right:6px;vertical-align:-1px;background:var(--leg, #1d4f91)}.sf-chart-stack{display:grid;grid-template-columns:36px 1fr;min-height:420px;border-bottom:1px solid #d5dee8}.sf-side-labels{background:#0b3a66;color:#fff;display:flex;flex-direction:column}.sf-side-label{flex:1;writing-mode:vertical-rl;text-orientation:upright;display:flex;align-items:center;justify-content:center;font-size:.8rem;letter-spacing:.18em;border-bottom:1px solid rgba(255,255,255,.2);padding:8px 0}.sf-table-block{display:grid;grid-template-columns:36px 1fr;border-top:1px solid #d5dee8}.sf-side-labels-single{min-height:160px}.sf-table-block .sf-table-panel{border:0}.sf-chart-main{padding:8px 10px 4px;background:#fff;min-height:320px}.sf-line-wrap{min-height:280px}.sf-line-wrap .line-chart-head{display:none}.sf-line-wrap .chart-card,.sf-strip-wrap .chart-card{border:none;box-shadow:none;padding:0;margin:0}.sf-strip-wrap{margin-top:8px;min-height:88px}.sf-scroll{width:100%;margin:4px 0 8px}.sf-table-panel{padding:10px 12px 16px}.sf-table-title{background:#e8eef5;border:1px solid #c5d3e3;padding:6px 10px;font-weight:600;font-size:.9rem;margin-bottom:8px}.sf-table th{background:#f3f6fa;font-weight:600}.ajz-machine-tag{position:absolute!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important;opacity:0!important;pointer-events:none!important;-webkit-user-select:none!important;user-select:none!important} diff --git a/web/dist/assets/index-WRB2YG1b.js b/web/dist/assets/index-WRB2YG1b.js new file mode 100644 index 0000000..6c6ebc2 --- /dev/null +++ b/web/dist/assets/index-WRB2YG1b.js @@ -0,0 +1,379 @@ +var L6=Object.defineProperty;var k6=(e,t,n)=>t in e?L6(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var fe=(e,t,n)=>k6(e,typeof t!="symbol"?t+"":t,n);function A6(e,t){for(var n=0;nr[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=n(o);fetch(o.href,s)}})();function k0(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var bE={exports:{}},Lf={},xE={exports:{}},kt={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var kc=Symbol.for("react.element"),D6=Symbol.for("react.portal"),F6=Symbol.for("react.fragment"),H6=Symbol.for("react.strict_mode"),V6=Symbol.for("react.profiler"),W6=Symbol.for("react.provider"),K6=Symbol.for("react.context"),U6=Symbol.for("react.forward_ref"),q6=Symbol.for("react.suspense"),G6=Symbol.for("react.memo"),X6=Symbol.for("react.lazy"),b$=Symbol.iterator;function Y6(e){return e===null||typeof e!="object"?null:(e=b$&&e[b$]||e["@@iterator"],typeof e=="function"?e:null)}var $E={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},SE=Object.assign,CE={};function Ya(e,t,n){this.props=e,this.context=t,this.refs=CE,this.updater=n||$E}Ya.prototype.isReactComponent={};Ya.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Ya.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function wE(){}wE.prototype=Ya.prototype;function A0(e,t,n){this.props=e,this.context=t,this.refs=CE,this.updater=n||$E}var D0=A0.prototype=new wE;D0.constructor=A0;SE(D0,Ya.prototype);D0.isPureReactComponent=!0;var x$=Array.isArray,EE=Object.prototype.hasOwnProperty,F0={current:null},IE={key:!0,ref:!0,__self:!0,__source:!0};function PE(e,t,n){var r,o={},s=null,i=null;if(t!=null)for(r in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(s=""+t.key),t)EE.call(t,r)&&!IE.hasOwnProperty(r)&&(o[r]=t[r]);var l=arguments.length-2;if(l===1)o.children=n;else if(1>>1,_=j[k];if(0>>1;ko(W,A))K<_&&0>o(q,W)?(j[k]=q,j[K]=A,k=K):(j[k]=W,j[V]=A,k=V);else if(K<_&&0>o(q,A))j[k]=q,j[K]=A,k=K;else break e}}return O}function o(j,O){var A=j.sortIndex-O.sortIndex;return A!==0?A:j.id-O.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var i=Date,l=i.now();e.unstable_now=function(){return i.now()-l}}var c=[],u=[],d=1,m=null,f=3,p=!1,y=!1,b=!1,x=typeof setTimeout=="function"?setTimeout:null,v=typeof clearTimeout=="function"?clearTimeout:null,g=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function h(j){for(var O=n(u);O!==null;){if(O.callback===null)r(u);else if(O.startTime<=j)r(u),O.sortIndex=O.expirationTime,t(c,O);else break;O=n(u)}}function $(j){if(b=!1,h(j),!y)if(n(c)!==null)y=!0,F(C);else{var O=n(u);O!==null&&L($,O.startTime-j)}}function C(j,O){y=!1,b&&(b=!1,v(E),E=-1),p=!0;var A=f;try{for(h(O),m=n(c);m!==null&&(!(m.expirationTime>O)||j&&!P());){var k=m.callback;if(typeof k=="function"){m.callback=null,f=m.priorityLevel;var _=k(m.expirationTime<=O);O=e.unstable_now(),typeof _=="function"?m.callback=_:m===n(c)&&r(c),h(O)}else r(c);m=n(c)}if(m!==null)var D=!0;else{var V=n(u);V!==null&&L($,V.startTime-O),D=!1}return D}finally{m=null,f=A,p=!1}}var N=!1,S=null,E=-1,w=5,R=-1;function P(){return!(e.unstable_now()-Rj||125k?(j.sortIndex=A,t(u,j),n(c)===null&&j===n(u)&&(b?(v(E),E=-1):b=!0,L($,A-k))):(j.sortIndex=_,t(c,j),y||p||(y=!0,F(C))),j},e.unstable_shouldYield=P,e.unstable_wrapCallback=function(j){var O=f;return function(){var A=f;f=O;try{return j.apply(this,arguments)}finally{f=A}}}})(OE);ME.exports=OE;var l8=ME.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var c8=a,zr=l8;function He(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Lg=Object.prototype.hasOwnProperty,u8=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,S$={},C$={};function d8(e){return Lg.call(C$,e)?!0:Lg.call(S$,e)?!1:u8.test(e)?C$[e]=!0:(S$[e]=!0,!1)}function f8(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function m8(e,t,n,r){if(t===null||typeof t>"u"||f8(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function dr(e,t,n,r,o,s,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=i}var qn={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){qn[e]=new dr(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];qn[t]=new dr(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){qn[e]=new dr(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){qn[e]=new dr(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){qn[e]=new dr(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){qn[e]=new dr(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){qn[e]=new dr(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){qn[e]=new dr(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){qn[e]=new dr(e,5,!1,e.toLowerCase(),null,!1,!1)});var V0=/[\-:]([a-z])/g;function W0(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(V0,W0);qn[t]=new dr(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(V0,W0);qn[t]=new dr(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(V0,W0);qn[t]=new dr(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){qn[e]=new dr(e,1,!1,e.toLowerCase(),null,!1,!1)});qn.xlinkHref=new dr("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){qn[e]=new dr(e,1,!1,e.toLowerCase(),null,!0,!0)});function K0(e,t,n,r){var o=qn.hasOwnProperty(t)?qn[t]:null;(o!==null?o.type!==0:r||!(2l||o[i]!==s[l]){var c=` +`+o[i].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}while(1<=i&&0<=l);break}}}finally{rp=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?El(e):""}function p8(e){switch(e.tag){case 5:return El(e.type);case 16:return El("Lazy");case 13:return El("Suspense");case 19:return El("SuspenseList");case 0:case 2:case 15:return e=op(e.type,!1),e;case 11:return e=op(e.type.render,!1),e;case 1:return e=op(e.type,!0),e;default:return""}}function Fg(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case la:return"Fragment";case aa:return"Portal";case kg:return"Profiler";case U0:return"StrictMode";case Ag:return"Suspense";case Dg:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case jE:return(e.displayName||"Context")+".Consumer";case zE:return(e._context.displayName||"Context")+".Provider";case q0:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case G0:return t=e.displayName||null,t!==null?t:Fg(e.type)||"Memo";case ms:t=e._payload,e=e._init;try{return Fg(e(t))}catch{}}return null}function g8(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Fg(t);case 8:return t===U0?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function js(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function LE(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function h8(e){var t=LE(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var o=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){r=""+i,s.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function gu(e){e._valueTracker||(e._valueTracker=h8(e))}function kE(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=LE(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Td(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Hg(e,t){var n=t.checked;return gn({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function E$(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=js(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function AE(e,t){t=t.checked,t!=null&&K0(e,"checked",t,!1)}function Vg(e,t){AE(e,t);var n=js(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Wg(e,t.type,n):t.hasOwnProperty("defaultValue")&&Wg(e,t.type,js(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function I$(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Wg(e,t,n){(t!=="number"||Td(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Il=Array.isArray;function Sa(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o"+t.valueOf().toString()+"",t=hu.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function oc(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var jl={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},y8=["Webkit","ms","Moz","O"];Object.keys(jl).forEach(function(e){y8.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),jl[t]=jl[e]})});function VE(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||jl.hasOwnProperty(e)&&jl[e]?(""+t).trim():t+"px"}function WE(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,o=VE(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,o):e[n]=o}}var v8=gn({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function qg(e,t){if(t){if(v8[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(He(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(He(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(He(61))}if(t.style!=null&&typeof t.style!="object")throw Error(He(62))}}function Gg(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Xg=null;function X0(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Yg=null,Ca=null,wa=null;function R$(e){if(e=Fc(e)){if(typeof Yg!="function")throw Error(He(280));var t=e.stateNode;t&&(t=Hf(t),Yg(e.stateNode,e.type,t))}}function KE(e){Ca?wa?wa.push(e):wa=[e]:Ca=e}function UE(){if(Ca){var e=Ca,t=wa;if(wa=Ca=null,R$(e),t)for(e=0;e>>=0,e===0?32:31-(R8(e)/T8|0)|0}var yu=64,vu=4194304;function Pl(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function zd(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,o=e.suspendedLanes,s=e.pingedLanes,i=n&268435455;if(i!==0){var l=i&~o;l!==0?r=Pl(l):(s&=i,s!==0&&(r=Pl(s)))}else i=n&~o,i!==0?r=Pl(i):s!==0&&(r=Pl(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&o)&&(o=r&-r,s=t&-t,o>=s||o===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Ac(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-uo(t),e[t]=n}function z8(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Ll),k$=" ",A$=!1;function f2(e,t){switch(e){case"keyup":return l3.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function m2(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ca=!1;function u3(e,t){switch(e){case"compositionend":return m2(t);case"keypress":return t.which!==32?null:(A$=!0,k$);case"textInput":return e=t.data,e===k$&&A$?null:e;default:return null}}function d3(e,t){if(ca)return e==="compositionend"||!rb&&f2(e,t)?(e=u2(),sd=eb=$s=null,ca=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=V$(n)}}function y2(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?y2(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function v2(){for(var e=window,t=Td();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Td(e.document)}return t}function ob(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function x3(e){var t=v2(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&y2(n.ownerDocument.documentElement,n)){if(r!==null&&ob(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var o=n.textContent.length,s=Math.min(r.start,o);r=r.end===void 0?s:Math.min(r.end,o),!e.extend&&s>r&&(o=r,r=s,s=o),o=W$(n,s);var i=W$(n,r);o&&i&&(e.rangeCount!==1||e.anchorNode!==o.node||e.anchorOffset!==o.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&(t=t.createRange(),t.setStart(o.node,o.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,ua=null,nh=null,Al=null,rh=!1;function K$(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;rh||ua==null||ua!==Td(r)||(r=ua,"selectionStart"in r&&ob(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Al&&uc(Al,r)||(Al=r,r=Ld(nh,"onSelect"),0ma||(e.current=ch[ma],ch[ma]=null,ma--)}function tn(e,t){ma++,ch[ma]=e.current,e.current=t}var Bs={},nr=Vs(Bs),xr=Vs(!1),$i=Bs;function za(e,t){var n=e.type.contextTypes;if(!n)return Bs;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o={},s;for(s in n)o[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function $r(e){return e=e.childContextTypes,e!=null}function Ad(){ln(xr),ln(nr)}function J$(e,t,n){if(nr.current!==Bs)throw Error(He(168));tn(nr,t),tn(xr,n)}function P2(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var o in r)if(!(o in t))throw Error(He(108,g8(e)||"Unknown",o));return gn({},n,r)}function Dd(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Bs,$i=nr.current,tn(nr,e),tn(xr,xr.current),!0}function Z$(e,t,n){var r=e.stateNode;if(!r)throw Error(He(169));n?(e=P2(e,t,$i),r.__reactInternalMemoizedMergedChildContext=e,ln(xr),ln(nr),tn(nr,e)):ln(xr),tn(xr,n)}var Ko=null,Vf=!1,vp=!1;function N2(e){Ko===null?Ko=[e]:Ko.push(e)}function O3(e){Vf=!0,N2(e)}function Ws(){if(!vp&&Ko!==null){vp=!0;var e=0,t=Qt;try{var n=Ko;for(Qt=1;e>=i,o-=i,Xo=1<<32-uo(t)+o|n<E?(w=S,S=null):w=S.sibling;var R=f(v,S,h[E],$);if(R===null){S===null&&(S=w);break}e&&S&&R.alternate===null&&t(v,S),g=s(R,g,E),N===null?C=R:N.sibling=R,N=R,S=w}if(E===h.length)return n(v,S),un&&ei(v,E),C;if(S===null){for(;EE?(w=S,S=null):w=S.sibling;var P=f(v,S,R.value,$);if(P===null){S===null&&(S=w);break}e&&S&&P.alternate===null&&t(v,S),g=s(P,g,E),N===null?C=P:N.sibling=P,N=P,S=w}if(R.done)return n(v,S),un&&ei(v,E),C;if(S===null){for(;!R.done;E++,R=h.next())R=m(v,R.value,$),R!==null&&(g=s(R,g,E),N===null?C=R:N.sibling=R,N=R);return un&&ei(v,E),C}for(S=r(v,S);!R.done;E++,R=h.next())R=p(S,v,E,R.value,$),R!==null&&(e&&R.alternate!==null&&S.delete(R.key===null?E:R.key),g=s(R,g,E),N===null?C=R:N.sibling=R,N=R);return e&&S.forEach(function(T){return t(v,T)}),un&&ei(v,E),C}function x(v,g,h,$){if(typeof h=="object"&&h!==null&&h.type===la&&h.key===null&&(h=h.props.children),typeof h=="object"&&h!==null){switch(h.$$typeof){case pu:e:{for(var C=h.key,N=g;N!==null;){if(N.key===C){if(C=h.type,C===la){if(N.tag===7){n(v,N.sibling),g=o(N,h.props.children),g.return=v,v=g;break e}}else if(N.elementType===C||typeof C=="object"&&C!==null&&C.$$typeof===ms&&nS(C)===N.type){n(v,N.sibling),g=o(N,h.props),g.ref=pl(v,N,h),g.return=v,v=g;break e}n(v,N);break}else t(v,N);N=N.sibling}h.type===la?(g=yi(h.props.children,v.mode,$,h.key),g.return=v,v=g):($=md(h.type,h.key,h.props,null,v.mode,$),$.ref=pl(v,g,h),$.return=v,v=$)}return i(v);case aa:e:{for(N=h.key;g!==null;){if(g.key===N)if(g.tag===4&&g.stateNode.containerInfo===h.containerInfo&&g.stateNode.implementation===h.implementation){n(v,g.sibling),g=o(g,h.children||[]),g.return=v,v=g;break e}else{n(v,g);break}else t(v,g);g=g.sibling}g=Ip(h,v.mode,$),g.return=v,v=g}return i(v);case ms:return N=h._init,x(v,g,N(h._payload),$)}if(Il(h))return y(v,g,h,$);if(cl(h))return b(v,g,h,$);Eu(v,h)}return typeof h=="string"&&h!==""||typeof h=="number"?(h=""+h,g!==null&&g.tag===6?(n(v,g.sibling),g=o(g,h),g.return=v,v=g):(n(v,g),g=Ep(h,v.mode,$),g.return=v,v=g),i(v)):n(v,g)}return x}var Ba=O2(!0),_2=O2(!1),Vd=Vs(null),Wd=null,ha=null,lb=null;function cb(){lb=ha=Wd=null}function ub(e){var t=Vd.current;ln(Vd),e._currentValue=t}function fh(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Ia(e,t){Wd=e,lb=ha=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(br=!0),e.firstContext=null)}function Yr(e){var t=e._currentValue;if(lb!==e)if(e={context:e,memoizedValue:t,next:null},ha===null){if(Wd===null)throw Error(He(308));ha=e,Wd.dependencies={lanes:0,firstContext:e}}else ha=ha.next=e;return t}var ci=null;function db(e){ci===null?ci=[e]:ci.push(e)}function z2(e,t,n,r){var o=t.interleaved;return o===null?(n.next=n,db(t)):(n.next=o.next,o.next=n),t.interleaved=n,rs(e,r)}function rs(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var ps=!1;function fb(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function j2(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Qo(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Ts(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Ut&2){var o=r.pending;return o===null?t.next=t:(t.next=o.next,o.next=t),r.pending=t,rs(e,n)}return o=r.interleaved,o===null?(t.next=t,db(r)):(t.next=o.next,o.next=t),r.interleaved=t,rs(e,n)}function ad(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Q0(e,n)}}function rS(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var o=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?o=s=i:s=s.next=i,n=n.next}while(n!==null);s===null?o=s=t:s=s.next=t}else o=s=t;n={baseState:r.baseState,firstBaseUpdate:o,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Kd(e,t,n,r){var o=e.updateQueue;ps=!1;var s=o.firstBaseUpdate,i=o.lastBaseUpdate,l=o.shared.pending;if(l!==null){o.shared.pending=null;var c=l,u=c.next;c.next=null,i===null?s=u:i.next=u,i=c;var d=e.alternate;d!==null&&(d=d.updateQueue,l=d.lastBaseUpdate,l!==i&&(l===null?d.firstBaseUpdate=u:l.next=u,d.lastBaseUpdate=c))}if(s!==null){var m=o.baseState;i=0,d=u=c=null,l=s;do{var f=l.lane,p=l.eventTime;if((r&f)===f){d!==null&&(d=d.next={eventTime:p,lane:0,tag:l.tag,payload:l.payload,callback:l.callback,next:null});e:{var y=e,b=l;switch(f=t,p=n,b.tag){case 1:if(y=b.payload,typeof y=="function"){m=y.call(p,m,f);break e}m=y;break e;case 3:y.flags=y.flags&-65537|128;case 0:if(y=b.payload,f=typeof y=="function"?y.call(p,m,f):y,f==null)break e;m=gn({},m,f);break e;case 2:ps=!0}}l.callback!==null&&l.lane!==0&&(e.flags|=64,f=o.effects,f===null?o.effects=[l]:f.push(l))}else p={eventTime:p,lane:f,tag:l.tag,payload:l.payload,callback:l.callback,next:null},d===null?(u=d=p,c=m):d=d.next=p,i|=f;if(l=l.next,l===null){if(l=o.shared.pending,l===null)break;f=l,l=f.next,f.next=null,o.lastBaseUpdate=f,o.shared.pending=null}}while(!0);if(d===null&&(c=m),o.baseState=c,o.firstBaseUpdate=u,o.lastBaseUpdate=d,t=o.shared.interleaved,t!==null){o=t;do i|=o.lane,o=o.next;while(o!==t)}else s===null&&(o.shared.lanes=0);wi|=i,e.lanes=i,e.memoizedState=m}}function oS(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=xp.transition;xp.transition={};try{e(!1),t()}finally{Qt=n,xp.transition=r}}function J2(){return Qr().memoizedState}function B3(e,t,n){var r=Os(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Z2(e))eI(t,n);else if(n=z2(e,t,n,r),n!==null){var o=sr();fo(n,e,r,o),tI(n,t,r)}}function L3(e,t,n){var r=Os(e),o={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Z2(e))eI(t,o);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var i=t.lastRenderedState,l=s(i,n);if(o.hasEagerState=!0,o.eagerState=l,po(l,i)){var c=t.interleaved;c===null?(o.next=o,db(t)):(o.next=c.next,c.next=o),t.interleaved=o;return}}catch{}finally{}n=z2(e,t,o,r),n!==null&&(o=sr(),fo(n,e,r,o),tI(n,t,r))}}function Z2(e){var t=e.alternate;return e===pn||t!==null&&t===pn}function eI(e,t){Dl=qd=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function tI(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Q0(e,n)}}var Gd={readContext:Yr,useCallback:Qn,useContext:Qn,useEffect:Qn,useImperativeHandle:Qn,useInsertionEffect:Qn,useLayoutEffect:Qn,useMemo:Qn,useReducer:Qn,useRef:Qn,useState:Qn,useDebugValue:Qn,useDeferredValue:Qn,useTransition:Qn,useMutableSource:Qn,useSyncExternalStore:Qn,useId:Qn,unstable_isNewReconciler:!1},k3={readContext:Yr,useCallback:function(e,t){return Eo().memoizedState=[e,t===void 0?null:t],e},useContext:Yr,useEffect:iS,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,cd(4194308,4,q2.bind(null,t,e),n)},useLayoutEffect:function(e,t){return cd(4194308,4,e,t)},useInsertionEffect:function(e,t){return cd(4,2,e,t)},useMemo:function(e,t){var n=Eo();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Eo();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=B3.bind(null,pn,e),[r.memoizedState,e]},useRef:function(e){var t=Eo();return e={current:e},t.memoizedState=e},useState:sS,useDebugValue:xb,useDeferredValue:function(e){return Eo().memoizedState=e},useTransition:function(){var e=sS(!1),t=e[0];return e=j3.bind(null,e[1]),Eo().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=pn,o=Eo();if(un){if(n===void 0)throw Error(He(407));n=n()}else{if(n=t(),Dn===null)throw Error(He(349));Ci&30||A2(r,t,n)}o.memoizedState=n;var s={value:n,getSnapshot:t};return o.queue=s,iS(F2.bind(null,r,s,e),[e]),r.flags|=2048,vc(9,D2.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=Eo(),t=Dn.identifierPrefix;if(un){var n=Yo,r=Xo;n=(r&~(1<<32-uo(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=hc++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=i.createElement(n,{is:r.is}):(e=i.createElement(n),n==="select"&&(i=e,r.multiple?i.multiple=!0:r.size&&(i.size=r.size))):e=i.createElementNS(e,n),e[Po]=t,e[mc]=r,dI(e,t,!1,!1),t.stateNode=e;e:{switch(i=Gg(n,r),n){case"dialog":sn("cancel",e),sn("close",e),o=r;break;case"iframe":case"object":case"embed":sn("load",e),o=r;break;case"video":case"audio":for(o=0;oAa&&(t.flags|=128,r=!0,gl(s,!1),t.lanes=4194304)}else{if(!r)if(e=Ud(i),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),gl(s,!0),s.tail===null&&s.tailMode==="hidden"&&!i.alternate&&!un)return Jn(t),null}else 2*xn()-s.renderingStartTime>Aa&&n!==1073741824&&(t.flags|=128,r=!0,gl(s,!1),t.lanes=4194304);s.isBackwards?(i.sibling=t.child,t.child=i):(n=s.last,n!==null?n.sibling=i:t.child=i,s.last=i)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=xn(),t.sibling=null,n=mn.current,tn(mn,r?n&1|2:n&1),t):(Jn(t),null);case 22:case 23:return Ib(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Rr&1073741824&&(Jn(t),t.subtreeFlags&6&&(t.flags|=8192)):Jn(t),null;case 24:return null;case 25:return null}throw Error(He(156,t.tag))}function U3(e,t){switch(ib(t),t.tag){case 1:return $r(t.type)&&Ad(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return La(),ln(xr),ln(nr),gb(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return pb(t),null;case 13:if(ln(mn),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(He(340));ja()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return ln(mn),null;case 4:return La(),null;case 10:return ub(t.type._context),null;case 22:case 23:return Ib(),null;case 24:return null;default:return null}}var Pu=!1,er=!1,q3=typeof WeakSet=="function"?WeakSet:Set,rt=null;function ya(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){vn(e,t,r)}else n.current=null}function $h(e,t,n){try{n()}catch(r){vn(e,t,r)}}var yS=!1;function G3(e,t){if(oh=jd,e=v2(),ob(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var o=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var i=0,l=-1,c=-1,u=0,d=0,m=e,f=null;t:for(;;){for(var p;m!==n||o!==0&&m.nodeType!==3||(l=i+o),m!==s||r!==0&&m.nodeType!==3||(c=i+r),m.nodeType===3&&(i+=m.nodeValue.length),(p=m.firstChild)!==null;)f=m,m=p;for(;;){if(m===e)break t;if(f===n&&++u===o&&(l=i),f===s&&++d===r&&(c=i),(p=m.nextSibling)!==null)break;m=f,f=m.parentNode}m=p}n=l===-1||c===-1?null:{start:l,end:c}}else n=null}n=n||{start:0,end:0}}else n=null;for(sh={focusedElem:e,selectionRange:n},jd=!1,rt=t;rt!==null;)if(t=rt,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,rt=e;else for(;rt!==null;){t=rt;try{var y=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(y!==null){var b=y.memoizedProps,x=y.memoizedState,v=t.stateNode,g=v.getSnapshotBeforeUpdate(t.elementType===t.type?b:so(t.type,b),x);v.__reactInternalSnapshotBeforeUpdate=g}break;case 3:var h=t.stateNode.containerInfo;h.nodeType===1?h.textContent="":h.nodeType===9&&h.documentElement&&h.removeChild(h.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(He(163))}}catch($){vn(t,t.return,$)}if(e=t.sibling,e!==null){e.return=t.return,rt=e;break}rt=t.return}return y=yS,yS=!1,y}function Fl(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var o=r=r.next;do{if((o.tag&e)===e){var s=o.destroy;o.destroy=void 0,s!==void 0&&$h(t,n,s)}o=o.next}while(o!==r)}}function Uf(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Sh(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function pI(e){var t=e.alternate;t!==null&&(e.alternate=null,pI(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Po],delete t[mc],delete t[lh],delete t[T3],delete t[M3])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function gI(e){return e.tag===5||e.tag===3||e.tag===4}function vS(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||gI(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ch(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=kd));else if(r!==4&&(e=e.child,e!==null))for(Ch(e,t,n),e=e.sibling;e!==null;)Ch(e,t,n),e=e.sibling}function wh(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(wh(e,t,n),e=e.sibling;e!==null;)wh(e,t,n),e=e.sibling}var Wn=null,io=!1;function cs(e,t,n){for(n=n.child;n!==null;)hI(e,t,n),n=n.sibling}function hI(e,t,n){if(Ro&&typeof Ro.onCommitFiberUnmount=="function")try{Ro.onCommitFiberUnmount(kf,n)}catch{}switch(n.tag){case 5:er||ya(n,t);case 6:var r=Wn,o=io;Wn=null,cs(e,t,n),Wn=r,io=o,Wn!==null&&(io?(e=Wn,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Wn.removeChild(n.stateNode));break;case 18:Wn!==null&&(io?(e=Wn,n=n.stateNode,e.nodeType===8?yp(e.parentNode,n):e.nodeType===1&&yp(e,n),lc(e)):yp(Wn,n.stateNode));break;case 4:r=Wn,o=io,Wn=n.stateNode.containerInfo,io=!0,cs(e,t,n),Wn=r,io=o;break;case 0:case 11:case 14:case 15:if(!er&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){o=r=r.next;do{var s=o,i=s.destroy;s=s.tag,i!==void 0&&(s&2||s&4)&&$h(n,t,i),o=o.next}while(o!==r)}cs(e,t,n);break;case 1:if(!er&&(ya(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(l){vn(n,t,l)}cs(e,t,n);break;case 21:cs(e,t,n);break;case 22:n.mode&1?(er=(r=er)||n.memoizedState!==null,cs(e,t,n),er=r):cs(e,t,n);break;default:cs(e,t,n)}}function bS(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new q3),t.forEach(function(r){var o=rO.bind(null,e,r);n.has(r)||(n.add(r),r.then(o,o))})}}function no(e,t){var n=t.deletions;if(n!==null)for(var r=0;ro&&(o=i),r&=~s}if(r=o,r=xn()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Y3(r/1960))-r,10e?16:e,Ss===null)var r=!1;else{if(e=Ss,Ss=null,Qd=0,Ut&6)throw Error(He(331));var o=Ut;for(Ut|=4,rt=e.current;rt!==null;){var s=rt,i=s.child;if(rt.flags&16){var l=s.deletions;if(l!==null){for(var c=0;cxn()-wb?hi(e,0):Cb|=n),Sr(e,t)}function wI(e,t){t===0&&(e.mode&1?(t=vu,vu<<=1,!(vu&130023424)&&(vu=4194304)):t=1);var n=sr();e=rs(e,t),e!==null&&(Ac(e,t,n),Sr(e,n))}function nO(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),wI(e,n)}function rO(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,o=e.memoizedState;o!==null&&(n=o.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(He(314))}r!==null&&r.delete(t),wI(e,n)}var EI;EI=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||xr.current)br=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return br=!1,W3(e,t,n);br=!!(e.flags&131072)}else br=!1,un&&t.flags&1048576&&R2(t,Hd,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ud(e,t),e=t.pendingProps;var o=za(t,nr.current);Ia(t,n),o=yb(null,t,r,e,o,n);var s=vb();return t.flags|=1,typeof o=="object"&&o!==null&&typeof o.render=="function"&&o.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,$r(r)?(s=!0,Dd(t)):s=!1,t.memoizedState=o.state!==null&&o.state!==void 0?o.state:null,fb(t),o.updater=Kf,t.stateNode=o,o._reactInternals=t,ph(t,r,e,n),t=yh(null,t,r,!0,s,n)):(t.tag=0,un&&s&&sb(t),rr(null,t,o,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ud(e,t),e=t.pendingProps,o=r._init,r=o(r._payload),t.type=r,o=t.tag=sO(r),e=so(r,e),o){case 0:t=hh(null,t,r,e,n);break e;case 1:t=pS(null,t,r,e,n);break e;case 11:t=fS(null,t,r,e,n);break e;case 14:t=mS(null,t,r,so(r.type,e),n);break e}throw Error(He(306,r,""))}return t;case 0:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:so(r,o),hh(e,t,r,o,n);case 1:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:so(r,o),pS(e,t,r,o,n);case 3:e:{if(lI(t),e===null)throw Error(He(387));r=t.pendingProps,s=t.memoizedState,o=s.element,j2(e,t),Kd(t,r,null,n);var i=t.memoizedState;if(r=i.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:i.cache,pendingSuspenseBoundaries:i.pendingSuspenseBoundaries,transitions:i.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){o=ka(Error(He(423)),t),t=gS(e,t,r,n,o);break e}else if(r!==o){o=ka(Error(He(424)),t),t=gS(e,t,r,n,o);break e}else for(Mr=Rs(t.stateNode.containerInfo.firstChild),_r=t,un=!0,co=null,n=_2(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(ja(),r===o){t=os(e,t,n);break e}rr(e,t,r,n)}t=t.child}return t;case 5:return B2(t),e===null&&dh(t),r=t.type,o=t.pendingProps,s=e!==null?e.memoizedProps:null,i=o.children,ih(r,o)?i=null:s!==null&&ih(r,s)&&(t.flags|=32),aI(e,t),rr(e,t,i,n),t.child;case 6:return e===null&&dh(t),null;case 13:return cI(e,t,n);case 4:return mb(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Ba(t,null,r,n):rr(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:so(r,o),fS(e,t,r,o,n);case 7:return rr(e,t,t.pendingProps,n),t.child;case 8:return rr(e,t,t.pendingProps.children,n),t.child;case 12:return rr(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,o=t.pendingProps,s=t.memoizedProps,i=o.value,tn(Vd,r._currentValue),r._currentValue=i,s!==null)if(po(s.value,i)){if(s.children===o.children&&!xr.current){t=os(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var l=s.dependencies;if(l!==null){i=s.child;for(var c=l.firstContext;c!==null;){if(c.context===r){if(s.tag===1){c=Qo(-1,n&-n),c.tag=2;var u=s.updateQueue;if(u!==null){u=u.shared;var d=u.pending;d===null?c.next=c:(c.next=d.next,d.next=c),u.pending=c}}s.lanes|=n,c=s.alternate,c!==null&&(c.lanes|=n),fh(s.return,n,t),l.lanes|=n;break}c=c.next}}else if(s.tag===10)i=s.type===t.type?null:s.child;else if(s.tag===18){if(i=s.return,i===null)throw Error(He(341));i.lanes|=n,l=i.alternate,l!==null&&(l.lanes|=n),fh(i,n,t),i=s.sibling}else i=s.child;if(i!==null)i.return=s;else for(i=s;i!==null;){if(i===t){i=null;break}if(s=i.sibling,s!==null){s.return=i.return,i=s;break}i=i.return}s=i}rr(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=t.pendingProps.children,Ia(t,n),o=Yr(o),r=r(o),t.flags|=1,rr(e,t,r,n),t.child;case 14:return r=t.type,o=so(r,t.pendingProps),o=so(r.type,o),mS(e,t,r,o,n);case 15:return sI(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:so(r,o),ud(e,t),t.tag=1,$r(r)?(e=!0,Dd(t)):e=!1,Ia(t,n),nI(t,r,o),ph(t,r,o,n),yh(null,t,r,!0,e,n);case 19:return uI(e,t,n);case 22:return iI(e,t,n)}throw Error(He(156,t.tag))};function II(e,t){return ZE(e,t)}function oO(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Gr(e,t,n,r){return new oO(e,t,n,r)}function Nb(e){return e=e.prototype,!(!e||!e.isReactComponent)}function sO(e){if(typeof e=="function")return Nb(e)?1:0;if(e!=null){if(e=e.$$typeof,e===q0)return 11;if(e===G0)return 14}return 2}function _s(e,t){var n=e.alternate;return n===null?(n=Gr(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function md(e,t,n,r,o,s){var i=2;if(r=e,typeof e=="function")Nb(e)&&(i=1);else if(typeof e=="string")i=5;else e:switch(e){case la:return yi(n.children,o,s,t);case U0:i=8,o|=8;break;case kg:return e=Gr(12,n,t,o|2),e.elementType=kg,e.lanes=s,e;case Ag:return e=Gr(13,n,t,o),e.elementType=Ag,e.lanes=s,e;case Dg:return e=Gr(19,n,t,o),e.elementType=Dg,e.lanes=s,e;case BE:return Gf(n,o,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case zE:i=10;break e;case jE:i=9;break e;case q0:i=11;break e;case G0:i=14;break e;case ms:i=16,r=null;break e}throw Error(He(130,e==null?e:typeof e,""))}return t=Gr(i,n,t,o),t.elementType=e,t.type=r,t.lanes=s,t}function yi(e,t,n,r){return e=Gr(7,e,r,t),e.lanes=n,e}function Gf(e,t,n,r){return e=Gr(22,e,r,t),e.elementType=BE,e.lanes=n,e.stateNode={isHidden:!1},e}function Ep(e,t,n){return e=Gr(6,e,null,t),e.lanes=n,e}function Ip(e,t,n){return t=Gr(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function iO(e,t,n,r,o){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ip(0),this.expirationTimes=ip(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ip(0),this.identifierPrefix=r,this.onRecoverableError=o,this.mutableSourceEagerHydrationData=null}function Rb(e,t,n,r,o,s,i,l,c){return e=new iO(e,t,n,l,c),t===1?(t=1,s===!0&&(t|=8)):t=0,s=Gr(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},fb(s),e}function aO(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(TI)}catch(e){console.error(e)}}TI(),TE.exports=Lr;var ss=TE.exports,MI,PS=ss;MI=Bg.createRoot=PS.createRoot,Bg.hydrateRoot=PS.hydrateRoot;const vt=e=>{const t=a.useRef(e);return t.current=e,a.useCallback((...r)=>{var o;return(o=t.current)==null?void 0:o.call(t,...r)},[])};function lr(){return!!(typeof window<"u"&&window.document&&window.document.createElement)}const NS=lr()?a.useLayoutEffect:a.useEffect,It=(e,t)=>{const n=a.useRef(!0);NS(()=>e(n.current),t),NS(()=>(n.current=!1,()=>{n.current=!0}),[])},pd=(e,t)=>{It(n=>{if(!n)return e()},t)},_b=e=>{const t=a.useRef(!1),[n,r]=a.useState(e);a.useEffect(()=>(t.current=!1,()=>{t.current=!0}),[]);function o(s,i){i&&t.current||r(s)}return[n,o]};function nn(e,t){const[n,r]=a.useState(e),o=t!==void 0?t:n;return It(s=>{s||r(t)},[t]),[o,r]}let OI=e=>+setTimeout(e,16),_I=e=>clearTimeout(e);typeof window<"u"&&"requestAnimationFrame"in window&&(OI=e=>window.requestAnimationFrame(e),_I=e=>window.cancelAnimationFrame(e));let RS=0;const zb=new Map;function zI(e){zb.delete(e)}const Ct=(e,t=1)=>{RS+=1;const n=RS;function r(o){if(o===0)zI(n),e();else{const s=OI(()=>{r(o-1)});zb.set(n,s)}}return r(t),n};Ct.cancel=e=>{const t=zb.get(e);return zI(e),_I(t)};function fO(){return{...t8}.useId}let TS=0;function jI(e,t){const r=String(t).replace(/[^a-zA-Z0-9_.:-]/g,"-");return`${e}-${r}`}const MS=fO(),jo=MS?function(t){const n=MS();return t||n}:function(t){const[n,r]=a.useState("ssr-id");return a.useEffect(()=>{const o=TS;TS+=1,r(`rc_unique_${o}`)},[]),t||n};function _i(e,t,n){const r=a.useRef({});return(!("value"in r.current)||n(r.current.condition,t))&&(r.current.value=e(),r.current.condition=t),r.current.value}function mO(e){const[,t]=a.useReducer(s=>s+1,0),n=a.useRef(e),r=vt(()=>n.current),o=vt(s=>{n.current=typeof s=="function"?s(n.current):s,t()});return[r,o]}var BI={exports:{}},Zt={};/** + * @license React + * react-is.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var jb=Symbol.for("react.transitional.element"),Bb=Symbol.for("react.portal"),Zf=Symbol.for("react.fragment"),em=Symbol.for("react.strict_mode"),tm=Symbol.for("react.profiler"),nm=Symbol.for("react.consumer"),rm=Symbol.for("react.context"),om=Symbol.for("react.forward_ref"),sm=Symbol.for("react.suspense"),im=Symbol.for("react.suspense_list"),am=Symbol.for("react.memo"),lm=Symbol.for("react.lazy"),pO=Symbol.for("react.view_transition"),gO=Symbol.for("react.client.reference");function eo(e){if(typeof e=="object"&&e!==null){var t=e.$$typeof;switch(t){case jb:switch(e=e.type,e){case Zf:case tm:case em:case sm:case im:case pO:return e;default:switch(e=e&&e.$$typeof,e){case rm:case om:case lm:case am:return e;case nm:return e;default:return t}}case Bb:return t}}}Zt.ContextConsumer=nm;Zt.ContextProvider=rm;Zt.Element=jb;Zt.ForwardRef=om;Zt.Fragment=Zf;Zt.Lazy=lm;Zt.Memo=am;Zt.Portal=Bb;Zt.Profiler=tm;Zt.StrictMode=em;Zt.Suspense=sm;Zt.SuspenseList=im;Zt.isContextConsumer=function(e){return eo(e)===nm};Zt.isContextProvider=function(e){return eo(e)===rm};Zt.isElement=function(e){return typeof e=="object"&&e!==null&&e.$$typeof===jb};Zt.isForwardRef=function(e){return eo(e)===om};Zt.isFragment=function(e){return eo(e)===Zf};Zt.isLazy=function(e){return eo(e)===lm};Zt.isMemo=function(e){return eo(e)===am};Zt.isPortal=function(e){return eo(e)===Bb};Zt.isProfiler=function(e){return eo(e)===tm};Zt.isStrictMode=function(e){return eo(e)===em};Zt.isSuspense=function(e){return eo(e)===sm};Zt.isSuspenseList=function(e){return eo(e)===im};Zt.isValidElementType=function(e){return typeof e=="string"||typeof e=="function"||e===Zf||e===tm||e===em||e===sm||e===im||typeof e=="object"&&e!==null&&(e.$$typeof===lm||e.$$typeof===am||e.$$typeof===rm||e.$$typeof===nm||e.$$typeof===om||e.$$typeof===gO||e.getModuleId!==void 0)};Zt.typeOf=eo;BI.exports=Zt;var Pp=BI.exports;const hO=Symbol.for("react.element"),yO=Symbol.for("react.transitional.element"),vO=Symbol.for("react.fragment");function LI(e){return e&&typeof e=="object"&&(e.$$typeof===hO||e.$$typeof===yO)&&e.type===vO}const bO=Number(a.version.split(".")[0]),Rh=(e,t)=>{typeof e=="function"?e(t):typeof e=="object"&&e&&"current"in e&&(e.current=t)},Tn=(...e)=>{const t=e.filter(Boolean);return t.length<=1?t[0]:n=>{e.forEach(r=>{Rh(r,n)})}},$o=(...e)=>_i(()=>Tn(...e),e,(t,n)=>t.length!==n.length||t.every((r,o)=>r!==n[o])),is=e=>{var n,r;if(!e)return!1;if(Lb(e)&&bO>=19)return!0;const t=Pp.isMemo(e)?e.type.type:e.type;return!(typeof t=="function"&&!((n=t.prototype)!=null&&n.render)&&t.$$typeof!==Pp.ForwardRef||typeof e=="function"&&!((r=e.prototype)!=null&&r.render)&&e.$$typeof!==Pp.ForwardRef)};function Lb(e){return a.isValidElement(e)&&!LI(e)}const kI=e=>Lb(e)&&is(e),Bo=e=>{if(e&&Lb(e)){const t=e;return t.props.propertyIsEnumerable("ref")?t.props.ref:t.ref}return null};function Th(e,t){if(!e)return!1;if(e.contains)return e.contains(t);let n=t;for(;n;){if(n===e)return!0;n=n.parentNode}return!1}const OS="data-rc-order",_S="data-rc-priority",xO="rc-util-key",Mh=new Map;function AI({mark:e}={}){return e?e.startsWith("data-")?e:`data-${e}`:xO}function cm(e){return e.attachTo?e.attachTo:document.querySelector("head")||document.body}function $O(e){return e==="queue"?"prependQueue":e?"prepend":"append"}function kb(e){return Array.from((Mh.get(e)||e).children).filter(t=>t.tagName==="STYLE")}function DI(e,t={}){if(!lr())return null;const{csp:n,prepend:r,priority:o=0}=t,s=$O(r),i=s==="prependQueue",l=document.createElement("style");l.setAttribute(OS,s),i&&o&&l.setAttribute(_S,`${o}`),n!=null&&n.nonce&&(l.nonce=n==null?void 0:n.nonce),l.innerHTML=e;const c=cm(t),{firstChild:u}=c;if(r){if(i){const d=(t.styles||kb(c)).filter(m=>{if(!["prepend","prependQueue"].includes(m.getAttribute(OS)))return!1;const f=Number(m.getAttribute(_S)||0);return o>=f});if(d.length)return c.insertBefore(l,d[d.length-1].nextSibling),l}c.insertBefore(l,u)}else c.appendChild(l);return l}function FI(e,t={}){let{styles:n}=t;return n||(n=kb(cm(t))),n.find(r=>r.getAttribute(AI(t))===e)}function xc(e,t={}){const n=FI(e,t);n&&cm(t).removeChild(n)}function SO(e,t){const n=Mh.get(e);if(!n||!Th(document,n)){const r=DI("",t),{parentNode:o}=r;Mh.set(e,o),e.removeChild(r)}}function vi(e,t,n={}){var c,u,d;const r=cm(n),o=kb(r),s={...n,styles:o};SO(r,s);const i=FI(t,s);if(i)return(c=s.csp)!=null&&c.nonce&&i.nonce!==((u=s.csp)==null?void 0:u.nonce)&&(i.nonce=(d=s.csp)==null?void 0:d.nonce),i.innerHTML!==e&&(i.innerHTML=e),i;const l=DI(e,s);return l.setAttribute(AI(s),t),l}function Da(e){return e instanceof HTMLElement||e instanceof SVGElement}function go(e){return e&&typeof e=="object"&&Da(e.nativeElement)?e.nativeElement:Da(e)?e:null}const Vc=e=>{if(!e)return!1;if(e instanceof Element){if(e.offsetParent)return!0;if(e.getBBox){const{width:t,height:n}=e.getBBox();if(t||n)return!0}if(e.getBoundingClientRect){const{width:t,height:n}=e.getBoundingClientRect();if(t||n)return!0}}return!1};function zS(e,t=!1){if(Vc(e)){const n=e.nodeName.toLowerCase(),r=["input","select","textarea","button"].includes(n)||e.isContentEditable||n==="a"&&!!e.getAttribute("href"),o=e.getAttribute("tabindex"),s=Number(o);let i=null;return o&&!Number.isNaN(s)?i=s:r&&i===null&&(i=0),r&&e.disabled&&(i=null),i!==null&&(i>=0||t&&i<0)}return!1}function Ab(e,t=!1){const n=[...e.querySelectorAll("*")].filter(r=>zS(r,t));return zS(e,t)&&n.unshift(e),n}function Db(e,t){if(!e)return;e.focus(t);const{cursor:n}=t||{};if(n&&(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)){const r=e.value.length;switch(n){case"start":e.setSelectionRange(0,0);break;case"end":e.setSelectionRange(r,r);break;default:e.setSelectionRange(0,r)}}}let Na=null,gs=[];const Oh=new Map,Fb=new Map;function Hb(){return gs[gs.length-1]}function CO(e){const t=Hb();if(e&&t){let n;for(const[o,s]of Oh.entries())if(s===t){n=o;break}const r=Fb.get(n);return!!r&&(r===e||r.contains(e))}return!1}function wO(e){const{activeElement:t}=document;return e===t||e.contains(t)}function Np(){const e=Hb(),{activeElement:t}=document;if(!CO(t))if(e&&!wO(e)){const n=Ab(e),r=n.includes(Na)?Na:n[0];r==null||r.focus({preventScroll:!0})}else Na=t}function jS(e){if(e.key==="Tab"){const{activeElement:t}=document,n=Hb(),r=Ab(n),o=r[r.length-1];e.shiftKey&&t===r[0]?Na=o:!e.shiftKey&&t===o&&(Na=r[0])}}function EO(e,t){return e&&(Oh.set(t,e),gs=gs.filter(n=>n!==e),gs.push(e),window.addEventListener("focusin",Np),window.addEventListener("keydown",jS,!0),Np()),()=>{Na=null,gs=gs.filter(n=>n!==e),Oh.delete(t),Fb.delete(t),gs.length===0&&(window.removeEventListener("focusin",Np),window.removeEventListener("keydown",jS,!0))}}function IO(e,t){const n=a.useRef(0),[r,o]=a.useState(0);a.useEffect(()=>{n.current=0},t),a.useEffect(()=>{const[s,i]=e(n.current);return i||(n.current+=1,o(l=>l+1)),s},[...t,r])}function PO(e,t){const n=jo(),r=a.useRef(t);return r.current=t,IO(i=>{if(!e)return[void 0,!0];const l=r.current();return l?[EO(l,n),!0]:[void 0,i>=1]},[n,e]),[i=>{i&&Fb.set(n,i)}]}function HI(e){var t;return(t=e==null?void 0:e.getRootNode)==null?void 0:t.call(e)}function NO(e){return HI(e)instanceof ShadowRoot}function _h(e){return NO(e)?HI(e):null}const RO=e=>{if(lr()&&window.document.documentElement){const t=Array.isArray(e)?e:[e],{documentElement:n}=window.document;return t.some(r=>r in n.style)}return!1};function BS(e,t){return RO(e)}const nt={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,CAPS_LOCK:20,ESC:27,SPACE:32,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,N:78,P:80,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,SEMICOLON:186,EQUALS:187,WIN_KEY:224};let Rp;function VI(e){const t=`rc-scrollbar-measure-${Math.random().toString(36).substring(7)}`,n=document.createElement("div");n.id=t;const r=n.style;r.position="absolute",r.left="0",r.top="0",r.width="100px",r.height="100px",r.overflow="scroll";let o,s;if(e){const c=getComputedStyle(e);r.scrollbarColor=c.scrollbarColor,r.scrollbarWidth=c.scrollbarWidth;const u=getComputedStyle(e,"::-webkit-scrollbar"),d=parseInt(u.width,10),m=parseInt(u.height,10);try{const f=d?`width: ${u.width};`:"",p=m?`height: ${u.height};`:"";vi(` +#${t}::-webkit-scrollbar { +${f} +${p} +}`,t)}catch(f){console.error(f),o=d,s=m}}document.body.appendChild(n);const i=e&&o&&!Number.isNaN(o)?o:n.offsetWidth-n.clientWidth,l=e&&s&&!Number.isNaN(s)?s:n.offsetHeight-n.clientHeight;return document.body.removeChild(n),xc(t),{width:i,height:l}}function LS(e){return typeof document>"u"?0:(Rp===void 0&&(Rp=VI()),Rp.width)}function zh(e){return typeof document>"u"||!e||!(e instanceof Element)?{width:0,height:0}:VI(e)}let jh={};const TO=e=>{};function MO(e,t){}function OO(e,t){}function _O(){jh={}}function WI(e,t,n){!t&&!jh[n]&&(e(!1,n),jh[n]=!0)}function fn(e,t){WI(MO,e,t)}function zO(e,t){WI(OO,e,t)}fn.preMessage=TO;fn.resetWarned=_O;fn.noteOnce=zO;function ho(e,t,n=!1){const r=new Set;function o(s,i,l=1){const c=r.has(s);if(fn(!c,"Warning: There may be circular references"),c)return!1;if(s===i)return!0;if(n&&l>1)return!1;r.add(s);const u=l+1;if(Array.isArray(s)){if(!Array.isArray(i)||s.length!==i.length)return!1;for(let d=0;do(s[m],i[m],u))}return!1}return o(e,t)}var um={exports:{}};um.exports=Vb;um.exports.isMobile=Vb;um.exports.default=Vb;const jO=/(android|bb\d+|meego).+mobile|armv7l|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|redmi|series[46]0|samsungbrowser.*mobile|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i,BO=/CrOS/,LO=/android|ipad|playbook|silk/i;function Vb(e){e||(e={});let t=e.ua;if(!t&&typeof navigator<"u"&&(t=navigator.userAgent),t&&t.headers&&typeof t.headers["user-agent"]=="string"&&(t=t.headers["user-agent"]),typeof t!="string")return!1;let n=jO.test(t)&&!BO.test(t)||!!e.tablet&&LO.test(t);return!n&&e.tablet&&e.featureDetect&&navigator&&navigator.maxTouchPoints>1&&t.indexOf("Macintosh")!==-1&&t.indexOf("Safari")!==-1&&(n=!0),n}var kO=um.exports;const AO=k0(kO);let Tp;const DO=()=>(typeof Tp>"u"&&(Tp=AO()),Tp);function Dt(e,t){const n=Object.assign({},e);return Array.isArray(t)&&t.forEach(r=>{delete n[r]}),n}const FO=`accept acceptCharset accessKey action allowFullScreen allowTransparency + alt async autoComplete autoFocus autoPlay capture cellPadding cellSpacing challenge + charSet checked classID className colSpan cols content contentEditable contextMenu + controls coords crossOrigin data dateTime default defer dir disabled download draggable + encType form formAction formEncType formMethod formNoValidate formTarget frameBorder + headers height hidden high href hrefLang htmlFor httpEquiv icon id inputMode integrity + is keyParams keyType kind label lang list loop low manifest marginHeight marginWidth max maxLength media + mediaGroup method min minLength multiple muted name noValidate nonce open + optimum pattern placeholder poster preload radioGroup readOnly rel required + reversed role rowSpan rows sandbox scope scoped scrolling seamless selected + shape size sizes span spellCheck src srcDoc srcLang srcSet start step style + summary tabIndex target title type useMap value width wmode wrap`,HO=`onCopy onCut onPaste onCompositionEnd onCompositionStart onCompositionUpdate onKeyDown + onKeyPress onKeyUp onFocus onBlur onChange onInput onSubmit onClick onContextMenu onDoubleClick + onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown + onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp onSelect onTouchCancel + onTouchEnd onTouchMove onTouchStart onScroll onWheel onAbort onCanPlay onCanPlayThrough + onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata + onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting onLoad + onPointerDown onPointerMove onPointerUp onPointerCancel onPointerEnter onPointerLeave onPointerOver onPointerOut onGotPointerCapture onLostPointerCapture + onAnimationStart onAnimationEnd onAnimationIteration + onTransitionEnd onTransitionRun onTransitionStart onTransitionCancel + onBeforeInput onReset onInvalid + onAuxClick onToggle onBeforeToggle onCancel onClose onResize onScrollEnd`,VO=`${FO} ${HO}`.split(/[\s\n]+/),WO="aria-",KO="data-";function kS(e,t){return e.indexOf(t)===0}function Nn(e,t=!1){let n;t===!1?n={aria:!0,data:!0,attr:!0}:t===!0?n={aria:!0}:n={...t};const r={};return Object.keys(e).forEach(o=>{(n.aria&&(o==="role"||kS(o,WO))||n.data&&kS(o,KO)||n.attr&&VO.includes(o))&&(r[o]=e[o])}),r}function UO(e,t){return typeof Proxy<"u"&&e?new Proxy(e,{get(n,r){if(t[r])return t[r];const o=n[r];return typeof o=="function"?o.bind(n):o}}):e}function zn(e,t={}){let n=[];return J.Children.forEach(e,r=>{r==null&&!t.keepEmpty||(Array.isArray(r)?n=n.concat(zn(r)):LI(r)&&r.props?n=n.concat(zn(r.props.children,t)):n.push(r))}),n}function Fa(...e){const t={};for(const n of e)if(n)for(const r of Object.keys(n))n[r]!==void 0&&(t[r]=n[r]);return t}function Kn(e,t){let n=e;for(let r=0;r"u"?Object.keys:Reflect.ownKeys;function XO(e,t={}){const{prepareArray:n}=t,r=n||(()=>[]);let o=AS(e[0]);return e.forEach(s=>{function i(l,c){const u=new Set(c),d=Kn(s,l),m=Array.isArray(d);if(m||qO(d)){if(!u.has(d)){u.add(d);const f=Kn(o,l);m?o=hr(o,l,r(f,d)):(!f||typeof f!="object")&&(o=hr(o,l,AS(d))),GO(d).forEach(p=>{Object.getOwnPropertyDescriptor(d,p).enumerable&&i([...l,p],u)})}}else o=hr(o,l,d)}i([])}),o}function ba(...e){return XO(e)}const ef="__rc_react_root__";function Wb(e,t){const n=t[ef]||MI(t);n.render(e),t[ef]=n}async function UI(e){return Promise.resolve().then(()=>{var t;(t=e[ef])==null||t.unmount(),delete e[ef]})}function YO(){}const QO=a.createContext({}),yo=()=>{const e=()=>{};return e.deprecated=YO,e},Bh=a.createContext(null);function JO({children:e,onBatchResize:t}){const n=a.useRef(0),r=a.useRef([]),o=a.useContext(Bh),s=a.useCallback((i,l,c)=>{n.current+=1;const u=n.current;r.current.push({size:i,element:l,data:c}),Promise.resolve().then(()=>{u===n.current&&(t==null||t(r.current),r.current=[])}),o==null||o(i,l,c)},[t,o]);return a.createElement(Bh.Provider,{value:s},e)}const Cs=new Map;function ZO(e){e.forEach(t=>{var r;const{target:n}=t;(r=Cs.get(n))==null||r.forEach(o=>o(n))})}let Mp;function qI(){return Mp||(Mp=new ResizeObserver(ZO)),Mp}function e_(e,t){Cs.has(e)||(Cs.set(e,new Set),qI().observe(e)),Cs.get(e).add(t)}function t_(e,t){Cs.has(e)&&(Cs.get(e).delete(t),Cs.get(e).size||(qI().unobserve(e),Cs.delete(e)))}function GI(e,t,n,r){const o=a.useRef({width:-1,height:-1,offsetWidth:-1,offsetHeight:-1}),s=vt(c=>{const{width:u,height:d}=c.getBoundingClientRect(),{offsetWidth:m,offsetHeight:f}=c,p=Math.floor(u),y=Math.floor(d);if(o.current.width!==p||o.current.height!==y||o.current.offsetWidth!==m||o.current.offsetHeight!==f){const b={width:p,height:y,offsetWidth:m,offsetHeight:f};o.current=b;const x=m===Math.round(u)?u:m,v=f===Math.round(d)?d:f,g={...b,offsetWidth:x,offsetHeight:v};r==null||r(g,c),Promise.resolve().then(()=>{n==null||n(g,c)})}}),i=typeof t=="function",l=a.useRef(0);a.useEffect(()=>{const c=i?t():t;return c&&e?e_(c,s):e&&i&&(l.current+=1),()=>{c&&t_(c,s)}},[e,i?l.current:t])}function n_(e,t){const{children:n,disabled:r,onResize:o,data:s}=e,i=a.useRef(null),l=a.useContext(Bh),c=typeof n=="function",u=c?n(i):n,d=!c&&a.isValidElement(u)&&is(u),m=d?Bo(u):null,f=$o(m,i),p=()=>go(i.current);return a.useImperativeHandle(t,()=>p()),GI(!r,p,o,(y,b)=>{l==null||l(y,b,s)}),d?a.cloneElement(u,{ref:f}):u}const r_=a.forwardRef(n_);function Lh(){return Lh=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const i=(o==null?void 0:o.key)||`${o_}-${s}`;return a.createElement(r_,Lh({},e,{key:i,ref:s===0?t:void 0}),o)})}const ir=a.forwardRef(s_);ir.Collection=JO;function XI(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;te.length)&&(t=e.length);for(var n=0,r=Array(t);nt||(e?`${$c}-${e}`:$c),ct=a.createContext({getPrefixCls:u_,iconPrefixCls:dm}),{Consumer:TZ}=ct,DS={};function Pt(e){const t=a.useContext(ct),{getPrefixCls:n,direction:r,getPopupContainer:o,renderEmpty:s}=t,i=t[e];return{classNames:DS,styles:DS,...i,getPrefixCls:n,direction:r,getPopupContainer:o,renderEmpty:s}}function Sc(e){for(var t=0,n,r=0,o=e.length;o>=4;++r,o-=4)n=e.charCodeAt(r)&255|(e.charCodeAt(++r)&255)<<8|(e.charCodeAt(++r)&255)<<16|(e.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,t=(n&65535)*1540483477+((n>>>16)*59797<<16)^(t&65535)*1540483477+((t>>>16)*59797<<16);switch(o){case 3:t^=(e.charCodeAt(r+2)&255)<<16;case 2:t^=(e.charCodeAt(r+1)&255)<<8;case 1:t^=e.charCodeAt(r)&255,t=(t&65535)*1540483477+((t>>>16)*59797<<16)}return t^=t>>>13,t=(t&65535)*1540483477+((t>>>16)*59797<<16),((t^t>>>15)>>>0).toString(36)}const d_="%";function Ah(e){return e.join(d_)}let FS=0;class f_{constructor(t){fe(this,"instanceId");fe(this,"cache",new Map);fe(this,"updateTimes",new Map);fe(this,"extracted",new Set);this.instanceId=t}get(t){return this.opGet(Ah(t))}opGet(t){return this.cache.get(t)||null}update(t,n){return this.opUpdate(Ah(t),n)}opUpdate(t,n){const r=this.cache.get(t),o=n(r);o===null?(this.cache.delete(t),this.updateTimes.delete(t)):(this.cache.set(t,o),this.updateTimes.set(t,FS),FS+=1)}}const Kb="data-token-hash",Jo="data-css-hash",qo="__cssinjs_instance__";function m_(){const e=Math.random().toString(12).slice(2);if(typeof document<"u"&&document.head&&document.body){const t=document.body.querySelectorAll(`style[${Jo}]`)||[],{firstChild:n}=document.head;Array.from(t).forEach(o=>{o[qo]||(o[qo]=e),o[qo]===e&&document.head.insertBefore(o,n)});const r={};Array.from(document.querySelectorAll(`style[${Jo}]`)).forEach(o=>{var i;const s=o.getAttribute(Jo);r[s]?o[qo]===e&&((i=o.parentNode)==null||i.removeChild(o)):r[s]=!0})}return new f_(e)}const Wc=a.createContext({hashPriority:"low",cache:m_(),defaultCache:!0,autoPrefix:!1});function p_(e,t){if(e.length!==t.length)return!1;for(let n=0;n{var s;r?r=(s=r==null?void 0:r.map)==null?void 0:s.get(o):r=void 0}),r!=null&&r.value&&n&&(r.value[1]=this.cacheCallTimes++),r==null?void 0:r.value}get(t){var n;return(n=this.internalGet(t,!0))==null?void 0:n[0]}has(t){return!!this.internalGet(t)}set(t,n){if(!this.has(t)){if(this.size()+1>$a.MAX_CACHE_SIZE+$a.MAX_CACHE_OFFSET){const[o]=this.keys.reduce((s,i)=>{const[,l]=s;return this.internalGet(i)[1]{if(s===t.length-1)r.set(o,{value:[n,this.cacheCallTimes++]});else{const i=r.get(o);i?i.map||(i.map=new Map):r.set(o,{map:new Map}),r=r.get(o).map}})}deleteByPath(t,n){var s;const r=t.get(n[0]);if(n.length===1)return r.map?t.set(n[0],{map:r.map}):t.delete(n[0]),(s=r.value)==null?void 0:s[0];const o=this.deleteByPath(r.map,n.slice(1));return(!r.map||r.map.size===0)&&!r.value&&t.delete(n[0]),o}delete(t){if(this.has(t))return this.keys=this.keys.filter(n=>!p_(n,t)),this.deleteByPath(this.cache,t)}};fe($a,"MAX_CACHE_SIZE",20),fe($a,"MAX_CACHE_OFFSET",5);let Dh=$a,HS=0;class QI{constructor(t){fe(this,"derivatives");fe(this,"id");this.derivatives=Array.isArray(t)?t:[t],this.id=HS,t.length===0&&(t.length>0,void 0),HS+=1}getDerivativeToken(t){return this.derivatives.reduce((n,r)=>r(t,n),void 0)}}const Op=new Dh;function tf(e){const t=Array.isArray(e)?e:[e];return Op.has(t)||Op.set(t,new QI(t)),Op.get(t)}const g_=new WeakMap,_p={};function h_(e,t){let n=g_;for(let r=0;r{const r=e[n];t+=n,r instanceof QI?t+=r.id:r&&typeof r=="object"?t+=Wl(r):t+=r}),t=Sc(t),VS.set(e,t)),t}function y_(e,t){return Sc(`${t}_${Wl(e)}`)}const Fh=lr();function G(e){return typeof e=="number"?`${e}px`:e}function JI(e){const{hashCls:t,hashPriority:n="low"}=e||{};if(!t)return"";const r=`.${t}`;return n==="low"?`:where(${r})`:r}const v_=e=>e!=null;function Ub(e,t){const n=typeof t=="function"?t():t;return n?{...e,csp:{...e.csp,nonce:n}}:e}const gd=(e,t="")=>`--${t?`${t}-`:""}${e}`.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z]+)([A-Z][a-z0-9]+)/g,"$1-$2").replace(/([a-z])([A-Z0-9])/g,"$1-$2").toLowerCase(),b_=(e,t,n)=>{const{hashCls:r,hashPriority:o="low",scope:s}=n||{};if(!Object.keys(e).length)return"";const i=`${JI({hashCls:r,hashPriority:o})}.${t}`,l=[s].flat().filter(Boolean);return`${l.length?l.map(u=>`${i}.${u}`).join(", "):i}{${Object.entries(e).map(([u,d])=>`${u}:${d};`).join("")}}`},ZI=(e,t,n)=>{const{hashCls:r,hashPriority:o="low",prefix:s,unitless:i,ignore:l,preserve:c}=n||{},u={},d={};return Object.entries(e).forEach(([m,f])=>{if(c!=null&&c[m])d[m]=f;else if((typeof f=="string"||typeof f=="number")&&!(l!=null&&l[m])){const p=gd(m,s);u[p]=typeof f=="number"&&!(i!=null&&i[m])?`${f}px`:String(f),d[m]=`var(${p})`}}),[d,b_(u,t,{scope:n==null?void 0:n.scope,hashCls:r,hashPriority:o})]},Tu=new Map;function qb(e,t,n,r,o){const{cache:s}=a.useContext(Wc),i=[e,...t],l=Ah(i),c=m=>{s.opUpdate(l,f=>{const[p=0,y]=f||[void 0,void 0],x=y||n(),v=[p,x];return m?m(v):v})};a.useMemo(()=>{c()},[l]);const d=s.opGet(l)[1];return a.useInsertionEffect(()=>(c(([m,f])=>[m+1,f]),Tu.has(l)||(o==null||o(d),Tu.set(l,!0),Promise.resolve().then(()=>{Tu.delete(l)})),()=>{s.opUpdate(l,m=>{const[f=0,p]=m||[];return f-1===0?(r==null||r(p,!1),Tu.delete(l),null):[f-1,p]})}),[l]),d}const x_={},$_="css",ni=new Map;function S_(e){ni.set(e,(ni.get(e)||0)+1)}function C_(e,t){typeof document<"u"&&document.querySelectorAll(`style[${Kb}="${e}"]`).forEach(r=>{var o;r[qo]===t&&((o=r.parentNode)==null||o.removeChild(r))})}const w_=-1;function E_(e,t){ni.set(e,(ni.get(e)||0)-1);const n=new Set;ni.forEach((r,o)=>{r<=0&&n.add(o)}),ni.size-n.size>w_&&n.forEach(r=>{C_(r,t),ni.delete(r)})}const eP=(e,t,n,r)=>{let s={...n.getDerivativeToken(e),...t};return r&&(s=r(s)),s},I_="token";function P_(e,t,n){const{cache:{instanceId:r},container:o,hashPriority:s}=a.useContext(Wc),{salt:i="",override:l=x_,formatToken:c,getComputedToken:u,cssVar:d,nonce:m}=n,f=h_(()=>Object.assign({},...t),t),p=Wl(f),y=Wl(l),b=Wl(d);return qb(I_,[i,e.id,p,y,b],()=>{const v=u?u(f,l,e):eP(f,l,e,c),g={...v},h=`${i}_${d.prefix}`,$=Sc(h),C=`${$_}-${$}`;g._tokenKey=y_(g,h);const[N,S]=ZI(v,d.key,{prefix:d.prefix,ignore:d.ignore,unitless:d.unitless,preserve:d.preserve,hashPriority:s,hashCls:d.hashed?C:void 0});return N._hashId=$,S_(d.key),[N,C,g,S,d.key]},([,,,,v])=>{E_(v,r)},([,,,v,g])=>{if(!v)return;let h={mark:Jo,prepend:"queue",attachTo:o,priority:-999};h=Ub(h,m);const $=vi(v,Sc(`css-var-${g}`),h);$[qo]=r,$.setAttribute(Kb,g)})}var N_={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},an="-ms-",Kl="-moz-",Xt="-webkit-",tP="comm",Gb="rule",Xb="decl",R_="@import",T_="@namespace",nP="@keyframes",M_="@layer",O_=Math.abs,Ul=String.fromCharCode,Hh=Object.assign;function __(e,t){return On(e,0)^45?(((t<<2^On(e,0))<<2^On(e,1))<<2^On(e,2))<<2^On(e,3):0}function rP(e){return e.trim()}function Vo(e,t){return(e=t.exec(e))?e[0]:e}function zt(e,t,n){return e.replace(t,n)}function zp(e,t){return e.indexOf(t)}function On(e,t){return e.charCodeAt(t)|0}function Ii(e,t,n){return e.slice(t,n)}function ao(e){return e.length}function oP(e){return e.length}function Rl(e,t){return t.push(e),e}function z_(e,t){return e.map(t).join("")}function WS(e,t){return e.filter(function(n){return!Vo(n,t)})}var fm=1,Ha=1,sP=0,Jr=0,In=0,Za="";function mm(e,t,n,r,o,s,i,l){return{value:e,root:t,parent:n,type:r,props:o,children:s,line:fm,column:Ha,length:i,return:"",siblings:l}}function ds(e,t){return Hh(mm("",null,null,"",null,null,0,e.siblings),e,{length:-e.length},t)}function Ui(e){for(;e.root;)e=ds(e.root,{children:[e]});Rl(e,e.siblings)}function j_(){return In}function B_(){return In=Jr>0?On(Za,--Jr):0,Ha--,In===10&&(Ha=1,fm--),In}function mo(){return In=Jr2||Cc(In)>3?"":" "}function D_(e,t){for(;--t&&mo()&&!(In<48||In>102||In>57&&In<65||In>70&&In<97););return pm(e,hd()+(t<6&&ws()==32&&mo()==32))}function Vh(e){for(;mo();)switch(In){case e:return Jr;case 34:case 39:e!==34&&e!==39&&Vh(In);break;case 40:e===41&&Vh(e);break;case 92:mo();break}return Jr}function F_(e,t){for(;mo()&&e+In!==57;)if(e+In===84&&ws()===47)break;return"/*"+pm(t,Jr-1)+"*"+Ul(e===47?e:mo())}function H_(e){for(;!Cc(ws());)mo();return pm(e,Jr)}function KS(e){return k_(yd("",null,null,null,[""],e=L_(e),0,[0],e))}function yd(e,t,n,r,o,s,i,l,c){for(var u=0,d=0,m=i,f=0,p=0,y=0,b=1,x=1,v=1,g=0,h=0,$="",C=o,N=s,S=r,E=$;x;)switch(y=h,h=mo()){case 40:y!=108&&On(E,m-1)==58?(g++,E+="("):E+=jp(h);break;case 41:g--,E+=")";break;case 34:case 39:case 91:E+=jp(h);break;case 9:case 10:case 13:case 32:if(g>0){E+=Ul(h);break}E+=A_(y);break;case 92:E+=D_(hd()-1,7);continue;case 47:switch(ws()){case 42:case 47:Rl(V_(F_(mo(),hd()),t,n,c),c),(Cc(y||1)==5||Cc(ws()||1)==5)&&ao(E)&&Ii(E,-1,void 0)!==" "&&(E+=" ");break;default:E+="/"}break;case 123*b:l[u++]=ao(E)*v;case 125*b:case 59:case 0:if(g>0&&h){E+=Ul(h);break}switch(h){case 0:case 125:x=0;case 59+d:v==-1&&(E=zt(E,/\f/g,"")),p>0&&(ao(E)-m||b===0)&&Rl(p>32?qS(E+";",r,n,m-1,c):qS(zt(E," ","")+";",r,n,m-2,c),c);break;case 59:E+=";";default:if(Rl(S=US(E,t,n,u,d,o,l,$,C=[],N=[],m,s),s),h===123)if(d===0)yd(E,t,S,S,C,s,m,l,N);else{switch(f){case 99:if(On(E,3)===110)break;case 108:if(On(E,2)===97)break;default:d=0;case 100:case 109:case 115:}d?yd(e,S,S,r&&Rl(US(e,S,S,0,0,o,l,$,o,C=[],m,N),N),o,N,m,l,r?C:N):yd(E,S,S,S,[""],N,0,l,N)}}u=d=p=0,b=v=1,$=E="",m=i;break;case 58:m=1+ao(E),p=y;default:if(b<1){if(h==123)--b;else if(h==125&&b++==0&&B_()==125)continue}switch(E+=Ul(h),h*b){case 38:v=d>0?1:(E+="\f",-1);break;case 44:if(g>0)break;l[u++]=(ao(E)-1)*v,v=1;break;case 64:ws()===45&&(E+=jp(mo())),f=ws(),d=m=ao($=E+=H_(hd())),h++;break;case 45:y===45&&ao(E)==2&&(b=0)}}return s}function US(e,t,n,r,o,s,i,l,c,u,d,m){for(var f=o-1,p=o===0?s:[""],y=oP(p),b=0,x=0,v=0;b0?p[g]+" "+h:zt(h,/&\f/g,p[g])))&&(c[v++]=$);return mm(e,t,n,o===0?Gb:l,c,u,d,m)}function V_(e,t,n,r){return mm(e,t,n,tP,Ul(j_()),Ii(e,2,-2),0,r)}function qS(e,t,n,r,o){return mm(e,t,n,Xb,Ii(e,0,r),Ii(e,r+1,-1),r,o)}function iP(e,t,n){switch(__(e,t)){case 5103:return Xt+"print-"+e+e;case 5737:case 4201:case 3177:case 3433:case 1641:case 4457:case 2921:case 5572:case 6356:case 5844:case 3191:case 6645:case 3005:case 4215:case 6389:case 5109:case 5365:case 5621:case 3829:case 6391:case 5879:case 5623:case 6135:case 4599:return Xt+e+e;case 4855:return Xt+e.replace("add","source-over").replace("substract","source-out").replace("intersect","source-in").replace("exclude","xor")+e;case 4789:return Kl+e+e;case 5349:case 4246:case 4810:case 6968:case 2756:return Xt+e+Kl+e+an+e+e;case 5936:switch(On(e,t+11)){case 114:return Xt+e+an+zt(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return Xt+e+an+zt(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return Xt+e+an+zt(e,/[svh]\w+-[tblr]{2}/,"lr")+e}case 6828:case 4268:case 2903:return Xt+e+an+e+e;case 6165:return Xt+e+an+"flex-"+e+e;case 5187:return Xt+e+zt(e,/(\w+).+(:[^]+)/,Xt+"box-$1$2"+an+"flex-$1$2")+e;case 5443:return Xt+e+an+"flex-item-"+zt(e,/flex-|-self/g,"")+(Vo(e,/flex-|baseline/)?"":an+"grid-row-"+zt(e,/flex-|-self/g,""))+e;case 4675:return Xt+e+an+"flex-line-pack"+zt(e,/align-content|flex-|-self/g,"")+e;case 5548:return Xt+e+an+zt(e,"shrink","negative")+e;case 5292:return Xt+e+an+zt(e,"basis","preferred-size")+e;case 6060:return Xt+"box-"+zt(e,"-grow","")+Xt+e+an+zt(e,"grow","positive")+e;case 4554:return Xt+zt(e,/([^-])(transform)/g,"$1"+Xt+"$2")+e;case 6187:return zt(zt(zt(e,/(zoom-|grab)/,Xt+"$1"),/(image-set)/,Xt+"$1"),e,"")+e;case 5495:case 3959:return zt(e,/(image-set\([^]*)/,Xt+"$1$`$1");case 4968:return zt(zt(e,/(.+:)(flex-)?(.*)/,Xt+"box-pack:$3"+an+"flex-pack:$3"),/space-between/,"justify")+Xt+e+e;case 4200:if(!Vo(e,/flex-|baseline/))return an+"grid-column-align"+Ii(e,t)+e;break;case 2592:case 3360:return an+zt(e,"template-","")+e;case 4384:case 3616:return n&&n.some(function(r,o){return t=o,Vo(r.props,/grid-\w+-end/)})?~zp(e+(n=n[t].value),"span")?e:an+zt(e,"-start","")+e+an+"grid-row-span:"+(~zp(n,"span")?Vo(n,/\d+/):+Vo(n,/\d+/)-+Vo(e,/\d+/))+";":an+zt(e,"-start","")+e;case 4896:case 4128:return n&&n.some(function(r){return Vo(r.props,/grid-\w+-start/)})?e:an+zt(zt(e,"-end","-span"),"span ","")+e;case 4095:case 3583:case 4068:case 2532:return zt(e,/(.+)-inline(.+)/,Xt+"$1$2")+e;case 8116:case 7059:case 5753:case 5535:case 5445:case 5701:case 4933:case 4677:case 5533:case 5789:case 5021:case 4765:if(ao(e)-1-t>6)switch(On(e,t+1)){case 109:if(On(e,t+4)!==45)break;case 102:return zt(e,/(.+:)(.+)-([^]+)/,"$1"+Xt+"$2-$3$1"+Kl+(On(e,t+3)==108?"$3":"$2-$3"))+e;case 115:return~zp(e,"stretch")?iP(zt(e,"stretch","fill-available"),t,n)+e:e}break;case 5152:case 5920:return zt(e,/(.+?):(\d+)(\s*\/\s*(span)?\s*(\d+))?(.*)/,function(r,o,s,i,l,c,u){return an+o+":"+s+u+(i?an+o+"-span:"+(l?c:+c-+s)+u:"")+e});case 4949:if(On(e,t+6)===121)return zt(e,":",":"+Xt)+e;break;case 6444:switch(On(e,On(e,14)===45?18:11)){case 120:return zt(e,/(.+:)([^;\s!]+)(;|(\s+)?!.+)?/,"$1"+Xt+(On(e,14)===45?"inline-":"")+"box$3$1"+Xt+"$2$3$1"+an+"$2box$3")+e;case 100:return zt(e,":",":"+an)+e}break;case 5719:case 2647:case 2135:case 3927:case 2391:return zt(e,"scroll-","scroll-snap-")+e}return e}function wc(e,t){for(var n="",r=0;r-1&&!e.return)switch(e.type){case Xb:e.return=iP(e.value,e.length,n);return;case nP:return wc([ds(e,{value:zt(e.value,"@","@"+Xt)})],r);case Gb:if(e.length)return z_(n=e.props,function(o){switch(Vo(o,r=/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":Ui(ds(e,{props:[zt(o,/:(read-\w+)/,":"+Kl+"$1")]})),Ui(ds(e,{props:[o]})),Hh(e,{props:WS(n,r)});break;case"::placeholder":Ui(ds(e,{props:[zt(o,/:(plac\w+)/,":"+Xt+"input-$1")]})),Ui(ds(e,{props:[zt(o,/:(plac\w+)/,":"+Kl+"$1")]})),Ui(ds(e,{props:[zt(o,/:(plac\w+)/,an+"input-$1")]})),Ui(ds(e,{props:[o]})),Hh(e,{props:WS(n,r)});break}return""})}}const XS="data-ant-cssinjs-cache-path",aP="_FILE_STYLE__";let bi,lP=!0;function U_(){var e;if(!bi&&(bi={},lr())){const t=document.createElement("div");t.className=XS,t.style.position="fixed",t.style.visibility="hidden",t.style.top="-9999px",document.body.appendChild(t);let n=getComputedStyle(t).content||"";n=n.replace(/^"/,"").replace(/"$/,""),n.split(";").forEach(o=>{const[s,i]=o.split(":");bi[s]=i});const r=document.querySelector(`style[${XS}]`);r&&(lP=!1,(e=r.parentNode)==null||e.removeChild(r)),document.body.removeChild(t)}}function q_(e){return U_(),!!bi[e]}function G_(e){const t=bi[e];let n=null;if(t&&lr())if(lP)n=aP;else{const r=document.querySelector(`style[${Jo}="${bi[e]}"]`);r?n=r.innerHTML:delete bi[e]}return[n,t]}const X_="_skip_check_",cP="_multi_value_";function Bp(e,t){return(t?wc(KS(e),W_([K_,GS])):wc(KS(e),GS)).replace(/\{%%%\:[^;];}/g,";")}function Y_(e){return typeof e=="object"&&e&&(X_ in e||cP in e)}function YS(e,t,n="high"){if(!t)return e;const r=JI({hashCls:t,hashPriority:n});return e.split(",").map(s=>{var u;const i=s.trim().split(/\s+/);let l=i[0]||"";const c=((u=l.match(/^\w+/))==null?void 0:u[0])||"";return l=`${c}${r}${l.slice(c.length)}`,[l,...i.slice(1)].join(" ")}).join(",")}const Wh=(e,t={},{root:n,injectHash:r,parentSelectors:o}={root:!0,parentSelectors:[]})=>{const{hashId:s,layer:i,path:l,hashPriority:c,transformers:u=[],linters:d=[]}=t;let m="",f={};function p(x){const v=x.getName(s);if(!f[v]){const[g]=Wh(x.style,t,{root:!1,parentSelectors:o});f[v]=`@keyframes ${x.getName(s)}${g}`}}function y(x,v=[]){return x.forEach(g=>{Array.isArray(g)?y(g,v):g&&v.push(g)}),v}return y(Array.isArray(e)?e:[e]).forEach(x=>{const v=typeof x=="string"&&!n?{}:x;if(typeof v=="string")m+=`${v} +`;else if(v._keyframe)p(v);else{const g=u.reduce((h,$)=>{var C;return((C=$==null?void 0:$.visit)==null?void 0:C.call($,h))||h},v);Object.keys(g).forEach(h=>{const $=g[h];if(typeof $=="object"&&$&&(h!=="animationName"||!$._keyframe)&&!Y_($)){let C=!1,N=h.trim(),S=!1;(n||r)&&s?N.startsWith("@")?C=!0:N==="&"?N=YS("",s,c):N=YS(h,s,c):n&&!s&&(N==="&"||N==="")&&(N="",S=!0);const[E,w]=Wh($,t,{root:S,injectHash:C,parentSelectors:[...o,N]});f={...f,...w},m+=`${N}${E}`}else{let C=function(S,E){const w=S.replace(/[A-Z]/g,P=>`-${P.toLowerCase()}`);let R=E;!N_[S]&&typeof R=="number"&&R!==0&&(R=`${R}px`),S==="animationName"&&(E!=null&&E._keyframe)&&(p(E),R=E.getName(s)),m+=`${w}:${R};`};const N=($==null?void 0:$.value)??$;typeof $=="object"&&($!=null&&$[cP])&&Array.isArray(N)?N.forEach(S=>{C(h,S)}):v_(N)&&C(h,N)}})}}),n?i&&(m&&(m=`@layer ${i.name} {${m}}`),i.dependencies&&(f[`@layer ${i.name}`]=i.dependencies.map(x=>`@layer ${x}, ${i.name};`).join(` +`))):m=`{${m}}`,[m,f]};function uP(e,t){return Sc(`${e.join("%")}${t}`)}const Q_="style";function Kh(e,t){const{path:n,hashId:r,layer:o,nonce:s,clientOnly:i,order:l=0}=e,{mock:c,hashPriority:u,container:d,transformers:m,linters:f,cache:p,layer:y,autoPrefix:b}=a.useContext(Wc),x=[r||""];y&&x.push("layer"),x.push(...n);let v=Fh;qb(Q_,x,()=>{const g=x.join("|");if(q_(g)){const[E,w]=G_(g);if(E)return[E,w,{},i,l]}const h=t(),[$,C]=Wh(h,{hashId:r,hashPriority:u,layer:y?o:void 0,path:n.join("-"),transformers:m,linters:f}),N=Bp($,b||!1),S=uP(x,N);return[N,S,C,i,l]},(g,h)=>{const[,$]=g;h&&Fh&&xc($,{mark:Jo,attachTo:d})},g=>{const[h,$,C,,N]=g;if(v&&h!==aP){let S={mark:Jo,prepend:y?!1:"queue",attachTo:d,priority:N};S=Ub(S,s);const E=[],w=[];Object.keys(C).forEach(P=>{P.startsWith("@layer")?E.push(P):w.push(P)}),E.forEach(P=>{vi(Bp(C[P],b||!1),`_layer-${P}`,{...S,prepend:!0})});const R=vi(h,$,S);R[qo]=p.instanceId,w.forEach(P=>{vi(Bp(C[P],b||!1),`_effect-${P}`,S)})}})}const J_="cssVar",Z_=(e,t)=>{const{key:n,prefix:r,unitless:o,ignore:s,token:i,hashId:l,scope:c,nonce:u}=e,{cache:{instanceId:d},container:m,hashPriority:f}=a.useContext(Wc),{_tokenKey:p}=i,y=Array.isArray(c)?c.join("@@"):c,b=[...e.path,n,y,p];return qb(J_,b,()=>{const v=t(),[g,h]=ZI(v,n,{prefix:r,unitless:o,ignore:s,scope:c,hashPriority:f,hashCls:l}),$=uP(b,h);return[g,h,$,n]},([,,v])=>{Fh&&xc(v,{mark:Jo,attachTo:m})},([,v,g])=>{if(!v)return;let h={mark:Jo,prepend:"queue",attachTo:m,priority:-999};h=Ub(h,u);const $=vi(v,g,h);$[qo]=d,$.setAttribute(Kb,n)})};class Ht{constructor(t,n){fe(this,"name");fe(this,"style");fe(this,"_keyframe",!0);this.name=t,this.style=n}getName(t=""){return t?`${t}-${this.name}`:this.name}}function qi(e){return e.notSplit=!0,e}qi(["borderTop","borderBottom"]),qi(["borderTop"]),qi(["borderBottom"]),qi(["borderLeft","borderRight"]),qi(["borderLeft"]),qi(["borderRight"]);function vo(e){"@babel/helpers - typeof";return vo=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},vo(e)}function ez(e){if(Array.isArray(e))return e}function tz(e,t){var n=e==null?null:typeof Symbol<"u"&&e[Symbol.iterator]||e["@@iterator"];if(n!=null){var r,o,s,i,l=[],c=!0,u=!1;try{if(s=(n=n.call(e)).next,t===0){if(Object(n)!==n)return;c=!1}else for(;!(c=(r=s.call(n)).done)&&(l.push(r.value),l.length!==t);c=!0);}catch(d){u=!0,o=d}finally{try{if(!c&&n.return!=null&&(i=n.return(),Object(i)!==i))return}finally{if(u)throw o}}return l}}function nz(){throw new TypeError(`Invalid attempt to destructure non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function nf(e,t){return ez(e)||tz(e,t)||YI(e,t)||nz()}function rz(e,t){if(vo(e)!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(vo(r)!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function dP(e){var t=rz(e,"string");return vo(t)=="symbol"?t:t+""}function En(e,t,n){return(t=dP(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function QS(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function yr(e){for(var t=1;t1e4){var r=Date.now();this.lastAccessBeat.forEach(function(o,s){r-o>mz&&(n.map.delete(s),n.lastAccessBeat.delete(s))}),this.accessBeat=0}}}]),e}(),n1=new pz;function gz(e,t){return J.useMemo(function(){var n=n1.get(t);if(n)return n;var r=e();return n1.set(t,r),r},t)}var hz=function(){return{}};function yz(e){var t=e.useCSP,n=t===void 0?hz:t,r=e.useToken,o=e.usePrefix,s=e.getResetStyles,i=e.getCommonStyle,l=e.getCompUnitless;function c(f,p,y,b){var x=Array.isArray(f)?f[0]:f;function v(E){return"".concat(String(x)).concat(E.slice(0,1).toUpperCase()).concat(E.slice(1))}var g=(b==null?void 0:b.unitless)||{},h=typeof l=="function"?l(f):{},$=yr(yr({},h),{},En({},v("zIndexPopup"),!0));Object.keys(g).forEach(function(E){$[v(E)]=g[E]});var C=yr(yr({},b),{},{unitless:$,prefixToken:v}),N=d(f,p,y,C),S=u(x,y,C);return function(E){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:E,R=N(E,w),P=b==null?void 0:b.extraCssVarPrefixCls,T=typeof P=="function"?P({prefixCls:E,rootCls:w}):P,M=S(T!=null&&T.length?[w].concat($t(T)):w);return[R,M]}}function u(f,p,y){var b=y.unitless,x=y.prefixToken,v=y.ignore;return function(g){var h=r(),$=h.cssVar,C=h.realToken,N=n();return Z_({path:[f],prefix:$.prefix,key:$.key,unitless:b,ignore:v,token:C,scope:g,nonce:function(){return N.nonce}},function(){var S=t1(f,C,p),E=ZS(f,C,S,{deprecatedTokens:y==null?void 0:y.deprecatedTokens});return S&&Object.keys(S).forEach(function(w){E[x(w)]=E[w],delete E[w]}),E}),$==null?void 0:$.key}}function d(f,p,y){var b=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},x=Array.isArray(f)?f:[f,f],v=nf(x,1),g=v[0],h=x.join("-"),$=e.layer||{name:"antd"};return function(C){var N=arguments.length>1&&arguments[1]!==void 0?arguments[1]:C,S=r(),E=S.theme,w=S.realToken,R=S.hashId,P=S.token,T=S.cssVar,M=S.zeroRuntime,z=a.useMemo(function(){return M},[]);if(z)return R;var B=o(),F=B.rootPrefixCls,L=B.iconPrefixCls,j=n(),O="css",A=gz(function(){var W=new Set;return Object.keys(b.unitless||{}).forEach(function(K){W.add(gd(K,T.prefix)),W.add(gd(K,JS(g,T.prefix)))}),cz(O,W)},[O,g,T==null?void 0:T.prefix]),k=fz(),_=k.max,D=k.min,V={theme:E,token:P,hashId:R,nonce:function(){return j.nonce},clientOnly:b.clientOnly,layer:$,order:b.order||-999};return typeof s=="function"&&Kh(yr(yr({},V),{},{clientOnly:!1,path:["Shared",F]}),function(){return s(P,{prefix:{rootPrefixCls:F,iconPrefixCls:L},csp:j})}),Kh(yr(yr({},V),{},{path:[h,C,L]}),function(){if(b.injectStyle===!1)return[];var W=dz(P),K=W.token,q=W.flush,Y=t1(g,w,y),ee=".".concat(C),ie=ZS(g,w,Y,{deprecatedTokens:b.deprecatedTokens});Y&&vo(Y)==="object"&&Object.keys(Y).forEach(function(Z){Y[Z]="var(".concat(gd(Z,JS(g,T.prefix)),")")});var ae=Rt(K,{componentCls:ee,prefixCls:C,iconCls:".".concat(L),antCls:".".concat(F),calc:A,max:_,min:D},Y),U=p(ae,{hashId:R,prefixCls:C,rootPrefixCls:F,iconPrefixCls:L});q(g,ie);var Q=typeof i=="function"?i(ae,C,N,b.resetFont):null;return[b.resetStyle===!1?null:Q,U]}),R}}function m(f,p,y){var b=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},x=d(f,p,y,yr({resetStyle:!1,order:-998},b)),v=function(h){var $=h.prefixCls,C=h.rootCls,N=C===void 0?$:C;return x($,N),null};return v}return{genStyleHooks:c,genSubStyleComponent:m,genComponentStyleHook:d}}const Oo=["blue","purple","cyan","green","magenta","pink","red","orange","yellow","volcano","geekblue","lime","gold"];function bd(e){return(e+8)/e}function vz(e){const t=Array.from({length:10}).map((n,r)=>{const o=r-1,s=e*Math.E**(o/5),i=r>1?Math.floor(s):Math.ceil(s);return Math.floor(i/2)*2});return t[1]=e,t.map(n=>({size:n,lineHeight:bd(n)}))}const bz="6.5.1",Qb={blue:"#1677FF",purple:"#722ED1",cyan:"#13C2C2",green:"#52C41A",magenta:"#EB2F96",pink:"#EB2F96",red:"#F5222D",orange:"#FA8C16",yellow:"#FADB14",volcano:"#FA541C",geekblue:"#2F54EB",gold:"#FAAD14",lime:"#A0D911"},Wa={...Qb,colorPrimary:"#1677ff",colorSuccess:"#52c41a",colorWarning:"#faad14",colorError:"#ff4d4f",colorInfo:"#1677ff",colorLink:"",colorTextBase:"",colorBgBase:"",fontFamily:`-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, +'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', +'Noto Color Emoji'`,fontFamilyCode:"'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace",fontSize:14,lineWidth:1,lineType:"solid",motionUnit:.1,motionBase:0,motionEaseOutCirc:"cubic-bezier(0.08, 0.82, 0.17, 1)",motionEaseInOutCirc:"cubic-bezier(0.78, 0.14, 0.15, 0.86)",motionEaseOut:"cubic-bezier(0.215, 0.61, 0.355, 1)",motionEaseInOut:"cubic-bezier(0.645, 0.045, 0.355, 1)",motionEaseOutBack:"cubic-bezier(0.12, 0.4, 0.29, 1.46)",motionEaseInBack:"cubic-bezier(0.71, -0.46, 0.88, 0.6)",motionEaseInQuint:"cubic-bezier(0.755, 0.05, 0.855, 0.06)",motionEaseOutQuint:"cubic-bezier(0.23, 1, 0.32, 1)",borderRadius:6,sizeUnit:4,sizeStep:4,sizePopupArrow:16,controlHeight:32,zIndexBase:0,zIndexPopupBase:1e3,opacityImage:1,wireframe:!1,motion:!0},xz={aliceblue:"9ehhb",antiquewhite:"9sgk7",aqua:"1ekf",aquamarine:"4zsno",azure:"9eiv3",beige:"9lhp8",bisque:"9zg04",black:"0",blanchedalmond:"9zhe5",blue:"73",blueviolet:"5e31e",brown:"6g016",burlywood:"8ouiv",cadetblue:"3qba8",chartreuse:"4zshs",chocolate:"87k0u",coral:"9yvyo",cornflowerblue:"3xael",cornsilk:"9zjz0",crimson:"8l4xo",cyan:"1ekf",darkblue:"3v",darkcyan:"rkb",darkgoldenrod:"776yz",darkgray:"6mbhl",darkgreen:"jr4",darkgrey:"6mbhl",darkkhaki:"7ehkb",darkmagenta:"5f91n",darkolivegreen:"3bzfz",darkorange:"9yygw",darkorchid:"5z6x8",darkred:"5f8xs",darksalmon:"9441m",darkseagreen:"5lwgf",darkslateblue:"2th1n",darkslategray:"1ugcv",darkslategrey:"1ugcv",darkturquoise:"14up",darkviolet:"5rw7n",deeppink:"9yavn",deepskyblue:"11xb",dimgray:"442g9",dimgrey:"442g9",dodgerblue:"16xof",firebrick:"6y7tu",floralwhite:"9zkds",forestgreen:"1cisi",fuchsia:"9y70f",gainsboro:"8m8kc",ghostwhite:"9pq0v",goldenrod:"8j4f4",gold:"9zda8",gray:"50i2o",green:"pa8",greenyellow:"6senj",grey:"50i2o",honeydew:"9eiuo",hotpink:"9yrp0",indianred:"80gnw",indigo:"2xcoy",ivory:"9zldc",khaki:"9edu4",lavenderblush:"9ziet",lavender:"90c8q",lawngreen:"4vk74",lemonchiffon:"9zkct",lightblue:"6s73a",lightcoral:"9dtog",lightcyan:"8s1rz",lightgoldenrodyellow:"9sjiq",lightgray:"89jo3",lightgreen:"5nkwg",lightgrey:"89jo3",lightpink:"9z6wx",lightsalmon:"9z2ii",lightseagreen:"19xgq",lightskyblue:"5arju",lightslategray:"4nwk9",lightslategrey:"4nwk9",lightsteelblue:"6wau6",lightyellow:"9zlcw",lime:"1edc",limegreen:"1zcxe",linen:"9shk6",magenta:"9y70f",maroon:"4zsow",mediumaquamarine:"40eju",mediumblue:"5p",mediumorchid:"79qkz",mediumpurple:"5r3rv",mediumseagreen:"2d9ip",mediumslateblue:"4tcku",mediumspringgreen:"1di2",mediumturquoise:"2uabw",mediumvioletred:"7rn9h",midnightblue:"z980",mintcream:"9ljp6",mistyrose:"9zg0x",moccasin:"9zfzp",navajowhite:"9zest",navy:"3k",oldlace:"9wq92",olive:"50hz4",olivedrab:"472ub",orange:"9z3eo",orangered:"9ykg0",orchid:"8iu3a",palegoldenrod:"9bl4a",palegreen:"5yw0o",paleturquoise:"6v4ku",palevioletred:"8k8lv",papayawhip:"9zi6t",peachpuff:"9ze0p",peru:"80oqn",pink:"9z8wb",plum:"8nba5",powderblue:"6wgdi",purple:"4zssg",rebeccapurple:"3zk49",red:"9y6tc",rosybrown:"7cv4f",royalblue:"2jvtt",saddlebrown:"5fmkz",salmon:"9rvci",sandybrown:"9jn1c",seagreen:"1tdnb",seashell:"9zje6",sienna:"6973h",silver:"7ir40",skyblue:"5arjf",slateblue:"45e4t",slategray:"4e100",slategrey:"4e100",snow:"9zke2",springgreen:"1egv",steelblue:"2r1kk",tan:"87yx8",teal:"pds",thistle:"8ggk8",tomato:"9yqfb",turquoise:"2j4r4",violet:"9b10u",wheat:"9ld4j",white:"9zldr",whitesmoke:"9lhpx",yellow:"9zl6o",yellowgreen:"61fzm"},kn=Math.round;function kp(e,t){const n=e.replace(/^[^(]*\((.*)/,"$1").replace(/\).*/,"").match(/\d*\.?\d+%?/g)||[],r=n.map(o=>parseFloat(o));for(let o=0;o<3;o+=1)r[o]=t(r[o]||0,n[o]||"",o);return n[3]?r[3]=n[3].includes("%")?r[3]/100:r[3]:r[3]=1,r}const r1=(e,t,n)=>n===0?e:e/100;function yl(e,t){const n=t||255;return e>n?n:e<0?0:e}class Gt{constructor(t){fe(this,"isValid",!0);fe(this,"r",0);fe(this,"g",0);fe(this,"b",0);fe(this,"a",1);fe(this,"_h");fe(this,"_hsl_s");fe(this,"_hsv_s");fe(this,"_l");fe(this,"_v");fe(this,"_max");fe(this,"_min");fe(this,"_brightness");function n(o){return o[0]in t&&o[1]in t&&o[2]in t}if(t)if(typeof t=="string"){let s=function(i){return o.startsWith(i)};var r=s;const o=t.trim();if(/^#?[A-F\d]{3,8}$/i.test(o))this.fromHexString(o);else if(s("rgb"))this.fromRgbString(o);else if(s("hsl"))this.fromHslString(o);else if(s("hsv")||s("hsb"))this.fromHsvString(o);else{const i=xz[o.toLowerCase()];i&&this.fromHexString(parseInt(i,36).toString(16).padStart(6,"0"))}}else if(t instanceof Gt)this.r=t.r,this.g=t.g,this.b=t.b,this.a=t.a,this._h=t._h,this._hsl_s=t._hsl_s,this._hsv_s=t._hsv_s,this._l=t._l,this._v=t._v;else if(n("rgb"))this.r=yl(t.r),this.g=yl(t.g),this.b=yl(t.b),this.a=typeof t.a=="number"?yl(t.a,1):1;else if(n("hsl"))this.fromHsl(t);else if(n("hsv"))this.fromHsv(t);else throw new Error("@ant-design/fast-color: unsupported input "+JSON.stringify(t))}setR(t){return this._sc("r",t)}setG(t){return this._sc("g",t)}setB(t){return this._sc("b",t)}setA(t){return this._sc("a",t,1)}setHue(t){const n=this.toHsv();return n.h=t,this._c(n)}getLuminance(){function t(s){const i=s/255;return i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4)}const n=t(this.r),r=t(this.g),o=t(this.b);return .2126*n+.7152*r+.0722*o}getHue(){if(typeof this._h>"u"){const t=this.getMax()-this.getMin();t===0?this._h=0:this._h=kn(60*(this.r===this.getMax()?(this.g-this.b)/t+(this.g"u"){const t=this.getMax()-this.getMin();t===0?this._hsv_s=0:this._hsv_s=t/this.getMax()}return this._hsv_s}getHSLSaturation(){if(typeof this._hsl_s>"u"){const t=this.getMax()-this.getMin();if(t===0)this._hsl_s=0;else{const n=this.getLightness();this._hsl_s=t/255/(1-Math.abs(2*n-1))}}return this._hsl_s}getLightness(){return typeof this._l>"u"&&(this._l=(this.getMax()+this.getMin())/510),this._l}getValue(){return typeof this._v>"u"&&(this._v=this.getMax()/255),this._v}getBrightness(){return typeof this._brightness>"u"&&(this._brightness=(this.r*299+this.g*587+this.b*114)/1e3),this._brightness}darken(t=10){const n=this.getHue(),r=this.getSaturation();let o=this.getLightness()-t/100;return o<0&&(o=0),this._c({h:n,s:r,l:o,a:this.a})}lighten(t=10){const n=this.getHue(),r=this.getSaturation();let o=this.getLightness()+t/100;return o>1&&(o=1),this._c({h:n,s:r,l:o,a:this.a})}mix(t,n=50){const r=this._c(t),o=n/100,s=l=>(r[l]-this[l])*o+this[l],i={r:kn(s("r")),g:kn(s("g")),b:kn(s("b")),a:kn(s("a")*100)/100};return this._c(i)}tint(t=10){return this.mix({r:255,g:255,b:255,a:1},t)}shade(t=10){return this.mix({r:0,g:0,b:0,a:1},t)}onBackground(t){const n=this._c(t),r=this.a+n.a*(1-this.a),o=s=>kn((this[s]*this.a+n[s]*n.a*(1-this.a))/r);return this._c({r:o("r"),g:o("g"),b:o("b"),a:r})}isDark(){return this.getBrightness()<128}isLight(){return this.getBrightness()>=128}equals(t){return this.r===t.r&&this.g===t.g&&this.b===t.b&&this.a===t.a}clone(){return this._c(this)}toHexString(){let t="#";const n=(this.r||0).toString(16);t+=n.length===2?n:"0"+n;const r=(this.g||0).toString(16);t+=r.length===2?r:"0"+r;const o=(this.b||0).toString(16);if(t+=o.length===2?o:"0"+o,typeof this.a=="number"&&this.a>=0&&this.a<1){const s=kn(this.a*255).toString(16);t+=s.length===2?s:"0"+s}return t}toHsl(){return{h:this.getHue(),s:this.getHSLSaturation(),l:this.getLightness(),a:this.a}}toHslString(){const t=this.getHue(),n=kn(this.getHSLSaturation()*100),r=kn(this.getLightness()*100);return this.a!==1?`hsla(${t},${n}%,${r}%,${this.a})`:`hsl(${t},${n}%,${r}%)`}toHsv(){return{h:this.getHue(),s:this.getHSVSaturation(),v:this.getValue(),a:this.a}}toRgb(){return{r:this.r,g:this.g,b:this.b,a:this.a}}toRgbString(){return this.a!==1?`rgba(${this.r},${this.g},${this.b},${this.a})`:`rgb(${this.r},${this.g},${this.b})`}toString(){return this.toRgbString()}_sc(t,n,r){const o=this.clone();return o[t]=yl(n,r),o}_c(t){return new this.constructor(t)}getMax(){return typeof this._max>"u"&&(this._max=Math.max(this.r,this.g,this.b)),this._max}getMin(){return typeof this._min>"u"&&(this._min=Math.min(this.r,this.g,this.b)),this._min}fromHexString(t){const n=t.replace("#","");function r(o,s){return parseInt(n[o]+n[s||o],16)}n.length<6?(this.r=r(0),this.g=r(1),this.b=r(2),this.a=n[3]?r(3)/255:1):(this.r=r(0,1),this.g=r(2,3),this.b=r(4,5),this.a=n[6]?r(6,7)/255:1)}fromHsl({h:t,s:n,l:r,a:o}){const s=(t%360+360)%360;if(this._h=s,this._hsl_s=n,this._l=r,this.a=typeof o=="number"?o:1,n<=0){const p=kn(r*255);this.r=p,this.g=p,this.b=p;return}let i=0,l=0,c=0;const u=s/60,d=(1-Math.abs(2*r-1))*n,m=d*(1-Math.abs(u%2-1));u>=0&&u<1?(i=d,l=m):u>=1&&u<2?(i=m,l=d):u>=2&&u<3?(l=d,c=m):u>=3&&u<4?(l=m,c=d):u>=4&&u<5?(i=m,c=d):u>=5&&u<6&&(i=d,c=m);const f=r-d/2;this.r=kn((i+f)*255),this.g=kn((l+f)*255),this.b=kn((c+f)*255)}fromHsv({h:t,s:n,v:r,a:o}){const s=(t%360+360)%360;this._h=s,this._hsv_s=n,this._v=r,this.a=typeof o=="number"?o:1;const i=kn(r*255);if(this.r=i,this.g=i,this.b=i,n<=0)return;const l=s/60,c=Math.floor(l),u=l-c,d=kn(r*(1-n)*255),m=kn(r*(1-n*u)*255),f=kn(r*(1-n*(1-u))*255);switch(c){case 0:this.g=f,this.b=d;break;case 1:this.r=m,this.b=d;break;case 2:this.r=d,this.b=f;break;case 3:this.r=d,this.g=m;break;case 4:this.r=f,this.g=d;break;case 5:default:this.g=d,this.b=m;break}}fromHsvString(t){const n=kp(t,r1);this.fromHsv({h:n[0],s:n[1],v:n[2],a:n[3]})}fromHslString(t){const n=kp(t,r1);this.fromHsl({h:n[0],s:n[1],l:n[2],a:n[3]})}fromRgbString(t){const n=kp(t,(r,o)=>o.includes("%")?kn(r/100*255):r);this.r=n[0],this.g=n[1],this.b=n[2],this.a=n[3]}}const Mu=2,o1=.16,$z=.05,Sz=.05,Cz=.15,hP=5,yP=4,wz=[{index:7,amount:15},{index:6,amount:25},{index:5,amount:30},{index:5,amount:45},{index:5,amount:65},{index:5,amount:85},{index:4,amount:90},{index:3,amount:95},{index:2,amount:97},{index:1,amount:98}];function s1(e,t,n){let r;return Math.round(e.h)>=60&&Math.round(e.h)<=240?r=n?Math.round(e.h)-Mu*t:Math.round(e.h)+Mu*t:r=n?Math.round(e.h)+Mu*t:Math.round(e.h)-Mu*t,r<0?r+=360:r>=360&&(r-=360),r}function i1(e,t,n){if(e.h===0&&e.s===0)return e.s;let r;return n?r=e.s-o1*t:t===yP?r=e.s+o1:r=e.s+$z*t,r>1&&(r=1),n&&t===hP&&r>.1&&(r=.1),r<.06&&(r=.06),Math.round(r*100)/100}function a1(e,t,n){let r;return n?r=e.v+Sz*t:r=e.v-Cz*t,r=Math.max(0,Math.min(1,r)),Math.round(r*100)/100}function gm(e,t={}){const n=[],r=new Gt(e),o=r.toHsv();for(let s=hP;s>0;s-=1){const i=new Gt({h:s1(o,s,!0),s:i1(o,s,!0),v:a1(o,s,!0)});n.push(i)}n.push(r);for(let s=1;s<=yP;s+=1){const i=new Gt({h:s1(o,s),s:i1(o,s),v:a1(o,s)});n.push(i)}return t.theme==="dark"?wz.map(({index:s,amount:i})=>new Gt(t.backgroundColor||"#141414").mix(n[s],i).toHexString()):n.map(s=>s.toHexString())}const Ap={red:"#F5222D",volcano:"#FA541C",orange:"#FA8C16",gold:"#FAAD14",yellow:"#FADB14",lime:"#A0D911",green:"#52C41A",cyan:"#13C2C2",blue:"#1677FF",geekblue:"#2F54EB",purple:"#722ED1",magenta:"#EB2F96",grey:"#666666"},Gh=["#fff1f0","#ffccc7","#ffa39e","#ff7875","#ff4d4f","#f5222d","#cf1322","#a8071a","#820014","#5c0011"];Gh.primary=Gh[5];const Xh=["#fff2e8","#ffd8bf","#ffbb96","#ff9c6e","#ff7a45","#fa541c","#d4380d","#ad2102","#871400","#610b00"];Xh.primary=Xh[5];const Yh=["#fff7e6","#ffe7ba","#ffd591","#ffc069","#ffa940","#fa8c16","#d46b08","#ad4e00","#873800","#612500"];Yh.primary=Yh[5];const rf=["#fffbe6","#fff1b8","#ffe58f","#ffd666","#ffc53d","#faad14","#d48806","#ad6800","#874d00","#613400"];rf.primary=rf[5];const Qh=["#feffe6","#ffffb8","#fffb8f","#fff566","#ffec3d","#fadb14","#d4b106","#ad8b00","#876800","#614700"];Qh.primary=Qh[5];const Jh=["#fcffe6","#f4ffb8","#eaff8f","#d3f261","#bae637","#a0d911","#7cb305","#5b8c00","#3f6600","#254000"];Jh.primary=Jh[5];const Zh=["#f6ffed","#d9f7be","#b7eb8f","#95de64","#73d13d","#52c41a","#389e0d","#237804","#135200","#092b00"];Zh.primary=Zh[5];const ey=["#e6fffb","#b5f5ec","#87e8de","#5cdbd3","#36cfc9","#13c2c2","#08979c","#006d75","#00474f","#002329"];ey.primary=ey[5];const ty=["#e6f4ff","#bae0ff","#91caff","#69b1ff","#4096ff","#1677ff","#0958d9","#003eb3","#002c8c","#001d66"];ty.primary=ty[5];const ny=["#f0f5ff","#d6e4ff","#adc6ff","#85a5ff","#597ef7","#2f54eb","#1d39c4","#10239e","#061178","#030852"];ny.primary=ny[5];const ry=["#f9f0ff","#efdbff","#d3adf7","#b37feb","#9254de","#722ed1","#531dab","#391085","#22075e","#120338"];ry.primary=ry[5];const oy=["#fff0f6","#ffd6e7","#ffadd2","#ff85c0","#f759ab","#eb2f96","#c41d7f","#9e1068","#780650","#520339"];oy.primary=oy[5];const sy=["#a6a6a6","#999999","#8c8c8c","#808080","#737373","#666666","#404040","#1a1a1a","#000000","#000000"];sy.primary=sy[5];const Dp={red:Gh,volcano:Xh,orange:Yh,gold:rf,yellow:Qh,lime:Jh,green:Zh,cyan:ey,blue:ty,geekblue:ny,purple:ry,magenta:oy,grey:sy};function vP(e,{generateColorPalettes:t,generateNeutralColorPalettes:n}){const{colorSuccess:r,colorWarning:o,colorError:s,colorInfo:i,colorPrimary:l,colorBgBase:c,colorTextBase:u}=e,d=t(l),m=t(r),f=t(o),p=t(s),y=t(i),b=n(c,u),x=e.colorLink||e.colorInfo,v=t(x),g=new Gt(p[1]).mix(new Gt(p[3]),50).toHexString(),h={};return Oo.forEach($=>{const C=e[$];if(C){const N=t(C);h[`${$}Hover`]=N[5],h[`${$}Active`]=N[7]}}),{...b,colorPrimaryBg:d[1],colorPrimaryBgHover:d[2],colorPrimaryBorder:d[3],colorPrimaryBorderHover:d[4],colorPrimaryHover:d[5],colorPrimary:d[6],colorPrimaryActive:d[7],colorPrimaryTextHover:d[8],colorPrimaryText:d[9],colorPrimaryTextActive:d[10],colorSuccessBg:m[1],colorSuccessBgHover:m[2],colorSuccessBorder:m[3],colorSuccessBorderHover:m[4],colorSuccessHover:m[4],colorSuccess:m[6],colorSuccessActive:m[7],colorSuccessTextHover:m[8],colorSuccessText:m[9],colorSuccessTextActive:m[10],colorErrorBg:p[1],colorErrorBgHover:p[2],colorErrorBgFilledHover:g,colorErrorBgActive:p[3],colorErrorBorder:p[3],colorErrorBorderHover:p[4],colorErrorHover:p[5],colorError:p[6],colorErrorActive:p[7],colorErrorTextHover:p[8],colorErrorText:p[9],colorErrorTextActive:p[10],colorWarningBg:f[1],colorWarningBgHover:f[2],colorWarningBorder:f[3],colorWarningBorderHover:f[4],colorWarningHover:f[4],colorWarning:f[6],colorWarningActive:f[7],colorWarningTextHover:f[8],colorWarningText:f[9],colorWarningTextActive:f[10],colorInfoBg:y[1],colorInfoBgHover:y[2],colorInfoBorder:y[3],colorInfoBorderHover:y[4],colorInfoHover:y[4],colorInfo:y[6],colorInfoActive:y[7],colorInfoTextHover:y[8],colorInfoText:y[9],colorInfoTextActive:y[10],colorLinkHover:v[4],colorLink:v[6],colorLinkActive:v[7],...h,colorBgMask:new Gt("#000").setA(.45).toRgbString(),colorWhite:"#fff"}}const Ez=e=>{let t=e,n=e,r=e,o=e;return e<6&&e>=5?t=e+1:e<16&&e>=6?t=e+2:e>=16&&(t=16),e<7&&e>=5?n=4:e<8&&e>=7?n=5:e<14&&e>=8?n=6:e<16&&e>=14?n=7:e>=16&&(n=8),e<6&&e>=2?r=1:e>=6&&(r=2),e>4&&e<8?o=4:e>=8&&(o=6),{borderRadius:e,borderRadiusXS:r,borderRadiusSM:n,borderRadiusLG:t,borderRadiusOuter:o}};function Iz(e){const{motionUnit:t,motionBase:n,borderRadius:r,lineWidth:o}=e;return{motionDurationFast:`${(n+t).toFixed(1)}s`,motionDurationMid:`${(n+t*2).toFixed(1)}s`,motionDurationSlow:`${(n+t*3).toFixed(1)}s`,lineWidthBold:o+1,...Ez(r)}}const bP=e=>{const{controlHeight:t}=e;return{controlHeightSM:t*.75,controlHeightXS:t*.5,controlHeightLG:t*1.25}},xP=e=>{const t=vz(e),n=t.map(d=>d.size),r=t.map(d=>d.lineHeight),o=n[1],s=n[0],i=n[2],l=r[1],c=r[0],u=r[2];return{fontSizeSM:s,fontSize:o,fontSizeLG:i,fontSizeXL:n[3],fontSizeHeading1:n[6],fontSizeHeading2:n[5],fontSizeHeading3:n[4],fontSizeHeading4:n[3],fontSizeHeading5:n[2],lineHeight:l,lineHeightLG:u,lineHeightSM:c,fontHeight:Math.round(l*o),fontHeightLG:Math.round(u*i),fontHeightSM:Math.round(c*s),lineHeightHeading1:r[6],lineHeightHeading2:r[5],lineHeightHeading3:r[4],lineHeightHeading4:r[3],lineHeightHeading5:r[2]}};function Pz(e){const{sizeUnit:t,sizeStep:n}=e;return{sizeXXL:t*(n+8),sizeXL:t*(n+4),sizeLG:t*(n+2),sizeMD:t*(n+1),sizeMS:t*n,size:t*n,sizeSM:t*(n-1),sizeXS:t*(n-2),sizeXXS:t*(n-3)}}const Fr=(e,t)=>new Gt(e).setA(t).toRgbString(),Gi=(e,t)=>new Gt(e).darken(t).toHexString(),Nz=e=>{const t=gm(e);return{1:t[0],2:t[1],3:t[2],4:t[3],5:t[4],6:t[5],7:t[6],8:t[4],9:t[5],10:t[6]}},Rz=(e,t,n)=>{const r=e||"#fff",o=t||"#000";return{colorBgBase:r,colorTextBase:o,colorShadow:n||"#000",colorText:Fr(o,.88),colorTextSecondary:Fr(o,.65),colorTextTertiary:Fr(o,.45),colorTextQuaternary:Fr(o,.25),colorFill:Fr(o,.15),colorFillSecondary:Fr(o,.06),colorFillTertiary:Fr(o,.04),colorFillQuaternary:Fr(o,.02),colorBgSolid:Fr(o,1),colorBgSolidHover:Fr(o,.75),colorBgSolidActive:Fr(o,.95),colorBgLayout:Gi(r,4),colorBgContainer:Gi(r,0),colorBgElevated:Gi(r,0),colorBgSpotlight:Fr(o,.85),colorBgBlur:"transparent",colorBorder:Gi(r,15),colorBorderDisabled:Gi(r,15),colorBorderSecondary:Gi(r,6)}};function hm(e){Ap.pink=Ap.magenta,Dp.pink=Dp.magenta;const t=Object.keys(Qb).map(n=>{const r=e[n]===Ap[n]?Dp[n]:gm(e[n]);return Array.from({length:10},()=>1).reduce((o,s,i)=>(o[`${n}-${i+1}`]=r[i],o[`${n}${i+1}`]=r[i],o),{})}).reduce((n,r)=>(n={...n,...r},n),{});return{...e,...t,...vP(e,{generateColorPalettes:Nz,generateNeutralColorPalettes:Rz}),...xP(e.fontSize),...Pz(e),...bP(e),...Iz(e)}}const Jb=tf(hm),Ec={token:Wa,override:{override:Wa},hashed:!0},Zb=J.createContext(Ec);function Fp(e){return e>=0&&e<=255}function Tl(e,t){const{r:n,g:r,b:o,a:s}=new Gt(e).toRgb();if(s<1)return e;const{r:i,g:l,b:c}=new Gt(t).toRgb();for(let u=.01;u<=1;u+=.01){const d=Math.round((n-i*(1-u))/u),m=Math.round((r-l*(1-u))/u),f=Math.round((o-c*(1-u))/u);if(Fp(d)&&Fp(m)&&Fp(f))return new Gt({r:d,g:m,b:f,a:Math.round(u*100)/100}).toRgbString()}return new Gt({r:n,g:r,b:o,a:1}).toRgbString()}function $P(e){const{override:t,...n}=e,r={...t};Object.keys(Wa).forEach(x=>{delete r[x]});const o={...n,...r},s=new Gt(o.colorShadow),i=s.a,l=x=>s.clone().setA(i*x).toRgbString(),c=480,u=576,d=768,m=992,f=1200,p=1600,y=1920;if(o.motion===!1){const x="0s";o.motionDurationFast=x,o.motionDurationMid=x,o.motionDurationSlow=x}return{...o,colorFillContent:o.colorFillSecondary,colorFillContentHover:o.colorFill,colorFillAlter:o.colorFillQuaternary,colorBgContainerDisabled:o.colorFillTertiary,colorBorderBg:o.colorBgContainer,colorSplit:Tl(o.colorBorderSecondary,o.colorBgContainer),colorTextPlaceholder:o.colorTextQuaternary,colorTextDisabled:o.colorTextQuaternary,colorTextHeading:o.colorText,colorTextLabel:o.colorTextSecondary,colorTextDescription:o.colorTextTertiary,colorTextLightSolid:o.colorWhite,colorHighlight:o.colorError,colorBgTextHover:o.colorFillSecondary,colorBgTextActive:o.colorFill,colorIcon:o.colorTextTertiary,colorIconHover:o.colorText,colorErrorOutline:Tl(o.colorErrorBg,o.colorBgContainer),colorWarningOutline:Tl(o.colorWarningBg,o.colorBgContainer),colorErrorAffix:o.colorError,colorWarningAffix:o.colorWarning,fontSizeIcon:o.fontSizeSM,lineWidthFocus:o.lineWidth*3,lineWidth:o.lineWidth,controlOutlineWidth:o.lineWidth*2,controlInteractiveSize:o.controlHeight/2,controlItemBgHover:o.colorFillTertiary,controlItemBgActive:o.colorPrimaryBg,controlItemBgActiveHover:o.colorPrimaryBgHover,controlItemBgActiveDisabled:o.colorFill,controlTmpOutline:o.colorFillQuaternary,controlOutline:Tl(o.colorPrimaryBg,o.colorBgContainer),lineType:o.lineType,borderRadius:o.borderRadius,borderRadiusXS:o.borderRadiusXS,borderRadiusSM:o.borderRadiusSM,borderRadiusLG:o.borderRadiusLG,fontWeightStrong:600,opacityLoading:.65,linkDecoration:"none",linkHoverDecoration:"none",linkFocusDecoration:"none",controlPaddingHorizontal:12,controlPaddingHorizontalSM:8,paddingXXS:o.sizeXXS,paddingXS:o.sizeXS,paddingSM:o.sizeSM,padding:o.size,paddingMD:o.sizeMD,paddingLG:o.sizeLG,paddingXL:o.sizeXL,paddingContentHorizontalLG:o.sizeLG,paddingContentVerticalLG:o.sizeMS,paddingContentHorizontal:o.sizeMS,paddingContentVertical:o.sizeSM,paddingContentHorizontalSM:o.size,paddingContentVerticalSM:o.sizeXS,marginXXS:o.sizeXXS,marginXS:o.sizeXS,marginSM:o.sizeSM,margin:o.size,marginMD:o.sizeMD,marginLG:o.sizeLG,marginXL:o.sizeXL,marginXXL:o.sizeXXL,boxShadow:` + 0 6px 16px 0 ${l(.08)}, + 0 3px 6px -4px ${l(.12)}, + 0 9px 28px 8px ${l(.05)} + `,boxShadowSecondary:` + 0 6px 16px 0 ${l(.08)}, + 0 3px 6px -4px ${l(.12)}, + 0 9px 28px 8px ${l(.05)} + `,boxShadowTertiary:` + 0 1px 2px 0 ${l(.05)}, + 0 1px 6px -1px ${l(.03)}, + 0 2px 4px 0 ${l(.03)} + `,screenXS:c,screenXSMin:c,screenXSMax:u-1,screenSM:u,screenSMMin:u,screenSMMax:d-1,screenMD:d,screenMDMin:d,screenMDMax:m-1,screenLG:m,screenLGMin:m,screenLGMax:f-1,screenXL:f,screenXLMin:f,screenXLMax:p-1,screenXXL:p,screenXXLMin:p,screenXXLMax:y-1,screenXXXL:y,screenXXXLMin:y,boxShadowPopoverArrow:`2px 2px 5px ${l(.05)}`,dropShadowPopover:`drop-shadow(0 6px 16px ${l(.08)}) drop-shadow(0 3px 6px ${l(.12)}) drop-shadow(0 9px 28px ${l(.05)})`,boxShadowCard:` + 0 1px 2px -2px ${l(.16)}, + 0 3px 6px 0 ${l(.12)}, + 0 5px 12px 4px ${l(.09)} + `,boxShadowDrawerRight:` + -6px 0 16px 0 ${l(.08)}, + -3px 0 6px -4px ${l(.12)}, + -9px 0 28px 8px ${l(.05)} + `,boxShadowDrawerLeft:` + 6px 0 16px 0 ${l(.08)}, + 3px 0 6px -4px ${l(.12)}, + 9px 0 28px 8px ${l(.05)} + `,boxShadowDrawerUp:` + 0 6px 16px 0 ${l(.08)}, + 0 3px 6px -4px ${l(.12)}, + 0 9px 28px 8px ${l(.05)} + `,boxShadowDrawerDown:` + 0 -6px 16px 0 ${l(.08)}, + 0 -3px 6px -4px ${l(.12)}, + 0 -9px 28px 8px ${l(.05)} + `,boxShadowTabsOverflowLeft:`inset 10px 0 8px -8px ${l(.08)}`,boxShadowTabsOverflowRight:`inset -10px 0 8px -8px ${l(.08)}`,boxShadowTabsOverflowTop:`inset 0 10px 8px -8px ${l(.08)}`,boxShadowTabsOverflowBottom:`inset 0 -10px 8px -8px ${l(.08)}`,...r}}const SP={lineHeight:!0,lineHeightSM:!0,lineHeightLG:!0,lineHeightHeading1:!0,lineHeightHeading2:!0,lineHeightHeading3:!0,lineHeightHeading4:!0,lineHeightHeading5:!0,opacityLoading:!0,fontWeightStrong:!0,zIndexPopupBase:!0,zIndexBase:!0,opacityImage:!0},Tz={motionBase:!0,motionUnit:!0},Mz={screenXS:!0,screenXSMin:!0,screenXSMax:!0,screenSM:!0,screenSMMin:!0,screenSMMax:!0,screenMD:!0,screenMDMin:!0,screenMDMax:!0,screenLG:!0,screenLGMin:!0,screenLGMax:!0,screenXL:!0,screenXLMin:!0,screenXLMax:!0,screenXXL:!0,screenXXLMin:!0,screenXXLMax:!0,screenXXXL:!0,screenXXXLMin:!0},CP=(e,t,n)=>{const r=n.getDerivativeToken(e),{override:o,...s}=t;let i={...r,override:o};return i=$P(i),s&&Object.entries(s).forEach(([l,c])=>{const{theme:u,...d}=c;let m=d;u&&(m=CP({...i,...d},{override:d},u)),i[l]=m}),i};function Yn(){const{token:e,hashed:t,theme:n,override:r,cssVar:o,zeroRuntime:s}=J.useContext(Zb),{csp:i,getPrefixCls:l}=J.useContext(ct),c={prefix:(o==null?void 0:o.prefix)??l(),key:(o==null?void 0:o.key)??"css-var-root"},u=`${bz}-${t||""}`,d=n||Jb,[m,f,p]=P_(d,[Wa,e],{salt:u,override:r,getComputedToken:CP,cssVar:{...c,unitless:SP,ignore:Tz,preserve:Mz},nonce:i==null?void 0:i.nonce});return[d,p,t?f:"",m,c,!!s]}const ar={overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"},Ft=(e,t=!1)=>({boxSizing:"border-box",margin:0,padding:0,color:e.colorText,fontSize:e.fontSize,lineHeight:e.lineHeight,listStyle:"none",fontFamily:t?"inherit":e.fontFamily}),Kc=()=>({display:"inline-flex",alignItems:"center",color:"inherit",fontStyle:"normal",lineHeight:0,textAlign:"center",textTransform:"none",verticalAlign:"-0.125em",textRendering:"optimizeLegibility","-webkit-font-smoothing":"antialiased","-moz-osx-font-smoothing":"grayscale","> *":{lineHeight:1},svg:{display:"inline-block"}}),Oz=new Ht("loadingCircle",{"100%":{transform:"rotate(360deg)"}}),Ls=()=>({"&::before":{display:"table",content:'""'},"&::after":{display:"table",clear:"both",content:'""'}}),jr=(e,t)=>({outline:`${G(e.lineWidthFocus)} solid ${e.colorPrimaryBorder}`,outlineOffset:t??1,transition:["outline-offset","outline"].map(n=>`${n} 0s`).join(", ")}),Br=(e,t)=>({"&:focus-visible":jr(e,t)}),_z=e=>({a:{color:e.colorLink,textDecoration:e.linkDecoration,backgroundColor:"transparent",outline:"none",cursor:"pointer",transition:`color ${e.motionDurationSlow}`,"-webkit-text-decoration-skip":"objects","&:hover":{color:e.colorLinkHover},"&:active":{color:e.colorLinkActive},"&:active, &:hover":{textDecoration:e.linkHoverDecoration,outline:0},"&:focus":{textDecoration:e.linkFocusDecoration,outline:0},...Br(e),"&[disabled]":{color:e.colorTextDisabled,cursor:"not-allowed"}}}),zz=(e,t,n,r)=>{const o=`[class^="${t}"], [class*=" ${t}"]`,s=n?`.${n}`:o,i={boxSizing:"border-box","&::before, &::after":{boxSizing:"border-box"}};let l={};return r!==!1&&(l={fontFamily:e.fontFamily,fontSize:e.fontSize}),{[s]:{...l,...i,[o]:i}}},wP=e=>({[`.${e}`]:{...Kc(),[`.${e} .${e}-icon`]:{display:"block"}},[`.${e}-spin`]:{animationName:Oz,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear"}}),ex=e=>({color:e.colorLink,textDecoration:e.linkDecoration,outline:"none",cursor:"pointer",transition:`all ${e.motionDurationSlow}`,border:0,padding:0,background:"none",userSelect:"none",...Br(e),"&:hover":{color:e.colorLinkHover,textDecoration:e.linkHoverDecoration},"&:focus":{color:e.colorLinkHover,textDecoration:e.linkFocusDecoration},"&:active":{color:e.colorLinkActive,textDecoration:e.linkHoverDecoration}}),{genStyleHooks:Tt,genComponentStyleHook:jz,genSubStyleComponent:Ks}=yz({usePrefix:()=>{const{getPrefixCls:e,iconPrefixCls:t}=a.useContext(ct);return{rootPrefixCls:e(),iconPrefixCls:t}},useToken:()=>{const[e,t,n,r,o,s]=Yn();return{theme:e,realToken:t,hashId:n,token:r,cssVar:o,zeroRuntime:s}},useCSP:()=>{const{csp:e}=a.useContext(ct);return e??{}},getResetStyles:(e,t)=>{const n=_z(e);return[n,{"&":n},wP((t==null?void 0:t.prefix.iconPrefixCls)??dm)]},getCommonStyle:zz,getCompUnitless:()=>SP}),rn=(e,t)=>{const n=`--${e.replace(/\./g,"")}-${t}-`;return[s=>`${n}${s}`,(s,i)=>i?`var(${n}${s}, ${i})`:`var(${n}${s})`]};function EP(e,t){return Oo.reduce((n,r)=>{const o=e[`${r}1`],s=e[`${r}3`],i=e[`${r}6`],l=e[`${r}7`];return{...n,...t(r,{lightColor:o,lightBorderColor:s,darkColor:i,textColor:l})}},{})}const Bz=(e,t)=>(Yn(),Kh({hashId:"",path:["ant-design-icons",e],nonce:()=>t==null?void 0:t.nonce,layer:{name:"antd"}},()=>wP(e)));var IP={};Object.defineProperty(IP,"__esModule",{value:!0});var Lz={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"}}]},name:"check-circle",theme:"filled"},kz=IP.default=Lz;const tx=a.createContext({}),l1="data-rc-order",c1="data-rc-priority",Az="rc-util-key",iy=new Map;function PP(){return!!(typeof window<"u"&&window.document&&window.document.createElement)}function Dz(e,t){if(!e||!t)return!1;if(e.contains)return e.contains(t);let n=t;for(;n;){if(n===e)return!0;n=n.parentNode}return!1}function NP({mark:e}={}){return e?e.startsWith("data-")?e:`data-${e}`:Az}function nx(e){return e.attachTo?e.attachTo:document.querySelector("head")||document.body}function Fz(e){return e==="queue"?"prependQueue":e?"prepend":"append"}function rx(e){return Array.from((iy.get(e)||e).children).filter(t=>t.tagName==="STYLE")}function RP(e,t={}){if(!PP())return null;const{csp:n,prepend:r,priority:o=0}=t,s=Fz(r),i=s==="prependQueue",l=document.createElement("style");l.setAttribute(l1,s),i&&o&&l.setAttribute(c1,`${o}`),n!=null&&n.nonce&&(l.nonce=n.nonce),l.innerHTML=e;const c=nx(t),{firstChild:u}=c;if(r){if(i){const d=(t.styles||rx(c)).filter(m=>{if(!["prepend","prependQueue"].includes(m.getAttribute(l1)))return!1;const f=Number(m.getAttribute(c1)||0);return o>=f});if(d.length)return c.insertBefore(l,d[d.length-1].nextSibling),l}c.insertBefore(l,u)}else c.appendChild(l);return l}function Hz(e,t={}){let{styles:n}=t;return n||(n=rx(nx(t))),n.find(r=>r.getAttribute(NP(t))===e)}function Vz(e,t){const n=iy.get(e);if(!n||!Dz(document,n)){const r=RP("",t);if(!r)return;const{parentNode:o}=r;iy.set(e,o),e.removeChild(r)}}function Wz(e,t,n={}){var c;if(!PP())return null;const r=nx(n),o=rx(r),s={...n,styles:o};Vz(r,s);const i=Hz(t,s);if(i)return(c=s.csp)!=null&&c.nonce&&i.nonce!==s.csp.nonce&&(i.nonce=s.csp.nonce),i.innerHTML!==e&&(i.innerHTML=e),i;const l=RP(e,s);return l==null||l.setAttribute(NP(s),t),l}function Kz(e){var t;return(t=e==null?void 0:e.getRootNode)==null?void 0:t.call(e)}function Uz(e){const t=Kz(e);return typeof ShadowRoot<"u"&&t instanceof ShadowRoot?t:null}const u1={};function qz(e,t){e||u1[t]||(u1[t]=!0)}function Gz(e){return e.replace(/-(.)/g,(t,n)=>n.toUpperCase())}function Xz(e,t){qz(e,`[@ant-design/icons] ${t}`)}function d1(e){return typeof e=="object"&&typeof e.name=="string"&&typeof e.theme=="string"&&(typeof e.icon=="object"||typeof e.icon=="function")}function f1(e={}){return Object.keys(e).reduce((t,n)=>{const r=e[n];switch(n){case"class":t.className=r,delete t.class;break;default:delete t[n],t[Gz(n)]=r}return t},{})}function ay(e,t,n){return n?J.createElement(e.tag,{key:t,...f1(e.attrs),...n},(e.children||[]).map((r,o)=>ay(r,`${t}-${e.tag}-${o}`))):J.createElement(e.tag,{key:t,...f1(e.attrs)},(e.children||[]).map((r,o)=>ay(r,`${t}-${e.tag}-${o}`)))}const Yz=` +.anticon { + display: inline-flex; + align-items: center; + color: inherit; + font-style: normal; + line-height: 0; + text-align: center; + text-transform: none; + vertical-align: -0.125em; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.anticon > * { + line-height: 1; +} + +.anticon svg { + display: inline-block; + vertical-align: inherit; +} + +.anticon::before { + display: none; +} + +.anticon .anticon-icon { + display: block; +} + +.anticon[tabindex] { + cursor: pointer; +} + +.anticon-spin { + -webkit-animation: loadingCircle 1s infinite linear; + animation: loadingCircle 1s infinite linear; +} + +@-webkit-keyframes loadingCircle { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes loadingCircle { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +`,Qz=e=>{const{csp:t,prefixCls:n,layer:r,zeroRuntime:o}=a.useContext(tx);let s=Yz;n&&(s=s.replace(/anticon/g,n)),r&&(s=`@layer ${r} { +${s} +}`),a.useEffect(()=>{if(o)return;const i=e.current,l=Uz(i);Wz(s,"@ant-design-icons",{prepend:!r,csp:t,attachTo:l})},[])},TP=e=>{const{icon:t,className:n,onClick:r,style:o,primaryColor:s,secondaryColor:i,...l}=e,c=a.useRef(null);if(Qz(c),Xz(d1(t),`icon should be icon definiton, but got ${t}`),!d1(t))return null;const u=t;return ay(u.icon,`svg-${u.name}`,{className:n,onClick:r,style:o,"data-icon":u.name,width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true",...l,ref:c})};TP.displayName="IconReact";function ly(){return ly=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{className:n,icon:r,spin:o,rotate:s,tabIndex:i,onClick:l,twoToneColor:c,...u}=e,{prefixCls:d="anticon",rootClassName:m}=a.useContext(tx),f=H(m,d,{[`${d}-${r.name}`]:!!r.name,[`${d}-spin`]:!!o||r.name==="loading"},n);let p=i;p===void 0&&l&&(p=-1);const y=s?{msTransform:`rotate(${s}deg)`,transform:`rotate(${s}deg)`}:void 0;return a.createElement("span",ly({role:"img","aria-label":r.name},u,{ref:t,tabIndex:p,onClick:l,className:f}),a.createElement(TP,{icon:r,style:y}))});function cy(){return cy=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,cy({},e,{ref:t,icon:kz})),Uc=a.forwardRef(Jz);var MP={};Object.defineProperty(MP,"__esModule",{value:!0});var Zz={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"}}]},name:"close-circle",theme:"filled"},e5=MP.default=Zz;function uy(){return uy=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,uy({},e,{ref:t,icon:e5})),Bi=a.forwardRef(t5);var OP={};Object.defineProperty(OP,"__esModule",{value:!0});var n5={icon:{tag:"svg",attrs:{"fill-rule":"evenodd",viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"}}]},name:"close",theme:"outlined"},r5=OP.default=n5;function dy(){return dy=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,dy({},e,{ref:t,icon:r5})),Us=a.forwardRef(o5);var _P={};Object.defineProperty(_P,"__esModule",{value:!0});var s5={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"}}]},name:"exclamation-circle",theme:"filled"},i5=_P.default=s5;function fy(){return fy=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,fy({},e,{ref:t,icon:i5})),Li=a.forwardRef(a5);var zP={};Object.defineProperty(zP,"__esModule",{value:!0});var l5={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"}}]},name:"info-circle",theme:"filled"},c5=zP.default=l5;function my(){return my=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,my({},e,{ref:t,icon:c5})),ym=a.forwardRef(u5),jP=a.createContext({}),d5=e=>{const{children:t,...n}=e,r=a.useMemo(()=>({motion:n.motion}),[n.motion]);return a.createElement(jP.Provider,{value:r},t)},Io="none",Ou="appear",_u="enter",zu="leave",m1="none",lo="prepare",ri="start",oi="active",ox="end",BP="prepared";function p1(e,t){const n={};return n[e.toLowerCase()]=t.toLowerCase(),n[`Webkit${e}`]=`webkit${t}`,n[`Moz${e}`]=`moz${t}`,n[`ms${e}`]=`MS${t}`,n[`O${e}`]=`o${t.toLowerCase()}`,n}function f5(e,t){const n={animationend:p1("Animation","AnimationEnd"),transitionend:p1("Transition","TransitionEnd")};return e&&("AnimationEvent"in t||delete n.animationend.animation,"TransitionEvent"in t||delete n.transitionend.transition),n}const m5=f5(lr(),typeof window<"u"?window:{});let LP={};lr()&&({style:LP}=document.createElement("div"));const ju={};function kP(e){if(ju[e])return ju[e];const t=m5[e];if(t){const n=Object.keys(t),r=n.length;for(let o=0;or[1].toUpperCase());return e[n]}return`${e}-${t}`}const g5=e=>{const t=a.useRef();function n(o){o&&(o.removeEventListener(h1,e),o.removeEventListener(g1,e))}function r(o){t.current&&t.current!==o&&n(t.current),o&&o!==t.current&&(o.addEventListener(h1,e),o.addEventListener(g1,e),t.current=o)}return a.useEffect(()=>()=>{n(t.current),t.current=null},[]),[r,n]},FP=lr()?a.useLayoutEffect:a.useEffect,h5=()=>{const e=a.useRef(null);function t(){Ct.cancel(e.current)}function n(r,o=2){t();const s=Ct(()=>{o<=1?r({isCanceled:()=>s!==e.current}):n(r,o-1)});e.current=s}return a.useEffect(()=>()=>{t()},[]),[n,t]},y5=[lo,ri,oi,ox],v5=[lo,BP],HP=!1,b5=!0;function VP(e){return e===oi||e===ox}const x5=(e,t,n)=>{const[r,o]=_b(m1),[s,i]=h5();function l(){o(lo,!0)}const c=t?v5:y5;return FP(()=>{if(r!==m1&&r!==ox){const u=c.indexOf(r),d=c[u+1],m=n(r);m===HP?o(d,!0):d&&s(f=>{function p(){f.isCanceled()||o(d,!0)}m===!0?p():Promise.resolve(m).then(p)})}},[e,r]),a.useEffect(()=>()=>{i()},[]),[l,r]};function $5(e,t,n,{motionEnter:r=!0,motionAppear:o=!0,motionLeave:s=!0,motionDeadline:i,motionLeaveImmediately:l,onAppearPrepare:c,onEnterPrepare:u,onLeavePrepare:d,onAppearStart:m,onEnterStart:f,onLeaveStart:p,onAppearActive:y,onEnterActive:b,onLeaveActive:x,onAppearEnd:v,onEnterEnd:g,onLeaveEnd:h,onVisibleChanged:$}){const[C,N]=a.useState(),[S,E]=mO(Io),[w,R]=a.useState([null,null]),P=S(),T=a.useRef(!1),M=a.useRef(null);function z(){return n()}const B=a.useRef(!1);function F(){E(Io),R([null,null])}const L=vt(Y=>{const ee=S();if(ee===Io)return;const ie=z();if(Y&&!Y.deadline&&Y.target!==ie)return;const ae=B.current;let U;ee===Ou&&ae?U=v==null?void 0:v(ie,Y):ee===_u&&ae?U=g==null?void 0:g(ie,Y):ee===zu&&ae&&(U=h==null?void 0:h(ie,Y)),ae&&U!==!1&&F()}),[j]=g5(L),O=Y=>{switch(Y){case Ou:return{[lo]:c,[ri]:m,[oi]:y};case _u:return{[lo]:u,[ri]:f,[oi]:b};case zu:return{[lo]:d,[ri]:p,[oi]:x};default:return{}}},A=a.useMemo(()=>O(P),[P]),[k,_]=x5(P,!e,Y=>{var ee;if(Y===lo){const ie=A[lo];return ie?ie(z()):HP}return Y in A&&R([((ee=A[Y])==null?void 0:ee.call(A,z(),null))||null,Y]),Y===oi&&P!==Io&&(j(z()),i>0&&(clearTimeout(M.current),M.current=setTimeout(()=>{L({deadline:!0})},i))),Y===BP&&F(),b5}),D=VP(_);B.current=D;const V=a.useRef(null);FP(()=>{if(T.current&&V.current===t)return;N(t);const Y=T.current;T.current=!0;let ee;!Y&&t&&o&&(ee=Ou),Y&&t&&r&&(ee=_u),(Y&&!t&&s||!Y&&l&&!t&&s)&&(ee=zu);const ie=O(ee);ee&&(e||ie[lo])?(E(ee),k()):E(Io),V.current=t},[t]),a.useEffect(()=>{(P===Ou&&!o||P===_u&&!r||P===zu&&!s)&&E(Io)},[o,r,s]),a.useEffect(()=>()=>{T.current=!1,clearTimeout(M.current)},[]);const W=a.useRef(!1);a.useEffect(()=>{C&&(W.current=!0),C!==void 0&&P===Io&&((W.current||C)&&($==null||$(C)),W.current=!0)},[C,P]);let K=w[0];A[lo]&&_===ri&&(K={transition:"none",...K});const q=w[1];return[S,_,K,C??t,!T.current&&P===Io&&e&&o?"NONE":_===ri||_===oi?q===_:!0]}function WP(e){return(e==null?void 0:e.length)<2}function S5(e){let t=e;typeof e=="object"&&({transitionSupport:t}=e);function n(o,s){return!!(o.motionName&&t&&s!==!1)}const r=a.forwardRef((o,s)=>{const{visible:i=!0,removeOnLeave:l=!0,forceRender:c,children:u,motionName:d,leavedClassName:m,eventProps:f}=o,{motion:p}=a.useContext(jP),y=n(o,p),b=a.useRef();function x(){return go(b.current)}const[v,g,h,$,C]=$5(y,i,x,o),N=v(),S=a.useRef($);$&&(S.current=!0);const E=a.useMemo(()=>{const P={};return Object.defineProperties(P,{nativeElement:{enumerable:!0,get:x},inMotion:{enumerable:!0,get:()=>()=>v()!==Io},enableMotion:{enumerable:!0,get:()=>()=>y}}),P},[]);a.useImperativeHandle(s,()=>E,[]);const w=a.useRef(0);C&&(w.current+=1);const R=a.useMemo(()=>{if(C==="NONE")return null;let P;const T={...f,visible:i};if(!u)P=null;else if(N===Io)$?P=u({...T},b):!l&&S.current&&m?P=u({...T,className:m},b):c||!l&&!m?P=u({...T,style:{display:"none"}},b):P=null;else{let M;g===lo?M="prepare":VP(g)?M="active":g===ri&&(M="start");const z=y1(d,`${N}-${M}`);P=u({...T,className:H(y1(d,N),{[z]:z&&M,[d]:typeof d=="string"}),style:h},b)}return P},[w.current]);if(WP(u)&&kI(R)){const P=Bo(R);if(P!==b)return a.cloneElement(R,{ref:Tn(P,b)})}return R});return r.displayName="CSSMotion",r}const fr=S5(p5),py="add",gy="keep",hy="remove",Hp="removed";function C5(e){let t;return e&&typeof e=="object"&&"key"in e?t=e:t={key:e},{...t,key:String(t.key)}}function yy(e=[]){return e.map(C5)}function w5(e=[],t=[]){let n=[],r=0;const o=t.length,s=yy(e),i=yy(t);s.forEach(u=>{let d=!1;for(let m=r;m({...p,status:py}))),r=m),n.push({...f,status:gy}),r+=1,d=!0;break}}d||n.push({...u,status:hy})}),r({...u,status:py}))));const l={};return n.forEach(({key:u})=>{l[u]=(l[u]||0)+1}),Object.keys(l).filter(u=>l[u]>1).forEach(u=>{n=n.filter(({key:d,status:m})=>d!==u||m!==hy),n.forEach(d=>{d.key===u&&(d.status=gy)})}),n}function vy(){return vy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{this.setState(i=>({keyEntities:i.keyEntities.map(c=>c.key!==s?c:{...c,status:Hp})}),()=>{const{keyEntities:i}=this.state;i.filter(({status:c})=>c!==Hp).length===0&&this.props.onAllRemoved&&this.props.onAllRemoved()})})}static getDerivedStateFromProps({keys:s},{keyEntities:i}){const l=yy(s);return{keyEntities:w5(i,l).filter(u=>{const d=i.find(({key:m})=>u.key===m);return!(d&&d.status===Hp&&u.status===hy)})}}render(){const{keyEntities:s}=this.state,{component:i,children:l,onVisibleChanged:c,onAllRemoved:u,...d}=this.props,m=i||a.Fragment,f={};return E5.forEach(p=>{f[p]=d[p],delete d[p]}),delete d.keys,a.createElement(m,d,s.map(({status:p,...y},b)=>{const x=p===py||p===gy;return a.createElement(t,vy({},f,{key:y.key,visible:x,eventProps:y,onVisibleChanged:v=>{c==null||c(v,{key:y.key}),v||this.removeKey(y.key)}}),WP(l)?v=>l({...v,index:b}):(v,g)=>l({...v,index:b},g))}))}}return fe(n,"defaultProps",{component:"div"}),n}const KP=I5(),bn=e=>e!=null,$n=e=>bn(e)&&e!==!1&&e!=="",Rn=e=>typeof e=="number"&&!Number.isNaN(e),Zo=e=>typeof e=="string",dt=e=>e!==null&&typeof e=="object",bt=e=>typeof e=="function",Vp=e=>bn(e)&&bt(e.then),P5=e=>typeof e!="object"&&!bt(e)||e===null,UP=e=>dt(e)&&"propertyName"in e&&Zo(e.propertyName),by=(e,t)=>{const n={...e};return Object.keys(t).forEach(r=>{t[r]._default?n[r]||(n[r]={}):n[r]=by(n[r],t[r])}),n},qP=(e={},...t)=>t.filter(n=>!!n).reduce((n,r)=>(Object.keys(r).forEach(o=>{const s=e[o],i=r[o];if(s)if(dt(i))n[o]=qP(s,n[o],i);else{const{_default:l}=s;l&&(n[o]=n[o]||{},n[o][l]=H(n[o][l],i))}else n[o]=H(n[o],i)}),n),{}),N5=(e,...t)=>a.useMemo(()=>qP.apply(void 0,[e].concat(t)),[e].concat(t)),R5=(...e)=>e.filter(t=>!!t).reduce((t,n={})=>(Object.keys(n).forEach(r=>{t[r]={...t[r],...n[r]}}),t),{}),T5=(...e)=>a.useMemo(()=>R5.apply(void 0,e),[].concat(e)),Mt=e=>a.useMemo(()=>e?{root:e}:void 0,[e]),Ka=(e,t)=>bt(e)?e(t):e,Ot=(e,t,n,r)=>{const o=e.map(c=>c?Ka(c,n):void 0),s=t.map(c=>c?Ka(c,n):void 0),i=N5.apply(void 0,[r].concat($t(o))),l=T5.apply(void 0,$t(s));return a.useMemo(()=>r?[by(i,r),by(l,r)]:[i,l],[i,l,r])},Bu=(e,t,n)=>({background:e,[`${n}-icon`]:{color:t}}),M5=e=>{const{componentCls:t,motionDurationSlow:n,marginXS:r,marginSM:o,fontSize:s,fontSizeLG:i,lineHeight:l,borderRadiusLG:c,motionEaseInOutCirc:u,withDescriptionIconSize:d,colorText:m,colorTextHeading:f,withDescriptionPadding:p,defaultPadding:y,lineWidth:b,lineType:x,colorSuccessBorder:v,colorWarningBorder:g,colorErrorBorder:h,colorInfoBorder:$}=e;return{[t]:{...Ft(e),position:"relative",display:"flex",alignItems:"center",padding:y,wordWrap:"break-word",borderRadius:c,borderWidth:G(b),borderStyle:x,[`&${t}-success`]:{borderColor:v},[`&${t}-info`]:{borderColor:$},[`&${t}-warning`]:{borderColor:g},[`&${t}-error`]:{borderColor:h},[`&${t}-filled`]:{borderColor:"transparent"},[`&${t}-rtl`]:{direction:"rtl"},[`${t}-section`]:{flex:1,minWidth:0},[`${t}-icon`]:{marginInlineEnd:r,lineHeight:0},"&-description":{display:"none",fontSize:s,lineHeight:l},"&-title":{color:f},[`&${t}-motion-leave`]:{overflow:"hidden",opacity:1,transition:["max-height","opacity","padding-top","padding-bottom","margin-bottom"].map(C=>`${C} ${n} ${u}`).join(", ")},[`&${t}-motion-leave-active`]:{maxHeight:0,marginBottom:"0 !important",paddingTop:0,paddingBottom:0,opacity:0},[`&${t}-with-description`]:{alignItems:"flex-start",padding:p,[`${t}-icon`]:{marginInlineEnd:o,fontSize:d,lineHeight:0},[`${t}-title`]:{display:"block",marginBottom:r,color:f,fontSize:i},[`${t}-description`]:{display:"block",color:m}},[`&${t}-banner`]:{marginBottom:0,border:"0 !important",borderRadius:0}}}},O5=e=>{const{componentCls:t,colorSuccess:n,colorSuccessBg:r,colorWarning:o,colorWarningBg:s,colorError:i,colorErrorBg:l,colorInfo:c,colorInfoBg:u}=e;return{[t]:{"&-success":Bu(r,n,t),"&-info":Bu(u,c,t),"&-warning":Bu(s,o,t),"&-error":{...Bu(l,i,t),[`${t}-description > pre`]:{margin:0,padding:0}}}}},_5=e=>{const{componentCls:t,iconCls:n,motionDurationMid:r,marginXS:o,fontSizeIcon:s,colorIcon:i,colorIconHover:l}=e;return{[t]:{[`${t}-actions`]:{marginInlineStart:o},[`${t}-close-icon`]:{marginInlineStart:o,padding:0,overflow:"hidden",fontSize:s,lineHeight:G(s),backgroundColor:"transparent",border:"none",cursor:"pointer",...Br(e),[`${n}-close`]:{color:i,transition:`color ${r}`,"&:hover":{color:l}}},"&-close-text":{color:i,transition:`color ${r}`,"&:hover":{color:l}}}}},z5=e=>({withDescriptionIconSize:e.fontSizeHeading3,defaultPadding:`${e.paddingContentVerticalSM}px 12px`,withDescriptionPadding:`${e.paddingMD}px ${e.paddingContentHorizontalLG}px`}),j5=Tt("Alert",e=>[M5(e),O5(e),_5(e)],z5),B5=e=>{const{icon:t,type:n,className:r,style:o,successIcon:s,infoIcon:i,warningIcon:l,errorIcon:c}=e,u={success:s??a.createElement(Uc,null),info:i??a.createElement(ym,null),error:c??a.createElement(Bi,null),warning:l??a.createElement(Li,null)};return a.createElement("span",{className:r,style:o},t??u[n])},L5=e=>{const{isClosable:t,prefixCls:n,closeIcon:r,handleClose:o,ariaProps:s,className:i,style:l}=e,c=r===!0||r===void 0?a.createElement(Us,null):r;return t?a.createElement("button",{type:"button",onClick:o,className:H(`${n}-close-icon`,i),tabIndex:0,style:l,...s},c):null},GP=a.forwardRef((e,t)=>{const{description:n,prefixCls:r,message:o,title:s,banner:i,className:l,rootClassName:c,style:u,onMouseEnter:d,onMouseLeave:m,onClick:f,afterClose:p,showIcon:y,closable:b,closeText:x,closeIcon:v,action:g,id:h,styles:$,classNames:C,...N}=e,S=s??o,[E,w]=a.useState(!1),R=a.useRef(null);a.useImperativeHandle(t,()=>({nativeElement:R.current}));const{getPrefixCls:P,direction:T,variant:M,closable:z,closeIcon:B,className:F,style:L,classNames:j,styles:O,successIcon:A,infoIcon:k,warningIcon:_,errorIcon:D}=Pt("alert"),V=P("alert",r),[W,K]=j5(V),{onClose:q,afterClose:Y}=dt(b)?b:{},ee=Se=>{var ue;w(!0),(ue=q??e.onClose)==null||ue(Se)},ie=a.useMemo(()=>e.type!==void 0?e.type:i?"warning":"info",[e.type,i]),ae=e.variant??M??"outlined",U=a.useMemo(()=>dt(b)&&b.closeIcon||x?!0:typeof b=="boolean"?b:v!==!1&&bn(v)?!0:!!z,[x,v,b,z]),Q=i&&y===void 0?!0:y,Z={...e,prefixCls:V,variant:ae,type:ie,showIcon:Q,closable:U},ne=Mt(L),oe=Mt(u),[le,re]=Ot([j,C],[O,ne,$,oe],{props:Z}),X=H(V,`${V}-${ie}`,`${V}-${ae}`,{[`${V}-with-description`]:!!n,[`${V}-no-icon`]:!Q,[`${V}-banner`]:!!i,[`${V}-rtl`]:T==="rtl"},F,l,c,le.root,K,W),se=Nn(N,{aria:!0,data:!0}),ge=a.useMemo(()=>dt(b)&&b.closeIcon?b.closeIcon:x||(v!==void 0?v:dt(z)&&z.closeIcon?z.closeIcon:B),[v,b,z,x,B]),de=a.useMemo(()=>{const Se=b??z;return dt(Se)?Nn(Se,{data:!0,aria:!0}):{}},[b,z]);return a.createElement(fr,{visible:!E,motionName:`${V}-motion`,motionAppear:!1,motionEnter:!1,onLeaveStart:Se=>({maxHeight:Se.offsetHeight}),onLeaveEnd:Y??p},({className:Se,style:ue},be)=>a.createElement("div",{id:h,ref:Tn(R,be),"data-show":!E,className:H(X,Se),style:{...re.root,...ue},onMouseEnter:d,onMouseLeave:m,onClick:f,role:"alert",...se},Q?a.createElement(B5,{className:H(`${V}-icon`,le.icon),style:re.icon,description:n,icon:e.icon,prefixCls:V,type:ie,successIcon:A,infoIcon:k,warningIcon:_,errorIcon:D}):null,a.createElement("div",{className:H(`${V}-section`,le.section),style:re.section},S?a.createElement("div",{className:H(`${V}-title`,le.title),style:re.title},S):null,n?a.createElement("div",{className:H(`${V}-description`,le.description),style:re.description},n):null),g?a.createElement("div",{className:H(`${V}-actions`,le.actions),style:re.actions},g):null,a.createElement(L5,{className:le.close,style:re.close,isClosable:U,prefixCls:V,closeIcon:ge,handleClose:ee,ariaProps:de})))});function k5(e,t,n){return t=Va(t),mP(e,Yb()?Reflect.construct(t,n||[],Va(e).constructor):t.apply(e,n))}let A5=function(e){function t(){var n;return zi(this,t),n=k5(this,t,arguments),n.state={error:void 0,info:{}},n}return fP(t,e),ji(t,[{key:"componentDidCatch",value:function(r,o){this.setState({error:r,info:o})}},{key:"render",value:function(){const{message:r,title:o,description:s,id:i,children:l}=this.props,{error:c,info:u}=this.state,d=o??r,m=(u==null?void 0:u.componentStack)||null,f=bn(d)?d:c==null?void 0:c.toString(),p=bn(s)?s:m;return c?a.createElement(GP,{id:i,type:"error",title:f,description:a.createElement("pre",{style:{fontSize:"0.9em",overflowX:"auto"}},p)}):l}}])}(a.PureComponent);const Mo=GP;Mo.ErrorBoundary=A5;const v1=e=>typeof e=="object"&&e!=null&&e.nodeType===1,b1=(e,t)=>(!t||e!=="hidden")&&e!=="visible"&&e!=="clip",Lu=(e,t)=>{if(e.clientHeight{const o=(s=>{if(!s.ownerDocument||!s.ownerDocument.defaultView)return null;try{return s.ownerDocument.defaultView.frameElement}catch{return null}})(r);return!!o&&(o.clientHeightst||s>e&&i=t&&l>=n?s-e-r:i>t&&ln?i-t+o:0,D5=e=>{const t=e.parentElement;return t??(e.getRootNode().host||null)},x1=(e,t)=>{var n,r,o,s;if(typeof document>"u")return[];const{scrollMode:i,block:l,inline:c,boundary:u,skipOverflowHiddenElements:d}=t,m=typeof u=="function"?u:F=>F!==u;if(!v1(e))throw new TypeError("Invalid target");const f=document.scrollingElement||document.documentElement,p=[];let y=e;for(;v1(y)&&m(y);){if(y=D5(y),y===f){p.push(y);break}y!=null&&y===document.body&&Lu(y)&&!Lu(document.documentElement)||y!=null&&Lu(y,d)&&p.push(y)}const b=(r=(n=window.visualViewport)==null?void 0:n.width)!=null?r:innerWidth,x=(s=(o=window.visualViewport)==null?void 0:o.height)!=null?s:innerHeight,{scrollX:v,scrollY:g}=window,{height:h,width:$,top:C,right:N,bottom:S,left:E}=e.getBoundingClientRect(),{top:w,right:R,bottom:P,left:T}=(F=>{const L=window.getComputedStyle(F);return{top:parseFloat(L.scrollMarginTop)||0,right:parseFloat(L.scrollMarginRight)||0,bottom:parseFloat(L.scrollMarginBottom)||0,left:parseFloat(L.scrollMarginLeft)||0}})(e);let M=l==="start"||l==="nearest"?C-w:l==="end"?S+P:C+h/2-w+P,z=c==="center"?E+$/2-T+R:c==="end"?N+R:E-T;const B=[];for(let F=0;F=0&&E>=0&&S<=x&&N<=b&&(L===f&&!Lu(L)||C>=A&&S<=_&&E>=D&&N<=k))return B;const V=getComputedStyle(L),W=parseInt(V.borderLeftWidth,10),K=parseInt(V.borderTopWidth,10),q=parseInt(V.borderRightWidth,10),Y=parseInt(V.borderBottomWidth,10);let ee=0,ie=0;const ae="offsetWidth"in L?L.offsetWidth-L.clientWidth-W-q:0,U="offsetHeight"in L?L.offsetHeight-L.clientHeight-K-Y:0,Q="offsetWidth"in L?L.offsetWidth===0?0:O/L.offsetWidth:0,Z="offsetHeight"in L?L.offsetHeight===0?0:j/L.offsetHeight:0;if(f===L)ee=l==="start"?M:l==="end"?M-x:l==="nearest"?ku(g,g+x,x,K,Y,g+M,g+M+h,h):M-x/2,ie=c==="start"?z:c==="center"?z-b/2:c==="end"?z-b:ku(v,v+b,b,W,q,v+z,v+z+$,$),ee=Math.max(0,ee+g),ie=Math.max(0,ie+v);else{ee=l==="start"?M-A-K:l==="end"?M-_+Y+U:l==="nearest"?ku(A,_,j,K,Y+U,M,M+h,h):M-(A+j/2)+U/2,ie=c==="start"?z-D-W:c==="center"?z-(D+O/2)+ae/2:c==="end"?z-k+q+ae:ku(D,k,O,W,q+ae,z,z+$,$);const{scrollLeft:ne,scrollTop:oe}=L;ee=Z===0?0:Math.max(0,Math.min(oe+ee/Z,L.scrollHeight-j/Z+U)),ie=Q===0?0:Math.max(0,Math.min(ne+ie/Q,L.scrollWidth-O/Q+ae)),M+=oe-ee,z+=ne-ie}B.push({el:L,top:ee,left:ie})}return B},F5=e=>e===!1?{block:"end",inline:"nearest"}:(t=>t===Object(t)&&Object.keys(t).length!==0)(e)?e:{block:"start",inline:"nearest"};function H5(e,t){if(!e.isConnected||!(o=>{let s=o;for(;s&&s.parentNode;){if(s.parentNode===document)return!0;s=s.parentNode instanceof ShadowRoot?s.parentNode.host:s.parentNode}return!1})(e))return;const n=(o=>{const s=window.getComputedStyle(o);return{top:parseFloat(s.scrollMarginTop)||0,right:parseFloat(s.scrollMarginRight)||0,bottom:parseFloat(s.scrollMarginBottom)||0,left:parseFloat(s.scrollMarginLeft)||0}})(e);if((o=>typeof o=="object"&&typeof o.behavior=="function")(t))return t.behavior(x1(e,t));const r=typeof t=="boolean"||t==null?void 0:t.behavior;for(const{el:o,top:s,left:i}of x1(e,F5(t))){const l=s-n.top+n.bottom,c=i-n.left+n.right;o.scroll({top:l,left:c,behavior:r})}}const xy=e=>bn(e)&&e===e.window,V5=e=>{var n;if(typeof window>"u")return 0;let t=0;return xy(e)?t=e.pageYOffset:e instanceof Document?t=e.documentElement.scrollTop:(e instanceof HTMLElement||e)&&(t=e.scrollTop),e&&!xy(e)&&!Rn(t)&&(t=(n=(e.ownerDocument??e).documentElement)==null?void 0:n.scrollTop),t};function W5(e,t,n,r){const o=n-t;return e/=r/2,e<1?o/2*e*e*e+t:o/2*((e-=2)*e*e+2)+t}function K5(e,t={}){const{getContainer:n=()=>window,callback:r,duration:o=450}=t,s=n(),i=V5(s),l=Date.now();let c;const u=()=>{const m=Date.now()-l,f=W5(m>o?o:m,i,e,o);xy(s)?s.scrollTo(window.pageXOffset,f):s instanceof Document||s.constructor.name==="HTMLDocument"?s.documentElement.scrollTop=f:s.scrollTop=f,m{Ct.cancel(c)}}const on=e=>`${e}-css-var`,XP=a.createContext(void 0),YP={items_per_page:"/ page",jump_to:"Go to",jump_to_confirm:"confirm",page:"Page",prev_page:"Previous Page",next_page:"Next Page",prev_5:"Previous 5 Pages",next_5:"Next 5 Pages",prev_3:"Previous 3 Pages",next_3:"Next 3 Pages",page_size:"Page Size"},U5={yearFormat:"YYYY",dayFormat:"D",cellMeridiemFormat:"A",monthBeforeYear:!0},q5={...U5,locale:"en_US",today:"Today",now:"Now",backToToday:"Back to today",ok:"OK",clear:"Clear",week:"Week",month:"Month",year:"Year",timeSelect:"select time",dateSelect:"select date",weekSelect:"Choose a week",monthSelect:"Choose a month",yearSelect:"Choose a year",decadeSelect:"Choose a decade",previousMonth:"Previous month (PageUp)",nextMonth:"Next month (PageDown)",previousYear:"Last year (Control + left)",nextYear:"Next year (Control + right)",previousDecade:"Last decade",nextDecade:"Next decade",previousCentury:"Last century",nextCentury:"Next century"},QP={placeholder:"Select time",rangePlaceholder:["Start time","End time"]},$1={lang:{placeholder:"Select date",yearPlaceholder:"Select year",quarterPlaceholder:"Select quarter",monthPlaceholder:"Select month",weekPlaceholder:"Select week",rangePlaceholder:["Start date","End date"],rangeYearPlaceholder:["Start year","End year"],rangeQuarterPlaceholder:["Start quarter","End quarter"],rangeMonthPlaceholder:["Start month","End month"],rangeWeekPlaceholder:["Start week","End week"],...q5},timePickerLocale:{...QP}},Er="${label} is not a valid ${type}",Zr={locale:"en",Pagination:YP,DatePicker:$1,TimePicker:QP,Calendar:$1,global:{placeholder:"Please select",close:"Close",sortable:"sortable",show:"Show",hide:"Hide"},Table:{filterTitle:"Filter menu",filterConfirm:"OK",filterReset:"Reset",filterEmptyText:"No filters",filterCheckAll:"Select all items",filterSearchPlaceholder:"Search in filters",emptyText:"No data",selectAll:"Select current page",selectInvert:"Invert current page",selectNone:"Clear all data",selectionAll:"Select all data",sortTitle:"Sort",expand:"Expand row",collapse:"Collapse row",triggerDesc:"Click to sort descending",triggerAsc:"Click to sort ascending",cancelSort:"Click to cancel sorting"},Tour:{Next:"Next",Previous:"Previous",Finish:"Finish"},Modal:{okText:"OK",cancelText:"Cancel",justOkText:"OK"},Popconfirm:{okText:"OK",cancelText:"Cancel"},Transfer:{titles:["",""],searchPlaceholder:"Search here",itemUnit:"item",itemsUnit:"items",remove:"Remove",selectCurrent:"Select current page",removeCurrent:"Remove current page",selectAll:"Select all data",deselectAll:"Deselect all data",removeAll:"Remove all data",selectInvert:"Invert current page"},Upload:{uploading:"Uploading...",removeFile:"Remove file",uploadError:"Upload error",previewFile:"Preview file",downloadFile:"Download file"},Empty:{description:"No data"},Icon:{icon:"icon"},Text:{edit:"Edit",copy:"Copy",copied:"Copied",expand:"Expand",collapse:"Collapse"},Form:{optional:"(optional)",defaultValidateMessages:{default:"Field validation error for ${label}",required:"Please enter ${label}",enum:"${label} must be one of [${enum}]",whitespace:"${label} cannot be a blank character",date:{format:"${label} date format is invalid",parse:"${label} cannot be converted to a date",invalid:"${label} is an invalid date"},types:{string:Er,method:Er,array:Er,object:Er,number:Er,date:Er,boolean:Er,integer:Er,float:Er,regexp:Er,email:Er,url:Er,hex:Er},string:{len:"${label} must be ${len} characters",min:"${label} must be at least ${min} characters",max:"${label} must be up to ${max} characters",range:"${label} must be between ${min}-${max} characters"},number:{len:"${label} must be equal to ${len}",min:"${label} must be minimum ${min}",max:"${label} must be maximum ${max}",range:"${label} must be between ${min}-${max}"},array:{len:"Must be ${len} ${label}",min:"At least ${min} ${label}",max:"At most ${max} ${label}",range:"The amount of ${label} must be between ${min}-${max}"},pattern:{mismatch:"${label} does not match the pattern ${pattern}"}}},QRCode:{expired:"QR code expired",refresh:"Refresh",scanned:"Scanned"},ColorPicker:{presetEmpty:"Empty",transparent:"Transparent",singleColor:"Single",gradientColor:"Gradient"}};let xd={...Zr.Modal},$d=[];const S1=()=>$d.reduce((e,t)=>({...e,...t}),Zr.Modal);function G5(e){if(e){const t={...e};return $d.push(t),xd=S1(),()=>{$d=$d.filter(n=>n!==t),xd=S1()}}xd={...Zr.Modal}}function JP(){return xd}const sx=a.createContext(void 0),Ar=(e,t)=>{const n=a.useContext(sx),r=a.useMemo(()=>{const s=t||Zr[e],i=(n==null?void 0:n[e])??{};return{...bt(s)?s():s,...i||{}}},[e,t,n]),o=a.useMemo(()=>{const s=n==null?void 0:n.locale;return n!=null&&n.exist&&!s?Zr.locale:s},[n]);return[r,o]},X5="internalMark",Y5=e=>{const{locale:t={},children:n,_ANT_MARK__:r}=e;a.useEffect(()=>G5(t==null?void 0:t.Modal),[t]);const o=a.useMemo(()=>({...t,exist:!0}),[t]);return a.createElement(sx.Provider,{value:o},n)},ZP=a.createContext(null);let Q5=!1;function J5(e){return Q5}const C1=[];function Z5(e,t){const[n]=a.useState(()=>lr()?document.createElement("div"):null),r=a.useRef(!1),o=a.useContext(ZP),[s,i]=a.useState(C1),l=o||(r.current?void 0:d=>{i(m=>[d,...m])});function c(){n.parentElement||document.body.appendChild(n),r.current=!0}function u(){var d;(d=n.parentElement)==null||d.removeChild(n),r.current=!1}return It(()=>(e?o?o(c):c():u(),u),[e]),It(()=>{s.length&&(s.forEach(d=>d()),i(C1))},[s]),[n,l]}function ej(){return document.body.scrollHeight>(window.innerHeight||document.documentElement.clientHeight)&&window.innerWidth>document.body.offsetWidth}const tj=`rc-util-locker-${Date.now()}`;let w1=0;function nj(e){const t=!!e,[n]=a.useState(()=>(w1+=1,`${tj}_${w1}`));It(()=>{if(t){const r=zh(document.body).width,o=ej();vi(` +html body { + overflow-y: hidden; + ${o?`width: calc(100% - ${r}px);`:""} +}`,n)}else xc(n);return()=>{xc(n)}},[t,n])}let di=[];const rj=200;let eN=0;const tN=e=>{if(e.key==="Escape"&&!e.isComposing){if(Date.now()-eN=0;r-=1)di[r].onEsc({top:r===n-1,event:e})}},nN=()=>{eN=Date.now()};function oj(){window.addEventListener("keydown",tN),window.addEventListener("compositionend",nN)}function sj(){di.length===0&&(window.removeEventListener("keydown",tN),window.removeEventListener("compositionend",nN))}function ij(e,t){const n=jo(),r=vt(t),o=()=>{di.find(i=>i.id===n)||di.push({id:n,onEsc:r})},s=()=>{di=di.filter(i=>i.id!==n)};a.useMemo(()=>{e?o():e||s()},[e]),a.useEffect(()=>{if(e)return o(),oj(),()=>{s(),sj()}},[e])}const E1=e=>e===!1?!1:!lr()||!e?null:typeof e=="string"?document.querySelector(e):typeof e=="function"?e():e,vm=a.forwardRef((e,t)=>{const{open:n,autoLock:r,getContainer:o,debug:s,autoDestroy:i=!0,children:l,onEsc:c}=e,[u,d]=a.useState(n),m=u||n;a.useEffect(()=>{(i||n)&&d(n)},[n,i]);const[f,p]=a.useState(()=>E1(o));a.useEffect(()=>{const C=E1(o);p(()=>C??null)});const[y,b]=Z5(m&&!f),x=f??y;nj(r&&n&&lr()&&(x===y||x===document.body)),ij(n,c);let v=null;l&&is(l)&&t&&(v=Bo(l));const g=$o(v,t);if(!m||!lr()||f===void 0)return null;const h=x===!1||J5();let $=l;return t&&($=a.cloneElement(l,{ref:g})),a.createElement(ZP.Provider,{value:b},h?$:ss.createPortal($,x))});function aj(e){const{prefixCls:t,align:n,arrow:r,arrowPos:o}=e,{className:s,content:i,style:l}=r||{},{x:c=0,y:u=0}=o,d=a.useRef(null);if(!n||!n.points)return null;const m={position:"absolute"};if(n.autoArrow!==!1){const f=n.points[0],p=n.points[1],y=f[0],b=f[1],x=p[0],v=p[1];y===x||!["t","b"].includes(y)?m.top=u:y==="t"?m.top=0:m.bottom=0,b===v||!["l","r"].includes(b)?m.left=c:b==="l"?m.left=0:m.right=0}return a.createElement("div",{ref:d,className:H(`${t}-arrow`,s),style:{...m,...l}},i)}function $y(){return $y=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement("div",{style:{zIndex:r},className:H(`${t}-mask`,i&&`${t}-mobile-mask`,l)})):null}const cj=a.memo(({children:e})=>e,(e,t)=>t.cache);function rN(e,t,n,r,o,s,i,l){var d;const c="auto",u=e?{}:{left:"-1000vw",top:"-1000vh",right:c,bottom:c};if(!e&&(t||!n)){const{points:m}=r,f=r.dynamicInset||((d=r._experimental)==null?void 0:d.dynamicInset),p=f&&m[0][1]==="r",y=f&&m[0][0]==="b";p?(u.right=o,u.left=c):(u.left=i,u.right=c),y?(u.bottom=s,u.top=c):(u.top=l,u.bottom=c)}return u}function Sy(){return Sy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{onEsc:n,popup:r,className:o,prefixCls:s,style:i,target:l,onVisibleChanged:c,open:u,keepDom:d,fresh:m,onClick:f,mask:p,arrow:y,arrowPos:b,align:x,motion:v,maskMotion:g,mobile:h,forceRender:$,getPopupContainer:C,autoDestroy:N,portal:S,children:E,zIndex:w,onMouseEnter:R,onMouseLeave:P,onPointerEnter:T,onPointerDownCapture:M,ready:z,offsetX:B,offsetY:F,offsetR:L,offsetB:j,onAlign:O,onPrepare:A,onResize:k,stretch:_,targetWidth:D,targetHeight:V}=e,W=typeof r=="function"?r():r,K=u||d,q=!!h,[Y,ee,ie]=a.useMemo(()=>h?[h.mask,h.maskMotion,h.motion]:[p,g,v],[h,p,g,v]),ae=(C==null?void 0:C.length)>0,[U,Q]=a.useState(!C||!ae);It(()=>{!U&&ae&&l&&Q(!0)},[U,ae,l]);const Z=vt((le,re)=>{k==null||k(le,re),O()}),ne=rN(q,z,u,x,L,j,B,F);if(!U)return null;const oe={};return _&&(_.includes("height")&&V?oe.height=V:_.includes("minHeight")&&V&&(oe.minHeight=V),_.includes("width")&&D?oe.width=D:_.includes("minWidth")&&D&&(oe.minWidth=D)),u||(oe.pointerEvents="none"),a.createElement(S,{open:$||K,getContainer:C&&(()=>C(l)),autoDestroy:N,onEsc:n},a.createElement(lj,{prefixCls:s,open:u,zIndex:w,mask:Y,motion:ee,mobile:q}),a.createElement(ir,{onResize:Z,disabled:!u},le=>a.createElement(fr,Sy({motionAppear:!0,motionEnter:!0,motionLeave:!0,removeOnLeave:!1,forceRender:$,leavedClassName:`${s}-hidden`},ie,{onAppearPrepare:A,onEnterPrepare:A,visible:u,onVisibleChanged:re=>{var X;(X=v==null?void 0:v.onVisibleChanged)==null||X.call(v,re),c(re)}}),({className:re,style:X},se)=>{const ge=H(s,re,o,{[`${s}-mobile`]:q});return a.createElement("div",{ref:Tn(le,t,se),className:ge,style:{"--arrow-x":`${b.x||0}px`,"--arrow-y":`${b.y||0}px`,...ne,...oe,...X,boxSizing:"border-box",zIndex:w,...i},onMouseEnter:R,onMouseLeave:P,onPointerEnter:T,onClick:f,onPointerDownCapture:M},y&&a.createElement(aj,{prefixCls:s,arrow:y,arrowPos:b,align:x}),a.createElement(cj,{cache:!u&&!m},W))})),E)}),of=a.createContext(null),sN=a.createContext(null);function I1(e){return e?Array.isArray(e)?e:[e]:[]}function uj(e,t,n){return a.useMemo(()=>{const r=I1(t??e),o=I1(n??e),s=new Set(r),i=new Set(o);return s.has("hover")&&!s.has("click")&&s.add("touch"),i.has("hover")&&!i.has("click")&&i.add("touch"),[s,i]},[e,t,n])}function dj(e=[],t=[],n){const r=(o,s)=>o[s]||"";return n?r(e,0)===r(t,0):r(e,0)===r(t,0)&&r(e,1)===r(t,1)}function iN(e,t,n,r){var i;const{points:o}=n,s=Object.keys(e);for(let l=0;lr.includes(l))&&t.push(n),n=n.parentElement}return t}function Ic(e,t=1){return Number.isNaN(e)?t:e}function vl(e){return Ic(parseFloat(e),0)}function P1(e,t){const n={...e};return(t||[]).forEach(r=>{if(r instanceof HTMLBodyElement||r instanceof HTMLHtmlElement)return;const{overflow:o,overflowClipMargin:s,borderTopWidth:i,borderBottomWidth:l,borderLeftWidth:c,borderRightWidth:u}=qc(r).getComputedStyle(r),d=r.getBoundingClientRect(),{offsetHeight:m,clientHeight:f,offsetWidth:p,clientWidth:y}=r,b=vl(i),x=vl(l),v=vl(c),g=vl(u),h=Ic(Math.round(d.width/p*1e3)/1e3),$=Ic(Math.round(d.height/m*1e3)/1e3),C=(p-y-v-g)*h,N=(m-f-b-x)*$,S=b*$,E=x*$,w=v*h,R=g*h;let P=0,T=0;if(o==="clip"){const L=vl(s);P=L*h,T=L*$}const M=d.x+w-P,z=d.y+S-T,B=M+d.width+2*P-w-R-C,F=z+d.height+2*T-S-E-N;n.left=Math.max(n.left,M),n.top=Math.max(n.top,z),n.right=Math.min(n.right,B),n.bottom=Math.min(n.bottom,F)}),n}function N1(e,t=0){const n=`${t}`,r=n.match(/^(.*)\%$/);return r?e*(parseFloat(r[1])/100):parseFloat(n)}function R1(e,t){const[n,r]=t||[];return[N1(e.width,n),N1(e.height,r)]}function T1(e=""){return[e[0],e[1]]}function Xi(e,t){const n=t[0],r=t[1];let o,s;return n==="t"?s=e.y:n==="b"?s=e.y+e.height:s=e.y+e.height/2,r==="l"?o=e.x:r==="r"?o=e.x+e.width:o=e.x+e.width/2,{x:o,y:s}}function us(e,t){const n={t:"b",b:"t",l:"r",r:"l"},r=[...e];return r[t]=n[e[t]]||"c",r}function M1(e){return e.join("")}function aN(e,t,n,r,o,s,i,l){const[c,u]=a.useState({ready:!1,offsetX:0,offsetY:0,offsetR:0,offsetB:0,arrowX:0,arrowY:0,scaleX:1,scaleY:1,align:o[r]||{}}),d=a.useRef(0),m=a.useMemo(()=>!t||l?[]:Cy(t),[t]),f=a.useRef({});e||(()=>{f.current={}})();const y=vt(()=>{var h,$;if(t&&n&&e&&!l){let Ue=function(Ye,Et,Kt=se){const pr=O.x+Ye,Dr=O.y+Et,ko=pr+ee,Ao=Dr+Y,Do=Math.max(pr,Kt.left),Qe=Math.max(Dr,Kt.top),Bt=Math.min(ko,Kt.right),Ln=Math.min(Ao,Kt.bottom);return Math.max(0,(Bt-Do)*(Ln-Qe))},St=function(){te=O.y+Te,ye=te+Y,Ae=O.x+Re,Je=Ae+ee};var v=Ue,g=St;const C=t,N=C.ownerDocument,S=qc(C),{position:E}=S.getComputedStyle(C),w=C.style.left,R=C.style.top,P=C.style.right,T=C.style.bottom,M=C.style.overflow,z=C.style.overflowX,B=C.style.overflowY,F={...o[r],...s},L=N.createElement("div");(h=C.parentElement)==null||h.appendChild(L),L.style.left=`${C.offsetLeft}px`,L.style.top=`${C.offsetTop}px`,L.style.position=E,L.style.height=`${C.offsetHeight}px`,L.style.width=`${C.offsetWidth}px`,C.style.left="0",C.style.top="0",C.style.right="auto",C.style.bottom="auto",C.style.overflow="hidden";let j;if(Array.isArray(n))j={x:n[0],y:n[1],width:0,height:0};else{const Ye=n.getBoundingClientRect();Ye.x=Ye.x??Ye.left,Ye.y=Ye.y??Ye.top,j={x:Ye.x,y:Ye.y,width:Ye.width,height:Ye.height}}const O=C.getBoundingClientRect(),{height:A,width:k}=S.getComputedStyle(C);O.x=O.x??O.left,O.y=O.y??O.top;const{clientWidth:_,clientHeight:D,scrollWidth:V,scrollHeight:W,scrollTop:K,scrollLeft:q}=N.documentElement,Y=O.height,ee=O.width,ie=j.height,ae=j.width,U={left:0,top:0,right:_,bottom:D},Q={left:-q,top:-K,right:V-q,bottom:W-K};let{htmlRegion:Z}=F;const ne="visible",oe="visibleFirst";Z!=="scroll"&&Z!==oe&&(Z=ne);const le=Z===oe,re=P1(Q,m),X=P1(U,m),se=Z===ne?X:re,ge=le?X:se;C.style.left="auto",C.style.top="auto",C.style.right="0",C.style.bottom="0";const de=C.getBoundingClientRect();C.style.left=w,C.style.top=R,C.style.right=P,C.style.bottom=T,C.style.overflow=M,C.style.overflowX=z,C.style.overflowY=B,($=C.parentElement)==null||$.removeChild(L);const Se=Ic(Math.round(ee/parseFloat(k)*1e3)/1e3),ue=Ic(Math.round(Y/parseFloat(A)*1e3)/1e3);if(Se===0||ue===0||Da(n)&&!Vc(n))return;const{offset:be,targetOffset:Ne}=F;let[we,ze]=R1(O,be);const[he,ke]=R1(j,Ne);j.x-=he,j.y-=ke;const[Oe,Ce]=F.points||[],Me=T1(Ce),xe=T1(Oe),Ee=Xi(j,Me),Ve=Xi(O,xe),qe={...F};let me=[xe,Me],Re=Ee.x-Ve.x+we,Te=Ee.y-Ve.y+ze;const Ge=Ue(Re,Te),Fe=Ue(Re,Te,X),et=Xi(j,["t","l"]),ve=Xi(O,["t","l"]),je=Xi(j,["b","r"]),ce=Xi(O,["b","r"]),Pe=F.overflow||{},{adjustX:pe,adjustY:$e,shiftX:_e,shiftY:Ie}=Pe,Be=Ye=>typeof Ye=="boolean"?Ye:Ye>=0;let te,ye,Ae,Je;St();const ht=Be($e),Nt=xe[0]===Me[0];if(ht&&xe[0]==="t"&&(ye>ge.bottom||f.current.bt)){let Ye=Te;Nt?Ye-=Y-ie:Ye=et.y-ce.y-ze;const Et=Ue(Re,Ye),Kt=Ue(Re,Ye,X);Et>Ge||Et===Ge&&(!le||Kt>=Fe)?(f.current.bt=!0,Te=Ye,ze=-ze,me=[us(me[0],0),us(me[1],0)]):f.current.bt=!1}if(ht&&xe[0]==="b"&&(teGe||Et===Ge&&(!le||Kt>=Fe)?(f.current.tb=!0,Te=Ye,ze=-ze,me=[us(me[0],0),us(me[1],0)]):f.current.tb=!1}const yt=Be(pe),at=xe[1]===Me[1];if(yt&&xe[1]==="l"&&(Je>ge.right||f.current.rl)){let Ye=Re;at?Ye-=ee-ae:Ye=et.x-ce.x-we;const Et=Ue(Ye,Te),Kt=Ue(Ye,Te,X);Et>Ge||Et===Ge&&(!le||Kt>=Fe)?(f.current.rl=!0,Re=Ye,we=-we,me=[us(me[0],1),us(me[1],1)]):f.current.rl=!1}if(yt&&xe[1]==="r"&&(AeGe||Et===Ge&&(!le||Kt>=Fe)?(f.current.lr=!0,Re=Ye,we=-we,me=[us(me[0],1),us(me[1],1)]):f.current.lr=!1}qe.points=[M1(me[0]),M1(me[1])],St();const Ze=_e===!0?0:_e;typeof Ze=="number"&&(AeX.right&&(Re-=Je-X.right-we,j.x>X.right-Ze&&(Re+=j.x-X.right+Ze)));const De=Ie===!0?0:Ie;typeof De=="number"&&(teX.bottom&&(Te-=ye-X.bottom-ze,j.y>X.bottom-De&&(Te+=j.y-X.bottom+De)));const Le=O.x+Re,Ke=Le+ee,lt=O.y+Te,_t=lt+Y,ft=j.x,xt=ft+ae,jt=j.y,pt=jt+ie,qt=Math.max(Le,ft),cn=Math.min(Ke,xt),Cr=(qt+cn)/2-Le,mr=Math.max(lt,jt),wr=Math.min(_t,pt),Wt=(mr+wr)/2-lt;i==null||i(t,qe);let en=de.right-O.x-(Re+O.width),tt=de.bottom-O.y-(Te+O.height);Se===1&&(Re=Math.floor(Re),en=Math.floor(en)),ue===1&&(Te=Math.floor(Te),tt=Math.floor(tt));const wt={ready:!0,offsetX:Re/Se,offsetY:Te/ue,offsetR:en/Se,offsetB:tt/ue,arrowX:Cr/Se,arrowY:Wt/ue,scaleX:Se,scaleY:ue,align:qe};u(wt)}}),b=()=>{d.current+=1;const v=d.current;Promise.resolve().then(()=>{d.current===v&&y()})},x=()=>{u(v=>({...v,ready:!1}))};return It(x,[r]),It(()=>{e||x()},[e]),[c.ready,c.offsetX,c.offsetY,c.offsetR,c.offsetB,c.arrowX,c.arrowY,c.scaleX,c.scaleY,c.align,b]}function lN(){const e=a.useRef(null),t=()=>{e.current&&(clearTimeout(e.current),e.current=null)},n=(r,o)=>{t(),o===0?r():e.current=setTimeout(()=>{r()},o*1e3)};return a.useEffect(()=>()=>{t()},[]),n}function fj(e,t,n,r,o){It(()=>{if(e&&t&&n){let f=function(){r(),o()};var s=f;const i=t,l=n,c=Cy(i),u=Cy(l),d=qc(l),m=new Set([d,...c,...u]);return m.forEach(p=>{p.addEventListener("scroll",f,{passive:!0})}),d.addEventListener("resize",f,{passive:!0}),r(),()=>{m.forEach(p=>{p.removeEventListener("scroll",f),d.removeEventListener("resize",f)})}}},[e,t,n])}function mj(e,t,n,r,o,s,i,l){const c=a.useRef(e);c.current=e;const u=a.useRef(!1);a.useEffect(()=>{if(t&&r&&(!o||s)){const m=()=>{u.current=!1},f=b=>{var x,v;c.current&&!i(((v=(x=b.composedPath)==null?void 0:x.call(b))==null?void 0:v[0])||b.target)&&!u.current&&l(!1)},p=qc(r);p.addEventListener("pointerdown",m,!0),p.addEventListener("mousedown",f,!0),p.addEventListener("contextmenu",f,!0);const y=_h(n);return y&&(y.addEventListener("mousedown",f,!0),y.addEventListener("contextmenu",f,!0)),()=>{p.removeEventListener("pointerdown",m,!0),p.removeEventListener("mousedown",f,!0),p.removeEventListener("contextmenu",f,!0),y&&(y.removeEventListener("mousedown",f,!0),y.removeEventListener("contextmenu",f,!0))}}},[t,n,r,o,s]);function d(){u.current=!0}return d}function pj(){const[e,t]=J.useState(null),[n,r]=J.useState(!1),[o,s]=J.useState(!1),i=J.useRef(null),l=vt(u=>{u===!1?(i.current=null,r(!1)):o&&n?i.current=u:(r(!0),t(u),i.current=null,n||s(!0))}),c=vt(u=>{u?(s(!1),i.current&&(t(i.current),i.current=null)):(s(!1),i.current=null)});return[l,n,e,c]}function wy(){return wy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:t,isMobile:n,ready:r,open:o,align:s,offsetR:i,offsetB:l,offsetX:c,offsetY:u,arrowPos:d,popupSize:m,motion:f,uniqueContainerClassName:p,uniqueContainerStyle:y}=e,b=`${t}-unique-container`,[x,v]=J.useState(!1),g=rN(n,r,o,s,i,l,c,u),h=J.useRef(g);r&&(h.current=g);const $={};return m&&($.width=m.width,$.height=m.height),J.createElement(fr,wy({motionAppear:!0,motionEnter:!0,motionLeave:!0,removeOnLeave:!1,leavedClassName:`${b}-hidden`},f,{visible:o,onVisibleChanged:C=>{v(C)}}),({className:C,style:N})=>{const S=H(b,C,p,{[`${b}-visible`]:x});return J.createElement("div",{className:S,style:{"--arrow-x":`${(d==null?void 0:d.x)||0}px`,"--arrow-y":`${(d==null?void 0:d.y)||0}px`,...h.current,...$,...N,...y}})})},hj=({children:e,postTriggerProps:t})=>{const[n,r,o,s]=pj(),i=a.useMemo(()=>!o||!t?o:t(o),[o,t]),[l,c]=a.useState(null),[u,d]=a.useState(null),m=a.useRef(null),f=vt(j=>{m.current=j,Da(j)&&l!==j&&c(j)}),p=a.useRef(null),y=lN(),b=vt((j,O)=>{p.current=O,y(()=>{n(j)},j.delay)}),x=j=>{y(()=>{var O;(O=p.current)!=null&&O.call(p)||n(!1)},j)},v=vt(j=>{s(j)}),[g,h,$,C,N,S,E,,,w,R]=aN(r,l,i==null?void 0:i.target,i==null?void 0:i.popupPlacement,(i==null?void 0:i.builtinPlacements)||{},i==null?void 0:i.popupAlign,void 0,!1),P=a.useMemo(()=>{var O;if(!i)return"";const j=iN(i.builtinPlacements||{},i.prefixCls||"",w,!1);return H(j,(O=i.getPopupClassNameFromAlign)==null?void 0:O.call(i,w))},[w,i==null?void 0:i.getPopupClassNameFromAlign,i==null?void 0:i.builtinPlacements,i==null?void 0:i.prefixCls]),T=a.useMemo(()=>({show:b,hide:x}),[]);a.useEffect(()=>{R()},[i==null?void 0:i.target]);const M=vt(()=>(R(),Promise.resolve())),z=a.useRef({}),B=a.useContext(of),F=a.useMemo(()=>({registerSubPopup:(j,O)=>{z.current[j]=O,B==null||B.registerSubPopup(j,O)}}),[B]),L=i==null?void 0:i.prefixCls;return a.createElement(sN.Provider,{value:T},e,i&&a.createElement(of.Provider,{value:F},a.createElement(oN,{ref:f,portal:vm,onEsc:i.onEsc,prefixCls:L,popup:i.popup,className:H(i.popupClassName,P,`${L}-unique-controlled`),style:i.popupStyle,target:i.target,open:r,keepDom:!0,fresh:!0,autoDestroy:!1,onVisibleChanged:v,ready:g,offsetX:h,offsetY:$,offsetR:C,offsetB:N,onAlign:R,onPrepare:M,onResize:j=>d({width:j.offsetWidth,height:j.offsetHeight}),arrowPos:{x:S,y:E},align:w,zIndex:i.zIndex,mask:i.mask,arrow:i.arrow,motion:i.popupMotion,maskMotion:i.maskMotion,getPopupContainer:i.getPopupContainer},a.createElement(gj,{prefixCls:L,isMobile:!1,ready:g,open:r,align:w,offsetR:C,offsetB:N,offsetX:h,offsetY:$,arrowPos:{x:S,y:E},popupSize:u,motion:i.popupMotion,uniqueContainerClassName:H(i.uniqueContainerClassName,P),uniqueContainerStyle:i.uniqueContainerStyle}))))};function yj(e=vm){return a.forwardRef((n,r)=>{const{prefixCls:o="rc-trigger-popup",children:s,action:i="hover",showAction:l,hideAction:c,disabled:u=!1,popupVisible:d,defaultPopupVisible:m,onOpenChange:f,afterOpenChange:p,onPopupVisibleChange:y,afterPopupVisibleChange:b,mouseEnterDelay:x,mouseLeaveDelay:v=.1,focusDelay:g,blurDelay:h,mask:$,maskClosable:C=!0,getPopupContainer:N,forceRender:S,autoDestroy:E,popup:w,popupClassName:R,uniqueContainerClassName:P,uniqueContainerStyle:T,popupStyle:M,popupPlacement:z,builtinPlacements:B={},popupAlign:F,zIndex:L,stretch:j,getPopupClassNameFromAlign:O,fresh:A,unique:k,alignPoint:_,onPopupClick:D,onPopupAlign:V,arrow:W,popupMotion:K,maskMotion:q,mobile:Y,...ee}=n,ie=E||!1,ae=d===void 0,U=!!Y,Q=a.useRef({}),Z=a.useContext(of),ne=a.useMemo(()=>({registerSubPopup:(Qe,Bt)=>{Q.current[Qe]=Bt,Z==null||Z.registerSubPopup(Qe,Bt)}}),[Z]),oe=a.useContext(sN),le=jo(),[re,X]=a.useState(null),se=a.useRef(null),ge=vt(Qe=>{se.current=Qe,Da(Qe)&&re!==Qe&&X(Qe),Z==null||Z.registerSubPopup(le,Qe)}),[de,Se]=a.useState(null),ue=a.useRef(null),be=vt(Qe=>{const Bt=go(Qe);Da(Bt)&&de!==Bt&&(Se(Bt),ue.current=Bt)}),Ne={},we=vt(Qe=>{var Ln,Wi;const Bt=de;return(Bt==null?void 0:Bt.contains(Qe))||((Ln=_h(Bt))==null?void 0:Ln.host)===Qe||Qe===Bt||(re==null?void 0:re.contains(Qe))||((Wi=_h(re))==null?void 0:Wi.host)===Qe||Qe===re||Object.values(Q.current).some(Xs=>(Xs==null?void 0:Xs.contains(Qe))||Qe===Xs)}),ze=W?{...W!==!0?W:{}}:null,[he,ke]=nn(m||!1,d),Oe=he||!1,Ce=Oe&&!u,Me=a.useMemo(()=>{const Qe=typeof s=="function"?s({open:Ce}):s;return a.Children.only(Qe)},[s,Ce]),xe=(Me==null?void 0:Me.props)||{},Ee=vt(()=>Ce),Ve=vt((Qe=0)=>({popup:w,target:de,delay:Qe,prefixCls:o,popupClassName:R,uniqueContainerClassName:P,uniqueContainerStyle:T,popupStyle:M,popupPlacement:z,builtinPlacements:B,popupAlign:F,zIndex:L,mask:$,maskClosable:C,popupMotion:K,maskMotion:q,arrow:ze,getPopupContainer:N,getPopupClassNameFromAlign:O,id:le,onEsc:Ue}));It(()=>{oe&&k&&de&&!ae&&!Z&&(Ce?oe.show(Ve(x),Ee):oe.hide(v))},[Ce,de]);const qe=a.useRef(Ce);qe.current=Ce;const me=vt(Qe=>{ss.flushSync(()=>{Oe!==Qe&&(ke(Qe),f==null||f(Qe),y==null||y(Qe))})}),Re=lN(),Te=(Qe,Bt=0)=>{if(d!==void 0){Re(()=>{me(Qe)},Bt);return}if(oe&&k&&ae&&!Z){Qe?oe.show(Ve(Bt),Ee):oe.hide(Bt);return}Re(()=>{me(Qe)},Bt)};function Ue({top:Qe}){Qe&&Te(!1)}const[Ge,Fe]=a.useState(!1);It(Qe=>{(!Qe||Ce)&&Fe(!0)},[Ce]);const[et,ve]=a.useState(null),[je,ce]=a.useState(null),Pe=Qe=>{ce([Qe.clientX,Qe.clientY])},[pe,$e,_e,Ie,Be,te,ye,Ae,Je,St,ht]=aN(Ce,re,_&&je!==null?je:de,z,B,F,V,U),[Nt,yt]=uj(i,l,c),at=Nt.has("click"),Ze=yt.has("click")||yt.has("contextMenu"),De=vt(()=>{Ge||ht()});fj(Ce,de,re,De,()=>{qe.current&&_&&Ze&&Te(!1)}),It(()=>{De()},[je,z]),It(()=>{Ce&&!(B!=null&&B[z])&&De()},[JSON.stringify(F)]);const Ke=a.useMemo(()=>{const Qe=iN(B,o,St,_);return H(Qe,O==null?void 0:O(St))},[St,O,B,o,_]);a.useImperativeHandle(r,()=>({nativeElement:ue.current,popupElement:se.current,forceAlign:De}));const[lt,_t]=a.useState(0),[ft,xt]=a.useState(0),jt=()=>{if(j&&de){const Qe=de.getBoundingClientRect();_t(Qe.width),xt(Qe.height)}},pt=()=>{jt(),De()},qt=Qe=>{Fe(!1),ht(),p==null||p(Qe),b==null||b(Qe)},cn=()=>new Promise(Qe=>{jt(),ve(()=>Qe)});It(()=>{et&&(ht(),et(),ve(null))},[et]);function hn(Qe,Bt,Ln,Wi,Xs){Ne[Qe]=(y$,...B6)=>{var v$;(!Xs||!Xs())&&(Wi==null||Wi(y$),Te(Bt,Ln)),(v$=xe[Qe])==null||v$.call(xe,y$,...B6)}}const Cr=Nt.has("touch"),mr=yt.has("touch"),wr=a.useRef(!1);(Cr||mr)&&(Ne.onTouchStart=(...Qe)=>{var Bt;wr.current=!0,qe.current&&mr?Te(!1):!qe.current&&Cr&&Te(!0),(Bt=xe.onTouchStart)==null||Bt.call(xe,...Qe)}),(at||Ze)&&(Ne.onClick=(Qe,...Bt)=>{var Ln;qe.current&&Ze?Te(!1):!qe.current&&at&&(Pe(Qe),Te(!0)),(Ln=xe.onClick)==null||Ln.call(xe,Qe,...Bt),wr.current=!1});const mt=mj(Ce,Ze||mr,de,re,$,C,we,Te),Wt=Nt.has("hover"),en=yt.has("hover");let tt,wt;const Ye=()=>wr.current;if(Wt){const Qe=Bt=>{Pe(Bt)};hn("onMouseEnter",!0,x,Qe,Ye),hn("onPointerEnter",!0,x,Qe,Ye),tt=Bt=>{(Ce||Ge)&&(re!=null&&re.contains(Bt.target))&&Te(!0,x)},_&&(Ne.onMouseMove=Bt=>{var Ln;(Ln=xe.onMouseMove)==null||Ln.call(xe,Bt)})}en&&(hn("onMouseLeave",!1,v,void 0,Ye),hn("onPointerLeave",!1,v,void 0,Ye),wt=()=>{Te(!1,v)}),Nt.has("focus")&&hn("onFocus",!0,g),yt.has("focus")&&hn("onBlur",!1,h),Nt.has("contextMenu")&&(Ne.onContextMenu=(Qe,...Bt)=>{var Ln;qe.current&&yt.has("contextMenu")?Te(!1):(Pe(Qe),Te(!0)),Qe.preventDefault(),(Ln=xe.onContextMenu)==null||Ln.call(xe,Qe,...Bt)});const Et=a.useRef(!1);Et.current||(Et.current=S||Ce||Ge);const Kt={...xe,...Ne},pr={};["onContextMenu","onClick","onMouseDown","onTouchStart","onMouseEnter","onMouseLeave","onFocus","onBlur"].forEach(Qe=>{ee[Qe]&&(pr[Qe]=(...Bt)=>{var Ln;(Ln=Kt[Qe])==null||Ln.call(Kt,...Bt),ee[Qe](...Bt)})});const ko={x:te,y:ye};GI(Ce,de,pt);const Ao=$o(be,Bo(Me)),Do=a.cloneElement(Me,{...Kt,...pr,ref:Ao});return a.createElement(a.Fragment,null,Do,Et.current&&(!oe||!k)&&a.createElement(of.Provider,{value:ne},a.createElement(oN,{portal:e,ref:ge,prefixCls:o,popup:w,className:H(R,!U&&Ke),style:M,target:de,onMouseEnter:tt,onMouseLeave:wt,onPointerEnter:tt,zIndex:L,open:Ce,keepDom:Ge,fresh:A,onClick:D,onPointerDownCapture:mt,mask:$,motion:K,maskMotion:q,onVisibleChanged:qt,onPrepare:cn,forceRender:S,autoDestroy:ie,getPopupContainer:N,onEsc:Ue,align:St,arrow:ze,arrowPos:ko,ready:pe,offsetX:$e,offsetY:_e,offsetR:Ie,offsetB:Be,onAlign:De,stretch:j,targetWidth:lt/Ae,targetHeight:ft/Je,mobile:Y})))})}const bm=yj(vm);function cN(e){return e&&J.isValidElement(e)&&e.type===J.Fragment}const uN=(e,t,n)=>J.isValidElement(e)?J.cloneElement(e,bt(n)?n(e.props||{}):n):t;function Fn(e,t){return uN(e,e,t)}const vj=({children:e})=>{const{getPrefixCls:t}=J.useContext(ct),n=t();return J.isValidElement(e)?J.createElement(fr,{visible:!0,motionName:`${n}-fade`,motionAppear:!0,motionEnter:!0,motionLeave:!1,removeOnLeave:!1},({style:r,className:o})=>Fn(e,s=>({className:H(s.className,o),style:{...s.style,...r}}))):e},Au=[null,null];function bj(e){if(Au[0]!==e){const t={};Object.keys(e).forEach(n=>{t[n]={...e[n],dynamicInset:!1}}),Au[0]=e,Au[1]=t}return Au[1]}const dN=({children:e})=>{const t=n=>{const{id:r,builtinPlacements:o,popup:s}=n,i=bt(s)?s():s,l=bj(o);return{...n,getPopupContainer:null,arrow:!1,popup:J.createElement(vj,{key:r},i),builtinPlacements:l}};return J.createElement(hj,{postTriggerProps:t},e)},cr=a.createContext(!1),ix=({children:e,disabled:t})=>{const n=a.useContext(cr);return a.createElement(cr.Provider,{value:t??n},e)},Pi=a.createContext(void 0),xj=({children:e,size:t})=>{const n=a.useContext(Pi);return a.createElement(Pi.Provider,{value:t||n},e)};function $j(){const e=a.useContext(cr),t=a.useContext(Pi);return{componentDisabled:e,componentSize:t}}function Sj(e,t,n){yo();const r=e||{},o=r.inherit===!1||!t?{...Ec,hashed:(t==null?void 0:t.hashed)??Ec.hashed,cssVar:t==null?void 0:t.cssVar}:t,s=a.useId();return _i(()=>{var u;if(!e)return t;const i={...o.components};Object.keys(e.components||{}).forEach(d=>{i[d]={...i[d],...e.components[d]}});const l=`css-var-${s.replace(/:/g,"")}`,c={prefix:n==null?void 0:n.prefixCls,...o.cssVar,...r.cssVar,key:((u=r.cssVar)==null?void 0:u.key)||l};return{...o,...r,token:{...o.token,...r.token},components:i,cssVar:c}},[r,o,n==null?void 0:n.prefixCls,s],(i,l)=>i.some((c,u)=>{const d=l[u];return!ho(c,d,!0)}))}const O1=a.createContext(!0);function Cj(e){const t=a.useContext(O1),{children:n}=e,[,r]=Yn(),{motion:o}=r,s=a.useRef(!1);return s.current||(s.current=t!==o),s.current?a.createElement(O1.Provider,{value:o},a.createElement(d5,{motion:o},n)):n}const wj=()=>null,Ej=({iconPrefixCls:e,csp:t})=>(Bz(e,t),null),Ij=["getTargetContainer","getPopupContainer","renderEmpty","input","pagination","form","select","button"];let sf,fN,mN,pN;function Wp(){return sf||$c}function Pj(){return fN||dm}const Nj=e=>{const{prefixCls:t,iconPrefixCls:n,theme:r,holderRender:o}=e;t!==void 0&&(sf=t),n!==void 0&&(fN=n),"holderRender"in e&&(pN=o),r&&(mN=r)},gN=()=>({getPrefixCls:(e,t)=>t||(e?`${Wp()}-${e}`:Wp()),getIconPrefixCls:Pj,getRootPrefixCls:()=>sf||Wp(),getTheme:()=>mN,holderRender:pN}),Rj=e=>{const{children:t,csp:n,autoInsertSpaceInButton:r,alert:o,affix:s,anchor:i,app:l,form:c,locale:u,componentSize:d,direction:m,space:f,splitter:p,virtual:y,dropdownMatchSelectWidth:b,popupMatchSelectWidth:x,popupOverflow:v,legacyLocale:g,parentContext:h,iconPrefixCls:$,theme:C,componentDisabled:N,segmented:S,statistic:E,spin:w,calendar:R,carousel:P,cascader:T,collapse:M,typography:z,checkbox:B,descriptions:F,divider:L,drawer:j,skeleton:O,steps:A,image:k,layout:_,list:D,mentions:V,modal:W,progress:K,result:q,slider:Y,breadcrumb:ee,masonry:ie,menu:ae,pagination:U,input:Q,inputPassword:Z,inputSearch:ne,textArea:oe,otp:le,empty:re,badge:X,borderBeam:se,radio:ge,rate:de,ribbon:Se,switch:ue,transfer:be,avatar:Ne,message:we,tag:ze,table:he,card:ke,cardMeta:Oe,tabs:Ce,timeline:Me,timePicker:xe,upload:Ee,notification:Ve,tree:qe,colorPicker:me,datePicker:Re,rangePicker:Te,flex:Ue,wave:Ge,dropdown:Fe,warning:et,tour:ve,tooltip:je,popover:ce,popconfirm:Pe,qrcode:pe,floatButton:$e,floatButtonGroup:_e,variant:Ie,inputNumber:Be,treeSelect:te,watermark:ye}=e,Ae=a.useMemo(()=>{var ft;return dt(u)&&Object.prototype.hasOwnProperty.call(u,"default")&&((ft=u.default)!=null&&ft.locale)?u.default:u},[u]),Je=a.useCallback((ft,xt)=>{const{prefixCls:jt}=e;if(xt)return xt;const pt=jt||h.getPrefixCls("");return ft?`${pt}-${ft}`:pt},[h.getPrefixCls,e.prefixCls]),St=$||h.iconPrefixCls||dm,ht=n||h.csp,Nt=Sj(C,h.theme,{prefixCls:Je("")}),yt={csp:ht,autoInsertSpaceInButton:r,alert:o,affix:s,anchor:i,app:l,locale:Ae||g,direction:m,space:f,splitter:p,virtual:y,popupMatchSelectWidth:x??b,popupOverflow:v,getPrefixCls:Je,iconPrefixCls:St,theme:Nt,segmented:S,statistic:E,spin:w,calendar:R,carousel:P,cascader:T,collapse:M,typography:z,checkbox:B,descriptions:F,divider:L,drawer:j,skeleton:O,steps:A,image:k,input:Q,inputPassword:Z,inputSearch:ne,textArea:oe,otp:le,layout:_,list:D,mentions:V,modal:W,progress:K,result:q,slider:Y,breadcrumb:ee,masonry:ie,menu:ae,pagination:U,empty:re,badge:X,borderBeam:se,radio:ge,rate:de,ribbon:Se,switch:ue,transfer:be,avatar:Ne,message:we,tag:ze,table:he,card:ke,cardMeta:Oe,tabs:Ce,timeline:Me,timePicker:xe,upload:Ee,notification:Ve,tree:qe,colorPicker:me,datePicker:Re,rangePicker:Te,flex:Ue,wave:Ge,dropdown:Fe,warning:et,tour:ve,tooltip:je,popover:ce,popconfirm:Pe,qrcode:pe,floatButton:$e,floatButtonGroup:_e,variant:Ie,inputNumber:Be,treeSelect:te,watermark:ye},at={...h};Object.keys(yt).forEach(ft=>{yt[ft]!==void 0&&(at[ft]=yt[ft])}),Ij.forEach(ft=>{const xt=e[ft];xt&&(at[ft]=xt)}),typeof r<"u"&&(at.button={autoInsertSpace:r,...at.button});const Ze=_i(()=>at,at,(ft,xt)=>{const jt=Object.keys(ft),pt=Object.keys(xt);return jt.length!==pt.length||jt.some(qt=>ft[qt]!==xt[qt])}),{layer:De}=a.useContext(Wc),Le=a.useMemo(()=>({prefixCls:St,csp:ht,layer:De?"antd":void 0,zeroRuntime:!!De||(Nt==null?void 0:Nt.zeroRuntime)}),[St,ht,De,Nt==null?void 0:Nt.zeroRuntime]);let Ke=a.createElement(a.Fragment,null,a.createElement(Ej,{iconPrefixCls:St,csp:ht}),a.createElement(wj,{dropdownMatchSelectWidth:b}),t);const lt=a.useMemo(()=>{var ft,xt,jt,pt;return ba(((ft=Zr.Form)==null?void 0:ft.defaultValidateMessages)||{},((jt=(xt=Ze.locale)==null?void 0:xt.Form)==null?void 0:jt.defaultValidateMessages)||{},((pt=Ze.form)==null?void 0:pt.validateMessages)||{},(c==null?void 0:c.validateMessages)||{})},[Ze,c==null?void 0:c.validateMessages]);Object.keys(lt).length>0&&(Ke=a.createElement(XP.Provider,{value:lt},Ke)),Ae&&(Ke=a.createElement(Y5,{locale:Ae,_ANT_MARK__:X5},Ke)),Ke=a.createElement(tx.Provider,{value:Le},Ke),d&&(Ke=a.createElement(xj,{size:d},Ke)),Ke=a.createElement(Cj,null,Ke),je!=null&&je.unique&&(Ke=a.createElement(dN,null,Ke));const _t=a.useMemo(()=>{const{algorithm:ft,token:xt,components:jt,cssVar:pt,...qt}=Nt||{},cn=ft&&(!Array.isArray(ft)||ft.length>0)?tf(ft):Jb,hn={};Object.entries(jt||{}).forEach(([mr,wr])=>{const mt={...wr};"algorithm"in mt&&(mt.algorithm===!0?mt.theme=cn:(Array.isArray(mt.algorithm)||bt(mt.algorithm))&&(mt.theme=tf(mt.algorithm)),delete mt.algorithm),hn[mr]=mt});const Cr={...Wa,...xt};return{...qt,theme:cn,token:Cr,components:hn,override:{override:Cr,...hn},cssVar:pt}},[Nt]);return C&&(Ke=a.createElement(Zb.Provider,{value:_t},Ke)),Ze.warning&&(Ke=a.createElement(QO.Provider,{value:Ze.warning},Ke)),N!==void 0&&(Ke=a.createElement(ix,{disabled:N},Ke)),a.createElement(ct.Provider,{value:Ze},Ke)},to=e=>{const t=a.useContext(ct),n=a.useContext(sx);return a.createElement(Rj,{parentContext:t,legacyLocale:n,...e})};to.ConfigContext=ct;to.SizeContext=Pi;to.config=Nj;to.useConfig=$j;Object.defineProperty(to,"SizeContext",{get:()=>Pi});function Tj(){const[e,t]=a.useState({}),n=a.useCallback((r,o)=>{if(!o){t(i=>{if(!(r in i))return i;const l={...i};return delete l[r],l});return}const s={width:o.offsetWidth,height:o.offsetHeight};t(i=>{const l=i[r];return l&&l.width===s.width&&l.height===s.height?i:{...i,[r]:s}})},[]);return[e,n]}function Mj(e,t,n=0){const[r,o]=Tj(),[s,i,l,c]=a.useMemo(()=>{let u=0,d=0;const m=(t==null?void 0:t.threshold)??0,f=new Map;let p,y;return e.slice().reverse().forEach((b,x)=>{var $,C;const v=String(b.key),g=(($=r[v])==null?void 0:$.height)??0,h=t&&x>0?u+(t.offset??0)-g:u;f.set(v,h),x===0&&(p=g,y=((C=r[v])==null?void 0:C.width)??0),(!t||x{const t={offset:_1,threshold:z1};return e&&typeof e=="object"&&(t.offset=e.offset??_1,t.threshold=e.threshold??z1),[!!e,t]};function _j(e,t,n){const o=Math.max(typeof e=="number"?e:0,0)*1e3,s=vt(t),i=vt(n),[l,c]=a.useState(o>0),u=a.useRef(0),d=a.useRef(null);function m(){const y=Date.now(),b=d.current;b!==null&&(u.current+=y-b),d.current=y}const f=a.useCallback(()=>{m(),c(!1)},[]),p=a.useCallback(()=>{o>0?(d.current=Date.now(),c(!0)):i(0)},[o]);return a.useEffect(()=>{u.current=0,c(o>0)},[o]),a.useEffect(()=>{if(!l)return;let y=null;function b(){m(),u.current>=o?(i(1),s()):(i(Math.min(u.current/o,1)),y=Ct(b))}return b(),()=>{Ct.cancel(y)}},[o,l]),[p,f]}function zj(e){const t=a.useMemo(()=>e===!1?{closeIcon:null,disabled:!0}:typeof e=="object"&&e!==null?e:{},[e]),n=a.useMemo(()=>({...t,closeIcon:"closeIcon"in t?t.closeIcon:"×",disabled:t.disabled??!1}),[t]),r=a.useMemo(()=>Nn(n,!0),[n]);return[!!e,n,r]}const jj=({className:e,style:t,percent:n})=>a.createElement("progress",{className:e,max:"100",value:n,style:t});function af(){return af=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,className:r,style:o,classNames:s,styles:i,components:l,title:c,description:u,icon:d,actions:m,role:f,closable:p,offset:y,notificationIndex:b,stackInThreshold:x,props:v,duration:g=4.5,showProgress:h,hovering:$,pauseOnHover:C=!0,onClick:N,onMouseEnter:S,onMouseLeave:E,onClose:w}=e,[R,P]=a.useState(0),T=`${n}-notice`,[M,z,B]=zj(p),F=vt(()=>{var re;(re=z.onClose)==null||re.call(z),w==null||w()}),[L,j]=a.useState(!1),[O,A]=_j(g,F,P),k=100-Math.min(Math.max(R*100,0),100),_=(l==null?void 0:l.progress)||jj;a.useEffect(()=>{C&&($?A():L||O())},[$,L,A,O,C]);function D(re){j(!0),C&&A(),S==null||S(re)}function V(re){j(!1),C&&!$&&O(),E==null||E(re)}function W(re){re.preventDefault(),re.stopPropagation(),F()}const K=a.useRef(y);y!==void 0&&(K.current=y);const q=a.useRef(b);b!==void 0&&(q.current=b);const Y=y??K.current,ee=b??q.current??0,ie=c!=null?a.createElement("div",{className:H(`${T}-title`,s==null?void 0:s.title),style:i==null?void 0:i.title},c):null,ae=u!=null?a.createElement("div",{className:H(`${T}-description`,s==null?void 0:s.description),style:i==null?void 0:i.description},u):null,U=ie!==null,Q=ae!==null;let Z=null;U&&Q?Z=a.createElement("div",{className:H(`${T}-section`,s==null?void 0:s.section),style:i==null?void 0:i.section},ie,ae):Z=ie||ae,d!=null&&(Z=a.createElement("div",{className:H(`${T}-wrapper`,s==null?void 0:s.wrapper),style:i==null?void 0:i.wrapper},a.createElement("div",{className:H(`${T}-icon`,s==null?void 0:s.icon),style:i==null?void 0:i.icon},d),Z));const ne=m?a.createElement("div",{className:H(`${T}-actions`,s==null?void 0:s.actions),style:i==null?void 0:i.actions},m):null,oe={"--notification-index":ee,...i==null?void 0:i.root,...o};Y!==void 0&&(oe["--notification-y"]=`${Y}px`);const le=f??(v==null?void 0:v.role)??"alert";return a.createElement("div",af({},v,{ref:t,role:le,"data-notification-index":ee,className:H(T,r,s==null?void 0:s.root,{[`${T}-closable`]:M,[`${T}-stack-in-threshold`]:x}),style:oe,onClick:N,onMouseEnter:D,onMouseLeave:V}),Z,ne,M&&a.createElement("button",af({className:H(`${T}-close`,s==null?void 0:s.close),"aria-label":"Close"},B,{style:i==null?void 0:i.close,onClick:W}),z.closeIcon),h&&typeof g=="number"&&g>0&&a.createElement(_,{className:H(`${T}-progress`,s==null?void 0:s.progress),percent:k,style:i==null?void 0:i.progress}))}),yN=a.createContext({}),vN=({children:e,classNames:t})=>{const n=a.useMemo(()=>({classNames:t}),[t]);return a.createElement(yN.Provider,{value:n},e)};function Ey(){return Ey=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{listPrefixCls:n,height:r,topNoticeHeight:o=0,topNoticeWidth:s=0,className:i,style:l,...c}=e,u=`${n}-content`,d=a.useRef(r),m=d.current,f=r(t[n]=H(...e.map(r=>r==null?void 0:r[n])),t),{})}function kj(e){return bN.reduce((t,n)=>(t[n]=Object.assign({},...e.map(r=>r==null?void 0:r[n])),t),{})}function Aj(e,t){const n=String(t),r=e.findIndex(o=>o.key===n);if(r!==-1)return e.length-r-1}const Dj=e=>{const{config:t,components:n,contextClassNames:r,classNames:o,styles:s,className:i,style:l,nodeRef:c,listHovering:u,stackEnabled:d,pauseOnHover:m,setNodeSize:f,onNoticeClose:p,...y}=e,{key:b,placement:x,...v}=t,g=String(b),h=a.useCallback(C=>{f(g,C)},[f,g]),$=$o(c,h);return a.createElement(hN,lf({},v,y,{ref:$,className:H(r==null?void 0:r.notice,t.className,i),style:{...l,...t.style},classNames:Lj([o,t.classNames]),styles:kj([s,t.styles]),components:{...n,...t.components},hovering:d&&u,pauseOnHover:t.pauseOnHover??m,onClose:()=>{var C;(C=t.onClose)==null||C.call(t),p==null||p(b)}}))},xN=e=>{const{configList:t=[],prefixCls:n="rc-notification",pauseOnHover:r,classNames:o,styles:s,components:i,stack:l,motion:c,placement:u,className:d,style:m,onNoticeClose:f,onAllRemoved:p}=e,{classNames:y}=a.useContext(yN),b=a.useMemo(()=>t.map(j=>({config:j,key:String(j.key)})),[t]),x=typeof c=="function"?c(u):c,[v,{offset:g,threshold:h}]=Oj(l),[$,C]=a.useState(!1),N=v&&($||b.length<=h),S=a.useMemo(()=>{if(!(!v||N))return{offset:g,threshold:h}},[N,g,v,h]),[E,w]=a.useState(0),R=a.useRef(null),[P,T,M,z,B]=Mj(t,S,E),F=!!t.length;a.useEffect(()=>{const j=R.current;if(!j)return;const{gap:O,rowGap:A}=window.getComputedStyle(j),k=parseFloat(A||O)||0;w(_=>_===k?_:k)},[F]);const L=`${n}-list`;return a.createElement("div",{className:H(n,L,`${n}-${u}`,y==null?void 0:y.list,d,o==null?void 0:o.list,{[`${n}-stack`]:v,[`${n}-stack-expanded`]:N,[`${L}-hovered`]:$}),onMouseEnter:()=>{C(!0)},onMouseLeave:()=>{C(!1)},style:{...s==null?void 0:s.list,...m}},a.createElement(Bj,{listPrefixCls:L,height:M,topNoticeHeight:z,topNoticeWidth:B,className:o==null?void 0:o.listContent,style:s==null?void 0:s.listContent,ref:R},a.createElement(KP,lf({component:!1,keys:b,motionAppear:!0},x,{onAllRemoved:()=>{u&&(p==null||p(u))}}),({config:j,className:O,style:A},k)=>{const{key:_}=j,D=String(_),V=Aj(b,_),W=v&&V!==void 0&&V{const{prefixCls:n="rc-notification",container:r,motion:o,maxCount:s,pauseOnHover:i,classNames:l,styles:c,components:u,className:d,style:m,onAllRemoved:f,stack:p,renderNotifications:y}=e,[b,x]=a.useState([]),[v,g]=a.useState({}),h=a.useRef(!1);a.useImperativeHandle(t,()=>({open:N=>{x(S=>{var P;let E=[...S];const w=E.findIndex(T=>T.key===N.key),R={...N};return w>=0?(R.times=(((P=S[w])==null?void 0:P.times)??0)+1,E[w]=R):(R.times=0,E.push(R)),s&&s>0&&E.length>s&&(E=E.slice(-s)),E})},close:N=>{x(S=>S.filter(E=>E.key!==N))},destroy:()=>{x([])}})),a.useEffect(()=>{const N={};b.forEach(S=>{const E=S.placement??"topRight";N[E]=N[E]||[],N[E].push(S)}),Object.keys(v).forEach(S=>{N[S]=N[S]||[]}),g(N)},[b]);const $=vt(N=>{g(S=>{const E={...S};return(E[N]||[]).length||delete E[N],E})});if(a.useEffect(()=>{Object.keys(v).length>0?h.current=!0:h.current&&(f==null||f(),h.current=!1)},[v,f]),!r)return null;const C=Object.keys(v);return ss.createPortal(a.createElement(a.Fragment,null,C.map(N=>{const S=a.createElement(xN,{key:N,configList:v[N],placement:N,prefixCls:n,pauseOnHover:i,classNames:l,styles:c,components:u,className:d==null?void 0:d(N),style:m==null?void 0:m(N),motion:o,stack:p,onNoticeClose:E=>{x(w=>w.filter(R=>R.key!==E))},onAllRemoved:$});return y?a.cloneElement(y(S,{prefixCls:n,key:N}),{key:N}):S})),r)}),Hj=()=>document.body;let j1=0;function Vj(...e){const t={};return e.forEach(n=>{n&&Object.keys(n).forEach(r=>{const o=n[r];o!==void 0&&(t[r]=o)})}),t}function $N(e={}){const{getContainer:t=Hj,motion:n,prefixCls:r,placement:o,closable:s,duration:i,showProgress:l,pauseOnHover:c,classNames:u,styles:d,components:m,maxCount:f,className:p,style:y,onAllRemoved:b,stack:x,renderNotifications:v}=e,g={placement:o,closable:s,duration:i,showProgress:l},[h,$]=a.useState(),C=a.useRef(null),[N,S]=a.useState([]),E=a.createElement(Fj,{container:h,ref:C,prefixCls:r,motion:n,maxCount:f,pauseOnHover:c,classNames:u,styles:d,components:m,className:p,style:y,onAllRemoved:b,stack:x,renderNotifications:v}),w=vt(P=>{const T=Vj(g,P);(T.key===null||T.key===void 0)&&(T.key=`rc-notification-${j1}`,j1+=1),S(M=>[...M,{type:"open",config:T}])}),R=a.useMemo(()=>({open:w,close:P=>{S(T=>[...T,{type:"close",key:P}])},destroy:()=>{S(P=>[...P,{type:"destroy"}])}}),[]);return a.useEffect(()=>{$(t())}),a.useEffect(()=>{C.current&&N.length&&(N.forEach(P=>{var T,M,z;switch(P.type){case"open":(T=C.current)==null||T.open(P.config);break;case"close":(M=C.current)==null||M.close(P.key);break;case"destroy":(z=C.current)==null||z.destroy();break}}),S(P=>{const T=P.filter(M=>!N.includes(M));return T.length===P.length?P:T}))},[N]),[R,E]}const SN=(e,t)=>a.useMemo(()=>{const n=e??t;return n?{...dt(t)?t:{},...dt(n)?n:{}}:!1},[e,t]);function CN(e,t){return{...bn(e)&&{"--notification-top":G(e)},...bn(t)&&{"--notification-bottom":G(t)}}}function Wj(e){return{motionName:`${e}-fade`}}function Kj(e,t,n){return typeof e<"u"?e:typeof(t==null?void 0:t.closeIcon)<"u"?t.closeIcon:n==null?void 0:n.closeIcon}var wN={};Object.defineProperty(wN,"__esModule",{value:!0});var Uj={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"}}]},name:"loading",theme:"outlined"},qj=wN.default=Uj;function Iy(){return Iy=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Iy({},e,{ref:t,icon:qj})),ki=a.forwardRef(Gj);function Ur(...e){return e.find(t=>t!==void 0)}const EN=e=>{const{allowClear:t,clearIcon:n,contextAllowClear:r,contextClearIcon:o,defaultAllowClear:s,componentName:i}=e;return a.useMemo(()=>t??r??s?{clearIcon:Ur(dt(t)?t==null?void 0:t.clearIcon:n,dt(r)?r==null?void 0:r.clearIcon:o,J.createElement(Bi,null)),disabled:(dt(t)?t==null?void 0:t.disabled:void 0)??(dt(r)?r==null?void 0:r.disabled:void 0)}:!1,[t,n,r,o,s])},Ua=e=>{if(!e)return;const{closable:t,closeIcon:n}=e;return{closable:t,closeIcon:n}},IN={},B1=(e,t)=>{if(!e&&(e===!1||t===!1||t===null))return!1;if(!bn(e)&&!bn(t))return null;let n={closeIcon:typeof t!="boolean"&&bn(t)?t:void 0};return dt(e)&&(n={...n,...e}),n},Xj=(e,t,n)=>e===!1?!1:e?Fa(n,t,e):t===!1?!1:t?Fa(n,t):n.closable?n:!1,Yj=(e,t,n)=>{const{closeIconRender:r}=t,{closeIcon:o,...s}=e;let i=o;const l=Nn(s,!0);return bn(i)&&(r&&(i=r(i)),i=J.isValidElement(i)?J.cloneElement(i,{"aria-label":n,...i.props,...l}):J.createElement("span",{"aria-label":n,...l},i)),[i,l]},PN=(e,t,n=IN,r="Close")=>{const o=B1(e==null?void 0:e.closable,e==null?void 0:e.closeIcon),s=B1(t==null?void 0:t.closable,t==null?void 0:t.closeIcon),i={closeIcon:J.createElement(Us,null),...n},l=Xj(o,s,i),c=typeof l!="boolean"?!!(l!=null&&l.disabled):!1;if(l===!1)return[!1,null,c,{}];const[u,d]=Yj(l,i,r);return[!0,u,c,d]},NN=(e,t,n=IN)=>{const[r]=Ar("global",Zr.global);return J.useMemo(()=>PN(e,t,{closeIcon:J.createElement(Us,null),...n},r.close),[e,t,n,r.close])},RN=()=>J.useReducer(e=>e+1,0),Py=(e,t)=>{let n={};return dt(e)&&(n=e),typeof e=="boolean"&&(n={enabled:e}),n.closable===void 0&&t!==void 0&&(n.closable=t),n},Qj=(e,t,n,r)=>a.useMemo(()=>{const o=Py(e,r),s=Py(t),i={blur:!1,...s,...o,closable:o.closable??r??s.closable??!0},l=i.blur?`${n}-mask-blur`:void 0;return[i.enabled!==!1,{mask:l},!!i.closable]},[e,t,n,r]),Jj=e=>{const[t,n]=a.useState(null);return[a.useCallback((o,s,i)=>{const l=t??o,c=Math.min(l||0,o),u=Math.max(l||0,o),d=s.slice(c,u+1).map(e),m=d.some(p=>!i.has(p)),f=[];return d.forEach(p=>{m?(i.has(p)||f.push(p),i.add(p)):(i.delete(p),f.push(p))}),n(m?u:null),f},[t]),n]},L1=e=>e==="horizontal"||e==="vertical",Gc=(e,t,n)=>a.useMemo(()=>{const r=L1(e);let o;return r?o=e:typeof t=="boolean"?o=t?"vertical":"horizontal":o=L1(n)?n:"horizontal",[o,o==="vertical"]},[n,e,t]),Zj=()=>{const[e,t]=a.useState([]),n=a.useCallback(r=>(t(o=>[].concat($t(o),[r])),()=>{t(o=>o.filter(s=>s!==r))}),[]);return[e,n]},eB=(e,t)=>(e._antProxy=e._antProxy||{},Object.keys(t).forEach(n=>{if(!(n in e._antProxy)){const r=e[n];e._antProxy[n]=r,e[n]=t[n]}}),e),tB=(e,t)=>a.useImperativeHandle(e,()=>{const n=t(),{nativeElement:r}=n;return typeof Proxy<"u"?new Proxy(r,{get(o,s){return n[s]?n[s]:Reflect.get(o,s)}}):eB(r,n)}),nB=e=>{const t=a.useRef(e),[,n]=RN();return[()=>t.current,r=>{t.current=r,n()}]},xm=J.createContext(void 0),fs=100,rB=10,ax=fs*rB,TN={Modal:fs,Drawer:fs,Popover:fs,Popconfirm:fs,Tooltip:fs,Tour:fs,FloatButton:fs},oB={SelectLike:50,Dropdown:50,DatePicker:50,Menu:50,ImagePreview:1},sB=e=>e in TN,Xc=(e,t)=>{const[,n]=Yn(),r=J.useContext(xm),o=sB(e);let s;if(t!==void 0)s=[t,t];else{let i=r??0;o?i+=(r?0:n.zIndexPopupBase)+TN[e]:i+=oB[e],s=[r===void 0?t:i,i]}return s},iB=e=>{const{motionDurationMid:t,motionEaseInOut:n}=e,r=`${t} ${n}`;return{transform:"scale(var(--notification-scale, 1))",transition:["transform","inset","clip-path","opacity"].map(o=>`${o} ${r}`).join(", ")}},MN=(e,t)=>{const{componentCls:n,antCls:r,colorSuccess:o,colorInfo:s,colorWarning:i,colorError:l,colorTextHeading:c,colorText:u,boxShadow:d,borderRadiusLG:m,fontSize:f,lineHeight:p,notificationBg:y,notificationPadding:b,notificationMarginEdge:x,margin:v,calc:g}=e,h=`${n}-notice`,[$,C]=rn(r,"notification");return{[h]:{position:"absolute",width:t.width,maxWidth:`calc(100vw - ${G(g(x).mul(2).equal())})`,padding:b,pointerEvents:"auto",[$("icon-font-size")]:t.iconFontSize,[$("title-font-size")]:t.titleFontSize,[$("title-line-height")]:t.titleLineHeight,boxSizing:"border-box",color:u,background:y,borderRadius:m,boxShadow:d,fontSize:f,lineHeight:p,wordWrap:"break-word",overflow:"visible",...iB(e),...t.noticeStyle,"&::after":{position:"absolute",insetInline:0,top:g(v).mul(-1).equal(),height:v,content:'""'},...t.typeStyle&&{"&-success":{background:C("color-success-bg",y)},"&-error":{background:C("color-error-bg",y)},"&-info":{background:C("color-info-bg",y)},"&-warning":{background:C("color-warning-bg",y)}}},[`${h}-wrapper`]:{display:"flex",...t.contentStyle},[`${h}-title`]:{color:c,fontSize:C("title-font-size"),lineHeight:C("title-line-height")},[`${h}-icon`]:{flex:"none",fontSize:C("icon-font-size"),lineHeight:1,[`&${h}-icon-success`]:{color:o},[`&${h}-icon-info, &${h}-icon-loading`]:{color:s},[`&${h}-icon-warning`]:{color:i},[`&${h}-icon-error`]:{color:l}}}},ON=e=>{const{componentCls:t,progressBg:n,notificationProgressHeight:r,fontSize:o,borderRadiusLG:s,width:i,notificationIconSize:l,colorText:c,motionDurationMid:u,fontSizeLG:d,lineHeightLG:m,marginSM:f,marginXS:p,paddingLG:y,notificationPaddingVertical:b,notificationPaddingHorizontal:x,notificationCloseButtonSize:v,colorIcon:g,borderRadiusSM:h,colorIconHover:$,colorBgTextHover:C,colorBgTextActive:N}=e,S=`${t}-notice`;return{...MN(e,{width:i,iconFontSize:l,titleFontSize:d,titleLineHeight:m,contentStyle:{alignItems:"flex-start",gap:f},typeStyle:!0}),[`${S}-section`]:{display:"flex",flexDirection:"column",flex:"auto",gap:p,minWidth:0},[`${S}-description`]:{color:c,fontSize:o},[`${S}-closable`]:{[`${S}-title, ${S}-description`]:{paddingInlineEnd:y},[`${S}-title + ${S}-description`]:{paddingInlineEnd:0}},[`${S}-close`]:{position:"absolute",top:b,insetInlineEnd:x,display:"flex",alignItems:"center",justifyContent:"center",width:v,height:v,color:g,background:"none",border:"none",borderRadius:h,outline:"none",transition:["color","background-color"].map(E=>`${E} ${u}`).join(", "),"&:hover":{color:$,backgroundColor:C},"&:active":{backgroundColor:N},...Br(e)},[`${S}-progress`]:{position:"absolute",bottom:0,display:"block",appearance:"none",inlineSize:`calc(100% - ${G(s)} * 2)`,blockSize:r,border:0,left:{_skip_check_:!0,value:s},right:{_skip_check_:!0,value:s},"&, &::-webkit-progress-bar":{borderRadius:s,backgroundColor:"rgba(0, 0, 0, 0.04)"},"&::-moz-progress-bar":{background:n},"&::-webkit-progress-value":{borderRadius:s,background:n}},[`${S}-actions`]:{float:"right",marginTop:f}}},aB=e=>{const{componentCls:t,width:n}=e,r=`${t}-notice`,o=`${r}-actions`,s=ON(e);return{[`${r}-pure-panel`]:{width:n,maxWidth:"100%",...s,[r]:{...s[r],position:"relative",width:"100%",maxWidth:"100%"},[o]:{...s[o],float:"none",textAlign:"end"}}}},lB=e=>{const{componentCls:t}=e;return{[t]:ON(e)}},cB=["top","topLeft","topRight","bottom","bottomLeft","bottomRight"],uB="--notification-margin-edge",dB=(e,t)=>({blockEnd:e==="top"?"bottom":"top",inlineEnd:t==="left"?"right":"left"}),k1=e=>{const t=(e==null?void 0:e.x)??"0",n=(e==null?void 0:e.y)??"0";return`translate3d(${t}, ${n}, 0) scale(var(--notification-scale, 1))`},fB=(e,t)=>{const n=e.startsWith("bottom")?"bottom":"top",r=e.endsWith("Right")?"right":"left",{blockEnd:o,inlineEnd:s}=dB(n,r),i=e==="top"||e==="bottom",l=e==="top"||e.endsWith("Left")?`-${t}`:t;return{placement:e,vertical:n,blockEnd:o,horizontal:r,inlineEnd:s,motionOffset:i?{x:"-50%",y:l}:{x:l},baseMotionOffset:i?{x:"-50%"}:void 0,isCenterPlacement:i}},mB=e=>e==="bottom"?"column-reverse":"column",pB=e=>{const t=`var(${uB}, 0px)`;return`calc(var(--notification-${e}, ${t}) - ${t})`},gB=e=>e==="bottom"?"center top":"center bottom",_N=e=>G(e.calc(e.marginXXL).mul(-1).equal()),hB=e=>{const t=_N(e);return`inset(${t} ${t} ${t} ${t})`},yB=(e,t)=>{const n=_N(e);return t==="bottom"?`inset(${n} ${n} 50% ${n})`:`inset(50% ${n} ${n} ${n})`},vB=(e,t)=>{const{componentCls:n}=e,{placement:r,vertical:o,blockEnd:s,horizontal:i,inlineEnd:l,isCenterPlacement:c}=t,u=`${n}-notice`,d=`${u}${n}-fade`,m=k1(t.motionOffset),f=k1(t.baseMotionOffset),p=gB(o);return{[`&${n}-${r}`]:{[o]:pB(o),[s]:"auto",display:"flex",flexDirection:mB(o),...c?{marginInline:0,left:"50%",right:"auto",transform:"translateX(-50%)"}:{[i]:0,[l]:"auto"},[u]:{[o]:"var(--notification-y, 0)",...c?{left:"50%",transform:f}:{[i]:"var(--notification-x, 0)"},transformOrigin:p},[`${d}-appear-prepare, ${d}-enter-prepare`]:{opacity:0,transform:m,transition:"none"},[`${d}-appear-start, ${d}-enter-start`]:{opacity:0,transform:m},[`${d}-appear-active, ${d}-enter-active`]:{opacity:1,transform:f},[`${d}-leave-start`]:{opacity:1,transform:f},[`${d}-leave-active`]:{opacity:0,transform:m},[`&${n}-stack:not(${n}-stack-expanded)`]:{[u]:{clipPath:yB(e,o)},[`${u}[data-notification-index='0']`]:{clipPath:hB(e)}}}}},bB=(e,t=cB)=>{const{notificationMotionOffset:n}=e,r=G(n);return{...t.reduce((o,s)=>({...o,...vB(e,fB(s,r))}),{})}},xB=e=>{const{componentCls:t}=e;return{[t]:bB(e)}},$B=3,zN=e=>({zIndexPopup:e.zIndexPopupBase+ax+50,width:384,progressBg:`linear-gradient(90deg, ${e.colorPrimaryBorderHover}, ${e.colorPrimary})`,colorSuccessBg:void 0,colorErrorBg:void 0,colorInfoBg:void 0,colorWarningBg:void 0}),lx=e=>{const t=e.paddingMD,n=e.paddingLG;return Rt(e,{notificationBg:e.colorBgElevated,notificationPaddingVertical:t,notificationPaddingHorizontal:n,notificationIconSize:e.calc(e.fontSizeLG).mul(e.lineHeightLG).equal(),notificationCloseButtonSize:e.calc(e.controlHeightLG).mul(.55).equal(),notificationMarginBottom:e.margin,notificationPadding:`${G(e.paddingMD)} ${G(e.paddingContentHorizontalLG)}`,notificationMarginEdge:e.marginLG,notificationProgressHeight:2,notificationMotionOffset:64})},SB=e=>`inset(${e} ${e} ${e} ${e})`,CB=e=>{const{componentCls:t,motionDurationMid:n,motionDurationSlow:r,motionEaseInOut:o}=e,i=`${`${t}-list`}-content`;return{[i]:{position:"relative",display:"flex",flexShrink:0,flexDirection:"column",gap:e.notificationMarginBottom,width:"100%",willChange:"height, transform",transition:"none",[`&${i}-decrease`]:{transition:`height calc(${r} * 2) ${o} ${n}`}},[`${t}-fade`]:{backfaceVisibility:"hidden",willChange:"transform, opacity"}}},wB=(e,t)=>{const{componentCls:n,notificationMarginEdge:r}=e,o="--notification-margin-edge",s=`${n}-notice`,i=`${n}-list`,l=t.listWidthKey?e.calc(e[t.listWidthKey]).add(e.calc(r).mul(2)).equal():"100%",c=t.stackVisibleCount??$B,u=`${s}:nth-last-child(n + ${c+1})`,d=G(e.calc(e.marginXXL).mul(-1).equal()),m=SB(d);return{[n]:{...Ft(e),[o]:G(r),position:"fixed",zIndex:e.zIndexPopup,width:l,maxWidth:"100vw",height:"100vh",overflow:"hidden",overscrollBehavior:"contain",[`${n}-hook-holder`]:{position:"relative"},[`&${i}`]:{maxHeight:"100vh",padding:`var(${o})`,overflowX:"hidden",overflowY:"auto",overscrollBehavior:"contain",scrollbarWidth:"none",msOverflowStyle:"none",pointerEvents:"none","&::-webkit-scrollbar":{display:"none",width:0,height:0}},...CB(e),[`&${n}-stack`]:{[s]:{clipPath:m},[`&:not(${n}-stack-expanded)`]:{[s]:{"--notification-scale":"calc(1 - min(var(--notification-index, 0), 2) * 0.06)"},[`${s}:not(${s}-stack-in-threshold)`]:{opacity:0,pointerEvents:"none"},[u]:{opacity:0,pointerEvents:"none"}}},"&-rtl":{direction:"rtl",[`${s}-actions`]:{float:"left"}}}}};Ks(["Notification","PurePanel"],e=>aB(lx(e)),zN);const jN=(e,t)=>{const n=t.itemStyle??lB;return[wB(e,t),n(e),xB(e)]},EB=Tt("Notification",e=>{const t=lx(e);return jN(t,{listWidthKey:"width"})},zN),BN=e=>{const t=e.calc(e.controlHeightLG).sub(e.calc(e.fontSize).mul(e.lineHeight)).div(2).equal(),n=e.paddingSM;return Rt(lx(e),{notificationBg:e.contentBg,notificationPadding:e.contentPadding,notificationPaddingVertical:t,notificationPaddingHorizontal:n})},LN=e=>({zIndexPopup:e.zIndexPopupBase+ax+10,contentBg:e.colorBgElevated,contentPadding:`${(e.controlHeightLG-e.fontSize*e.lineHeight)/2}px ${e.paddingSM}px`}),cx=e=>{const{fontSize:t,fontSizeLG:n,lineHeight:r}=e;return MN(e,{width:"max-content",iconFontSize:n,titleFontSize:t,titleLineHeight:r,contentStyle:{alignItems:"center",gap:e.marginXS},noticeStyle:{zIndex:1}})},IB=e=>{const{componentCls:t}=e,n=`${t}-notice`,r=`${t}-list-content`,o=cx(e),{"&::after":s,...i}=o[n],l={...i,position:"absolute",zIndex:-1,left:"50%",height:e.calc(e.marginXS).mul(2).equal(),padding:0,boxShadow:e.boxShadowTertiary,opacity:0,pointerEvents:"none",transform:"translateX(-50%) translateY(100%)",transition:[`opacity ${e.motionDurationFast} ${e.motionEaseInOut}`,`transform ${e.motionDurationFast} ${e.motionEaseInOut}`,`width ${e.motionDurationSlow} ${e.motionEaseInOut}`].join(", "),content:'""'};return{[t]:{[`&${t}-stack`]:{[r]:{isolation:"isolate","&::before":{...l,top:`calc(var(--top-notificiation-height) - ${G(e.marginXS)})`,width:`calc(var(--top-notificiation-width) - ${G(e.margin)})`},"&::after":{...l,zIndex:-2,top:"var(--top-notificiation-height)",width:`calc(var(--top-notificiation-width) - ${G(e.calc(e.margin).mul(2).equal())})`}},[`&:not(${t}-stack-expanded)`]:{[r]:{"&::before, &::after":{opacity:1,transform:"translateX(-50%) translateY(0)"}}}}}}},PB=e=>{const{componentCls:t}=e,n=`${t}-notice`,r=cx(e);return{[`${n}-pure-panel`]:{width:"max-content",maxWidth:"100%",...r,[n]:{...r[n],position:"relative",width:"max-content",maxWidth:"100%"}}}},NB=Ks(["Message","PurePanel"],e=>PB(BN(e)),LN),RB=e=>({[e.componentCls]:cx(e)}),ux=Tt("Message",e=>{const t=BN(e);return[jN(t,{stackVisibleCount:1,itemStyle:RB}),IB(t)]},LN),TB={info:a.createElement(ym,null),success:a.createElement(Uc,null),error:a.createElement(Bi,null),warning:a.createElement(Li,null),loading:a.createElement(ki,null)},dx=(e,t)=>t||e&&TB[e]||null,MB=e=>{const{prefixCls:t,className:n,style:r,type:o,icon:s,content:i,classNames:l,styles:c,...u}=e,{getPrefixCls:d,className:m,style:f,classNames:p,styles:y}=Pt("message"),b=t||d("message"),x=`${b}-notice`,v=on(b),[g,h]=ux(b,v),[$,C]=Ot([p,l],[y,c],{props:e}),N=dx(o,s),S=o?`${x}-icon-${o}`:void 0,E={wrapper:H(o&&`${b}-${o}`,$.wrapper),icon:H(S,$.icon),title:$.title},w={wrapper:C.wrapper,icon:C.icon,title:C.title};return a.createElement("div",{className:H(`${x}-pure-panel`,g,n,h,v,$.root),style:C.root},a.createElement(NB,{prefixCls:b}),a.createElement(hN,{...u,prefixCls:b,className:m,style:{...f,...r},duration:null,icon:N,title:i,classNames:E,styles:w}))};function OB(e,t){return{motionName:t??`${e}-fade`}}function fx(e){let t;const n=new Promise(o=>{t=e(()=>{o(!0)})}),r=()=>{t==null||t()};return r.then=(o,s)=>n.then(o,s),r.promise=n,r}const _B=8,zB=3,jB=!1,BB=({children:e,prefixCls:t})=>{const n=on(t),[r,o]=ux(t,n);return a.createElement(vN,{classNames:{list:H(r,o,n)}},e)},LB=(e,{prefixCls:t,key:n})=>a.createElement(BB,{prefixCls:t,key:n},e),kB=a.forwardRef((e,t)=>{const{top:n,prefixCls:r,getContainer:o,maxCount:s,duration:i=zB,rtl:l,classNames:c,styles:u,transitionName:d,pauseOnHover:m=!0,stack:f,onAllRemoved:p}=e,{getPrefixCls:y,direction:b,getPopupContainer:x}=Pt("message"),{message:v}=a.useContext(ct),g=r||y("message"),[h,$]=Ot([v==null?void 0:v.classNames,c],[v==null?void 0:v.styles,u],{props:e}),C=()=>CN(n??_B),N=()=>H({[`${g}-rtl`]:l??b==="rtl"}),S=()=>OB(g,d),E=SN(f,jB),[w,R]=$N({prefixCls:g,style:C,className:N,motion:S,closable:!1,duration:i,getContainer:()=>(o==null?void 0:o())||(x==null?void 0:x())||document.body,maxCount:s,onAllRemoved:p,classNames:h,styles:$,renderNotifications:LB,pauseOnHover:m,stack:E});return a.useImperativeHandle(t,()=>({...w,prefixCls:g,message:v})),R});let A1=0;function kN(e){const t=a.useRef(null);return yo(),[a.useMemo(()=>{const r=c=>{var u;(u=t.current)==null||u.close(c)},o=c=>{if(!t.current){const B=()=>{};return B.then=()=>{},B}const{open:u,prefixCls:d,message:m}=t.current,f=(m==null?void 0:m.className)||{},p=(m==null?void 0:m.style)||{},y=`${d}-notice`,{content:b,icon:x,type:v,key:g,className:h,style:$,onClose:C,classNames:N={},styles:S={},...E}=c;let w=g;bn(w)||(A1+=1,w=`antd-message-${A1}`);const R={...e,...c},P=Ka(N,{props:R}),T=Ka(S,{props:R}),M=dx(v,x),z=v?`${y}-icon-${v}`:void 0;return fx(B=>(u({...E,key:w,icon:M,title:b,classNames:{...P,wrapper:H(v&&`${d}-${v}`,P.wrapper),icon:H(z,P.icon)},styles:T,placement:"top",className:H({[`${y}-${v}`]:v},h,f),style:{...p,...$},onClose:()=>{C==null||C(),B()}}),()=>{r(w)}))},i={open:o,destroy:c=>{var u;c!==void 0?r(c):(u=t.current)==null||u.destroy()}};return["info","success","warning","error","loading"].forEach(c=>{const u=(d,m,f)=>{let p;dt(d)&&"content"in d?p=d:p={content:d};let y,b;bt(m)?b=m:(y=m,b=f);const x={onClose:b,duration:y,...p,type:c};return o(x)};i[c]=u}),i},[]),a.createElement(kB,{key:"message-holder",...e,ref:t})]}function AN(e){return kN(e)}const Kp=()=>({height:0,opacity:0}),D1=e=>({height:(e==null?void 0:e.scrollHeight)??0,opacity:e?1:0}),AB=e=>({height:(e==null?void 0:e.offsetHeight)??0}),Up=(e,t)=>(t==null?void 0:t.deadline)===!0||UP(t)&&t.propertyName==="height",cf=(e=$c)=>({motionName:`${e}-motion-collapse`,onAppearStart:Kp,onEnterStart:Kp,onAppearActive:D1,onEnterActive:D1,onLeaveStart:AB,onLeaveActive:Kp,onAppearEnd:Up,onEnterEnd:Up,onLeaveEnd:Up,motionDeadline:500}),ks=(e,t,n)=>n!==void 0?n:`${e}-${t}`,DB=e=>{const{componentCls:t,colorPrimary:n,motionDurationSlow:r,motionEaseInOut:o,motionEaseOutCirc:s,antCls:i}=e,[,l]=rn(i,"wave");return{[t]:{position:"absolute",background:"transparent",pointerEvents:"none",boxSizing:"border-box",color:l("color",n),boxShadow:"0 0 0 0 currentcolor",opacity:.2,"&.wave-motion-appear":{transition:["box-shadow 0.4s","opacity 2s"].map(c=>`${c} ${s}`).join(","),"&-active":{boxShadow:"0 0 0 6px currentcolor",opacity:0},"&.wave-quick":{transition:["box-shadow","opacity"].map(c=>`${c} ${r} ${o}`).join(",")}}}}},FB=jz("Wave",DB),$m=`${$c}-wave-target`,F1=e=>e?Zo(e)&&e!=="#fff"&&e!=="#ffffff"&&e!=="rgb(255, 255, 255)"&&e!=="rgba(255, 255, 255, 1)"&&!/rgba\((?:\d*, ){3}0\)/i.test(e)&&!/^#(?:[0-9a-f]{3}0|[0-9a-f]{6}00)$/i.test(e)&&e!=="transparent"&&e!=="canvastext":!1;function HB(e,t=null){const n=getComputedStyle(e),{borderTopColor:r,borderColor:o,backgroundColor:s}=n;return t&&F1(n[t])?n[t]:[r,o,s].find(F1)??null}function qp(e){return Number.isNaN(e)?0:e}const VB=e=>{const{className:t,target:n,component:r,colorSource:o}=e,s=a.useRef(null),{getPrefixCls:i}=a.useContext(ct),l=i(),[c]=rn(l,"wave"),[u,d]=a.useState(null),[m,f]=a.useState([]),[p,y]=a.useState(0),[b,x]=a.useState(0),[v,g]=a.useState(0),[h,$]=a.useState(0),[C,N]=a.useState(!1),S={left:p,top:b,width:v,height:h,borderRadius:m.map(R=>`${R}px`).join(" ")};u&&(S[c("color")]=u);function E(){const R=getComputedStyle(n);d(HB(n,o));const P=R.position==="static",{borderLeftWidth:T,borderTopWidth:M}=R;y(P?n.offsetLeft:qp(-Number.parseFloat(T))),x(P?n.offsetTop:qp(-Number.parseFloat(M))),g(n.offsetWidth),$(n.offsetHeight);const{borderTopLeftRadius:z,borderTopRightRadius:B,borderBottomLeftRadius:F,borderBottomRightRadius:L}=R;f([z,B,L,F].map(j=>qp(Number.parseFloat(j))))}if(a.useEffect(()=>{if(n){const R=Ct(()=>{E(),N(!0)});let P;return typeof ResizeObserver<"u"&&(P=new ResizeObserver(E),P.observe(n)),()=>{Ct.cancel(R),P==null||P.disconnect()}}},[n]),!C)return null;const w=(r==="Checkbox"||r==="Radio")&&(n==null?void 0:n.classList.contains($m));return a.createElement(fr,{visible:!0,motionAppear:!0,motionName:"wave-motion",motionDeadline:5e3,onAppearEnd:(R,P)=>{var T;if(P.deadline||UP(P)&&P.propertyName==="opacity"){const M=(T=s.current)==null?void 0:T.parentElement;UI(M).then(()=>{M==null||M.remove()})}return!1}},({className:R},P)=>a.createElement("div",{ref:Tn(s,P),className:H(t,R,{"wave-quick":w}),style:S}))},WB=(e,t)=>{var o;const{component:n}=t;if(n==="Checkbox"&&!((o=e.querySelector("input"))!=null&&o.checked))return;const r=document.createElement("div");r.style.position="absolute",r.style.left="0px",r.style.top="0px",e==null||e.insertBefore(r,e==null?void 0:e.firstChild),Wb(a.createElement(VB,{...t,target:e}),r)},KB=(e,t,n,r)=>{const{wave:o}=a.useContext(ct),[,s,i]=Yn(),l=vt(d=>{const m=e.current;if(o!=null&&o.disabled||!m)return;const f=m.querySelector(`.${$m}`)||m,{showEffect:p}=o||{};(p||WB)(f,{className:t,token:s,component:n,event:d,hashId:i,colorSource:r})}),c=a.useRef(null);return a.useEffect(()=>()=>{Ct.cancel(c.current)},[]),d=>{Ct.cancel(c.current),c.current=Ct(()=>{l(d)})}},H1={click:"click",mousedown:"mousedown",mouseup:"mouseup",pointerdown:"pointerdown",pointerup:"pointerup"},Sm=e=>{const{children:t,disabled:n,component:r,colorSource:o}=e,{getPrefixCls:s,wave:i}=a.useContext(ct),l=a.useRef(null),c=s("wave"),u=FB(c),d=KB(l,H(c,u),r,o);if(J.useEffect(()=>{const f=l.current;if(!f||f.nodeType!==window.Node.ELEMENT_NODE||n)return;const p=x=>{!Vc(x.target)||!f.getAttribute||f.getAttribute("disabled")||f.disabled||f.className.includes("disabled")&&!f.className.includes("disabled:")||f.getAttribute("aria-disabled")==="true"||f.className.includes("-leave")||d(x)},y=i==null?void 0:i.triggerType,b=y&&y in H1?H1[y]:"click";return f.addEventListener(b,p,!0),()=>{f.removeEventListener(b,p,!0)}},[n,i==null?void 0:i.triggerType]),!J.isValidElement(t))return t??null;const m=is(t)?Tn(Bo(t),l):l;return Fn(t,{ref:m})},Cn=e=>{const t=J.useContext(Pi);return J.useMemo(()=>e?Zo(e)?e??t:bt(e)?e(t):t:t,[e,t])},UB=e=>{const{componentCls:t}=e;return{[t]:{display:"inline-flex","&-block":{display:"flex",width:"100%"},"&-vertical":{flexDirection:"column"},"&-rtl":{direction:"rtl"}}}},qB=Tt(["Space","Compact"],UB,()=>({}),{resetStyle:!1}),Cm=a.createContext(null),qs=(e,t)=>{const n=a.useContext(Cm),r=a.useMemo(()=>{if(!n)return"";const{compactDirection:o,isFirstItem:s,isLastItem:i}=n,l=o==="vertical"?"-vertical-":"-";return H(`${e}-compact${l}item`,{[`${e}-compact${l}first-item`]:s,[`${e}-compact${l}last-item`]:i,[`${e}-compact${l}item-rtl`]:t==="rtl"})},[e,t,n]);return{compactSize:n==null?void 0:n.compactSize,compactDirection:n==null?void 0:n.compactDirection,compactItemClassnames:r}},GB=e=>{const{children:t}=e;return a.createElement(Cm.Provider,{value:null},t)},XB=e=>{const{children:t,...n}=e;return a.createElement(Cm.Provider,{value:a.useMemo(()=>n,[n])},t)},mx=e=>{const{getPrefixCls:t,direction:n}=a.useContext(ct),{size:r,direction:o,orientation:s,block:i,prefixCls:l,className:c,rootClassName:u,children:d,vertical:m,...f}=e,[p,y]=Gc(s,m,o),b=Cn(N=>r??N),x=t("space-compact",l),[v]=qB(x),g=H(x,v,{[`${x}-rtl`]:n==="rtl",[`${x}-block`]:i,[`${x}-vertical`]:y},c,u),h=a.useContext(Cm),$=zn(d),C=a.useMemo(()=>$.map((N,S)=>{const E=(N==null?void 0:N.key)||`${x}-item-${S}`;return a.createElement(XB,{key:E,compactSize:b,compactDirection:p,isFirstItem:S===0&&(!h||(h==null?void 0:h.isFirstItem)),isLastItem:S===$.length-1&&(!h||(h==null?void 0:h.isLastItem))},N)}),[$,h,p,b,x]);return $.length===0?null:a.createElement("div",{className:g,...f},C)},DN=a.createContext(void 0),YB=e=>{const{getPrefixCls:t,direction:n}=a.useContext(ct),{prefixCls:r,size:o,className:s,...i}=e,l=t("btn-group",r),[,,c]=Yn(),u=a.useMemo(()=>{switch(o){case"large":return"lg";case"small":return"sm";default:return""}},[o]),d=H(l,{[`${l}-${u}`]:u,[`${l}-rtl`]:n==="rtl"},s,c);return a.createElement(DN.Provider,{value:o},a.createElement("div",{...i,className:d}))},V1=/^[\u4E00-\u9FA5]{2}$/,Ny=V1.test.bind(V1);function px(e){return e==="danger"?{danger:!0}:{type:e}}function Gp(e){return e==="text"||e==="link"}function QB(e,t,n,r){if(!$n(e))return;const o=t?" ":"";return!Zo(e)&&!Rn(e)&&Zo(e.type)&&Ny(e.props.children)?Fn(e,s=>{const i=H(s.className,r)||void 0,l={...n,...s.style};return{...s,children:s.children.split("").join(o),className:i,style:l}}):Zo(e)?J.createElement("span",{className:r,style:n},Ny(e)?e.split("").join(o):e):cN(e)?J.createElement("span",{className:r,style:n},e):Fn(e,s=>({...s,className:H(s.className,r)||void 0,style:{...s.style,...n}}))}function JB(e,t,n,r){let o=!1;const s=[];return J.Children.forEach(e,i=>{const l=Zo(i)||Rn(i);if(o&&l){const c=s.length-1,u=s[c];s[c]=`${u}${i}`}else s.push(i);o=l}),J.Children.map(s,i=>QB(i,t,n,r))}["default","primary","danger"].concat($t(Oo));const FN=a.forwardRef((e,t)=>{const{className:n,style:r,children:o,prefixCls:s}=e,i=H(`${s}-icon`,n);return J.createElement("span",{ref:t,className:i,style:r},o)}),W1=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,style:o,iconClassName:s}=e,i=H(`${n}-loading-icon`,r);return J.createElement(FN,{prefixCls:n,className:i,style:o,ref:t},J.createElement(ki,{className:s}))}),Xp=()=>({width:0,opacity:0,transform:"scale(0)"}),Yp=e=>({width:e.scrollWidth,opacity:1,transform:"scale(1)"}),ZB=e=>{const{prefixCls:t,loading:n,existIcon:r,className:o,style:s,mount:i}=e,l=!!n;return r?J.createElement(W1,{prefixCls:t,className:o,style:s}):J.createElement(fr,{visible:l,motionName:`${t}-loading-icon-motion`,motionAppear:!i,motionEnter:!i,motionLeave:!i,removeOnLeave:!0,onAppearStart:Xp,onAppearActive:Yp,onEnterStart:Xp,onEnterActive:Yp,onLeaveStart:Yp,onLeaveActive:Xp},({className:c,style:u},d)=>{const m={...s,...u};return J.createElement(W1,{prefixCls:t,className:H(o,c),style:m,ref:d})})},gx=e=>{const{componentCls:t,antCls:n,motionDurationMid:r,motionEaseInOut:o}=e;return{[t]:{[`${n}-motion-collapse-legacy`]:{overflow:"hidden","&-active":{transition:`${["height","opacity"].map(s=>`${s} ${r} ${o}`).join(", ")} !important`}},[`${n}-motion-collapse`]:{overflow:"hidden",transition:`${["height","opacity"].map(s=>`${s} ${r} ${o}`).join(", ")} !important`}}}},K1=e=>({animationDuration:e,animationFillMode:"both"}),wm=(e,t,n,r,o=!1)=>{const s=o?"&":"";return{[` + ${s}${e}-enter, + ${s}${e}-appear + `]:{...K1(r),animationPlayState:"paused"},[`${s}${e}-leave`]:{...K1(r),animationPlayState:"paused"},[` + ${s}${e}-enter${e}-enter-active, + ${s}${e}-appear${e}-appear-active + `]:{animationName:t,animationPlayState:"running"},[`${s}${e}-leave${e}-leave-active`]:{animationName:n,animationPlayState:"running",pointerEvents:"none"}}},eL=new Ht("antFadeIn",{"0%":{opacity:0},"100%":{opacity:1}}),tL=new Ht("antFadeOut",{"0%":{opacity:1},"100%":{opacity:0}}),HN=(e,t=!1)=>{const{antCls:n}=e,r=`${n}-fade`,o=t?"&":"";return[wm(r,eL,tL,e.motionDurationMid,t),{[` + ${o}${r}-enter, + ${o}${r}-appear + `]:{opacity:0,animationTimingFunction:"linear"},[`${o}${r}-leave`]:{animationTimingFunction:"linear"}}]},nL=new Ht("antMoveDownIn",{"0%":{transform:"translate3d(0, 100%, 0)",transformOrigin:"0 0",opacity:0},"100%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1}}),rL=new Ht("antMoveDownOut",{"0%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1},"100%":{transform:"translate3d(0, 100%, 0)",transformOrigin:"0 0",opacity:0}}),oL=new Ht("antMoveLeftIn",{"0%":{transform:"translate3d(-100%, 0, 0)",transformOrigin:"0 0",opacity:0},"100%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1}}),sL=new Ht("antMoveLeftOut",{"0%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1},"100%":{transform:"translate3d(-100%, 0, 0)",transformOrigin:"0 0",opacity:0}}),iL=new Ht("antMoveRightIn",{"0%":{transform:"translate3d(100%, 0, 0)",transformOrigin:"0 0",opacity:0},"100%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1}}),aL=new Ht("antMoveRightOut",{"0%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1},"100%":{transform:"translate3d(100%, 0, 0)",transformOrigin:"0 0",opacity:0}}),lL=new Ht("antMoveUpIn",{"0%":{transform:"translate3d(0, -100%, 0)",transformOrigin:"0 0",opacity:0},"100%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1}}),cL=new Ht("antMoveUpOut",{"0%":{transform:"translate3d(0, 0, 0)",transformOrigin:"0 0",opacity:1},"100%":{transform:"translate3d(0, -100%, 0)",transformOrigin:"0 0",opacity:0}}),uL={"move-up":{inKeyframes:lL,outKeyframes:cL},"move-down":{inKeyframes:nL,outKeyframes:rL},"move-left":{inKeyframes:oL,outKeyframes:sL},"move-right":{inKeyframes:iL,outKeyframes:aL}},uf=(e,t)=>{const{antCls:n}=e,r=`${n}-${t}`,{inKeyframes:o,outKeyframes:s}=uL[t];return[wm(r,o,s,e.motionDurationMid),{[` + ${r}-enter, + ${r}-appear + `]:{opacity:0,animationTimingFunction:e.motionEaseOutCirc},[`${r}-leave`]:{animationTimingFunction:e.motionEaseInOutCirc}}]},Em=new Ht("antSlideUpIn",{"0%":{transform:"scaleY(0.8)",transformOrigin:"0% 0%",opacity:0},"100%":{transform:"scaleY(1)",transformOrigin:"0% 0%",opacity:1}}),Im=new Ht("antSlideUpOut",{"0%":{transform:"scaleY(1)",transformOrigin:"0% 0%",opacity:1},"100%":{transform:"scaleY(0.8)",transformOrigin:"0% 0%",opacity:0}}),Pm=new Ht("antSlideDownIn",{"0%":{transform:"scaleY(0.8)",transformOrigin:"100% 100%",opacity:0},"100%":{transform:"scaleY(1)",transformOrigin:"100% 100%",opacity:1}}),Nm=new Ht("antSlideDownOut",{"0%":{transform:"scaleY(1)",transformOrigin:"100% 100%",opacity:1},"100%":{transform:"scaleY(0.8)",transformOrigin:"100% 100%",opacity:0}}),VN=new Ht("antSlideLeftIn",{"0%":{transform:"scaleX(0.8)",transformOrigin:"0% 0%",opacity:0},"100%":{transform:"scaleX(1)",transformOrigin:"0% 0%",opacity:1}}),WN=new Ht("antSlideLeftOut",{"0%":{transform:"scaleX(1)",transformOrigin:"0% 0%",opacity:1},"100%":{transform:"scaleX(0.8)",transformOrigin:"0% 0%",opacity:0}}),KN=new Ht("antSlideRightIn",{"0%":{transform:"scaleX(0.8)",transformOrigin:"100% 0%",opacity:0},"100%":{transform:"scaleX(1)",transformOrigin:"100% 0%",opacity:1}}),UN=new Ht("antSlideRightOut",{"0%":{transform:"scaleX(1)",transformOrigin:"100% 0%",opacity:1},"100%":{transform:"scaleX(0.8)",transformOrigin:"100% 0%",opacity:0}}),dL={"slide-up":{inKeyframes:Em,outKeyframes:Im},"slide-down":{inKeyframes:Pm,outKeyframes:Nm},"slide-left":{inKeyframes:VN,outKeyframes:WN},"slide-right":{inKeyframes:KN,outKeyframes:UN}},No=(e,t)=>{const{antCls:n}=e,r=`${n}-${t}`,{inKeyframes:o,outKeyframes:s}=dL[t];return[wm(r,o,s,e.motionDurationMid),{[` + ${r}-enter, + ${r}-appear + `]:{transform:"scale(0)",transformOrigin:"0% 0%",opacity:0,animationTimingFunction:e.motionEaseOutQuint,"&-prepare":{transform:"scale(1)"}},[`${r}-leave`]:{animationTimingFunction:e.motionEaseInQuint}}]},Sd=()=>({"@media (prefers-reduced-motion: reduce)":{transition:"none",animation:"none"}}),hx=new Ht("antZoomIn",{"0%":{transform:"scale(0.2)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),fL=new Ht("antZoomOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.2)",opacity:0}}),U1=new Ht("antZoomBigIn",{"0%":{transform:"scale(0.8)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),q1=new Ht("antZoomBigOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.8)",opacity:0}}),mL=new Ht("antZoomUpIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 0%"}}),pL=new Ht("antZoomUpOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 0%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0}}),gL=new Ht("antZoomLeftIn",{"0%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"0% 50%"}}),hL=new Ht("antZoomLeftOut",{"0%":{transform:"scale(1)",transformOrigin:"0% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0}}),yL=new Ht("antZoomRightIn",{"0%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"100% 50%"}}),vL=new Ht("antZoomRightOut",{"0%":{transform:"scale(1)",transformOrigin:"100% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0}}),bL=new Ht("antZoomDownIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 100%"}}),xL=new Ht("antZoomDownOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 100%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0}}),$L={zoom:{inKeyframes:hx,outKeyframes:fL},"zoom-big":{inKeyframes:U1,outKeyframes:q1},"zoom-big-fast":{inKeyframes:U1,outKeyframes:q1},"zoom-left":{inKeyframes:gL,outKeyframes:hL},"zoom-right":{inKeyframes:yL,outKeyframes:vL},"zoom-up":{inKeyframes:mL,outKeyframes:pL},"zoom-down":{inKeyframes:bL,outKeyframes:xL}},Yc=(e,t)=>{const{antCls:n}=e,r=`${n}-${t}`,{inKeyframes:o,outKeyframes:s}=$L[t];return[wm(r,o,s,t==="zoom-big-fast"?e.motionDurationFast:e.motionDurationMid),{[` + ${r}-enter, + ${r}-appear + `]:{transform:"scale(0)",opacity:0,animationTimingFunction:e.motionEaseOutCirc,"&-prepare":{transform:"none"}},[`${r}-leave`]:{animationTimingFunction:e.motionEaseInOutCirc}}]},G1=(e,t)=>({[`> span, > ${e}`]:{"&:not(:last-child)":{[`&, & > ${e}`]:{"&:not(:disabled)":{borderInlineEndColor:t}}},"&:not(:first-child)":{[`&, & > ${e}`]:{"&:not(:disabled)":{borderInlineStartColor:t}}}}}),SL=e=>{const{componentCls:t,fontSize:n,lineWidth:r,groupBorderColor:o,colorErrorHover:s}=e;return{[`${t}-group`]:[{position:"relative",display:"inline-flex",[`> span, > ${t}`]:{"&:not(:last-child)":{[`&, & > ${t}`]:{borderStartEndRadius:0,borderEndEndRadius:0}},"&:not(:first-child)":{marginInlineStart:e.calc(r).mul(-1).equal(),[`&, & > ${t}`]:{borderStartStartRadius:0,borderEndStartRadius:0}}},[t]:{position:"relative",zIndex:1,"&:hover, &:focus, &:active":{zIndex:2},"&[disabled]":{zIndex:0}},[`${t}-icon-only`]:{fontSize:n}},G1(`${t}-primary`,o),G1(`${t}-danger`,s)]}},Qp=e=>Math.round(Number(e||0)),CL=e=>{if(e instanceof Gt)return e;if(e&&typeof e=="object"&&"h"in e&&"b"in e){const{b:t,...n}=e;return{...n,v:t}}return typeof e=="string"&&/hsb/.test(e)?e.replace(/hsb/,"hsv"):e};class Pc extends Gt{constructor(t){super(CL(t))}toHsbString(){const t=this.toHsb(),n=Qp(t.s*100),r=Qp(t.b*100),o=Qp(t.h),s=t.a,i=`hsb(${o}, ${n}%, ${r}%)`,l=`hsba(${o}, ${n}%, ${r}%, ${s.toFixed(s===0?0:2)})`;return s===1?i:l}toHsb(){const{v:t,...n}=this.toHsv();return{...n,b:t,a:this.a}}}const wL=e=>e instanceof Pc?e:new Pc(e);wL("#1677ff");const EL=(e,t)=>(e==null?void 0:e.replace(/[^0-9a-f]/gi,"").slice(0,t?8:6))||"",IL=(e,t)=>e?EL(e,t):"";let df=function(){function e(t){var r;if(zi(this,e),this.cleared=!1,t instanceof e){this.metaColor=t.metaColor.clone(),this.colors=(r=t.colors)==null?void 0:r.map(o=>({color:new e(o.color),percent:o.percent})),this.cleared=t.cleared;return}const n=Array.isArray(t);n&&t.length?(this.colors=t.map(({color:o,percent:s})=>({color:new e(o),percent:s})),this.metaColor=new Pc(this.colors[0].color.metaColor)):this.metaColor=new Pc(n?"":t),(!t||n&&!this.colors)&&(this.metaColor=this.metaColor.setA(0),this.cleared=!0)}return ji(e,[{key:"toHsb",value:function(){return this.metaColor.toHsb()}},{key:"toHsbString",value:function(){return this.metaColor.toHsbString()}},{key:"toHex",value:function(){return IL(this.toHexString(),this.metaColor.a<1)}},{key:"toHexString",value:function(){return this.metaColor.toHexString()}},{key:"toRgb",value:function(){return this.metaColor.toRgb()}},{key:"toRgbString",value:function(){return this.metaColor.toRgbString()}},{key:"isGradient",value:function(){return!!this.colors&&!this.cleared}},{key:"getColors",value:function(){return this.colors||[{color:this,percent:0}]}},{key:"toCssString",value:function(){const{colors:n}=this;return n?`linear-gradient(90deg, ${n.map(o=>`${o.color.toRgbString()} ${o.percent}%`).join(", ")})`:this.metaColor.toRgbString()}},{key:"equals",value:function(n){return!n||this.isGradient()!==n.isGradient()?!1:this.isGradient()?this.colors.length===n.colors.length&&this.colors.every((r,o)=>{const s=n.colors[o];return r.percent===s.percent&&r.color.equals(s.color)}):this.toHexString()===n.toHexString()}}])}();var qN={};Object.defineProperty(qN,"__esModule",{value:!0});var PL={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"},NL=qN.default=PL;function Ry(){return Ry=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Ry({},e,{ref:t,icon:NL})),Nc=a.forwardRef(RL);function qr(){return qr=Object.assign?Object.assign.bind():function(e){for(var t=1;te instanceof df?e:new df(e),GN=(e,t)=>{const{r:n,g:r,b:o,a:s}=e.toRgb(),i=new Pc(e.toRgbString()).onBackground(t).toHsv();return s<=.5?i.v>.5:n*.299+r*.587+o*.114>192},XN=e=>{const{paddingInline:t,onlyIconSize:n,borderColorDisabled:r}=e;return Rt(e,{buttonPaddingHorizontal:t,buttonPaddingVertical:0,buttonIconOnlyFontSize:n,colorBorderDisabled:r})},YN=e=>{const t=e.contentFontSize??e.fontSize,n=e.contentFontSizeSM??e.fontSize,r=e.contentFontSizeLG??e.fontSizeLG,o=e.contentLineHeight??bd(t),s=e.contentLineHeightSM??bd(n),i=e.contentLineHeightLG??bd(r),l=GN(new df(e.colorBgSolid),"#fff")?"#000":"#fff",c=Oo.reduce((m,f)=>({...m,[`${f}ShadowColor`]:`0 ${G(e.controlOutlineWidth)} 0 ${Tl(e[`${f}1`],e.colorBgContainer)}`}),{}),u=e.colorBgContainerDisabled,d=e.colorBgContainerDisabled;return{...c,fontWeight:400,iconGap:e.marginXS,defaultShadow:`0 ${e.controlOutlineWidth}px 0 ${e.controlTmpOutline}`,primaryShadow:`0 ${e.controlOutlineWidth}px 0 ${e.controlOutline}`,dangerShadow:`0 ${e.controlOutlineWidth}px 0 ${e.colorErrorOutline}`,primaryColor:e.colorTextLightSolid,dangerColor:e.colorTextLightSolid,borderColorDisabled:e.colorBorderDisabled,defaultGhostColor:e.colorBgContainer,ghostBg:"transparent",defaultGhostBorderColor:e.colorBgContainer,paddingInline:e.paddingContentHorizontal-e.lineWidth,paddingInlineLG:e.paddingContentHorizontal-e.lineWidth,paddingInlineSM:8-e.lineWidth,onlyIconSize:"inherit",onlyIconSizeSM:"inherit",onlyIconSizeLG:"inherit",groupBorderColor:e.colorPrimaryHover,linkHoverBg:"transparent",textTextColor:e.colorText,textTextHoverColor:e.colorText,textTextActiveColor:e.colorText,textHoverBg:e.colorFillTertiary,defaultColor:e.colorText,defaultBg:e.colorBgContainer,defaultBorderColor:e.colorBorder,defaultBorderColorDisabled:e.colorBorder,defaultHoverBg:e.colorBgContainer,defaultHoverColor:e.colorPrimaryHover,defaultHoverBorderColor:e.colorPrimaryHover,defaultActiveBg:e.colorBgContainer,defaultActiveColor:e.colorPrimaryActive,defaultActiveBorderColor:e.colorPrimaryActive,solidTextColor:l,contentFontSize:t,contentFontSizeSM:n,contentFontSizeLG:r,contentLineHeight:o,contentLineHeightSM:s,contentLineHeightLG:i,paddingBlock:Math.max((e.controlHeight-t*o)/2-e.lineWidth,0),paddingBlockSM:Math.max((e.controlHeightSM-n*s)/2-e.lineWidth,0),paddingBlockLG:Math.max((e.controlHeightLG-r*i)/2-e.lineWidth,0),defaultBgDisabled:u,dashedBgDisabled:d}},ML=e=>{const{componentCls:t,antCls:n,lineWidth:r}=e,[o,s]=rn(n,"btn");return{[t]:[{[o("border-width")]:r,[o("border-color")]:"#000",[o("border-color-hover")]:s("border-color"),[o("border-color-active")]:s("border-color"),[o("border-color-disabled")]:s("border-color"),[o("border-style")]:"solid",[o("text-color")]:"#000",[o("text-color-hover")]:s("text-color"),[o("text-color-active")]:s("text-color"),[o("text-color-disabled")]:s("text-color"),[o("bg-color")]:"#ddd",[o("bg-color-hover")]:s("bg-color"),[o("bg-color-active")]:s("bg-color"),[o("bg-color-disabled")]:e.colorBgContainerDisabled,[o("bg-color-container")]:e.colorBgContainer,[o("shadow")]:"none"},{border:[s("border-width"),s("border-style"),s("border-color")].join(" "),color:s("text-color"),backgroundColor:s("bg-color"),[`&:not(:disabled):not(${t}-disabled)`]:{"&:hover":{border:[s("border-width"),s("border-style"),s("border-color-hover")].join(" "),color:s("text-color-hover"),backgroundColor:s("bg-color-hover")},"&:active":{border:[s("border-width"),s("border-style"),s("border-color-active")].join(" "),color:s("text-color-active"),backgroundColor:s("bg-color-active")}}},{[`&${t}-variant-solid`]:{[o("solid-bg-color")]:s("color-base"),[o("solid-bg-color-hover")]:s("color-hover"),[o("solid-bg-color-active")]:s("color-active"),[o("border-color")]:"transparent",[o("text-color")]:e.colorTextLightSolid,[o("bg-color")]:s("solid-bg-color"),[o("bg-color-hover")]:s("solid-bg-color-hover"),[o("bg-color-active")]:s("solid-bg-color-active"),boxShadow:s("shadow")},[`&${t}-variant-outlined, &${t}-variant-dashed`]:{[o("border-color")]:s("color-base"),[o("border-color-hover")]:s("color-hover"),[o("border-color-active")]:s("color-active"),[o("bg-color")]:s("bg-color-container"),[o("text-color")]:s("color-base"),[o("text-color-hover")]:s("color-hover"),[o("text-color-active")]:s("color-active"),boxShadow:s("shadow")},[`&${t}-variant-dashed`]:{[o("border-style")]:"dashed",[o("bg-color-disabled")]:e.dashedBgDisabled},[`&${t}-variant-filled`]:{[o("border-color")]:"transparent",[o("text-color")]:s("color-base"),[o("bg-color")]:s("color-light"),[o("bg-color-hover")]:s("color-light-hover"),[o("bg-color-active")]:s("color-light-active")},[`&${t}-variant-text, &${t}-variant-link`]:{[o("border-color")]:"transparent",[o("text-color")]:s("color-base"),[o("text-color-hover")]:s("color-hover"),[o("text-color-active")]:s("color-active"),[o("bg-color")]:"transparent",[o("bg-color-hover")]:"transparent",[o("bg-color-active")]:"transparent",[`&:disabled, &${e.componentCls}-disabled`]:{background:"transparent",borderColor:"transparent"}},[`&${t}-variant-text`]:{[o("bg-color-hover")]:s("color-light"),[o("bg-color-active")]:s("color-light-active")}},{[`&${t}-variant-link`]:{[o("color-base")]:e.colorLink,[o("color-hover")]:e.colorLinkHover,[o("color-active")]:e.colorLinkActive,[o("bg-color-hover")]:e.linkHoverBg},[`&${t}-color-primary`]:{[o("color-base")]:e.colorPrimary,[o("color-hover")]:e.colorPrimaryHover,[o("color-active")]:e.colorPrimaryActive,[o("color-light")]:e.colorPrimaryBg,[o("color-light-hover")]:e.colorPrimaryBgHover,[o("color-light-active")]:e.colorPrimaryBorder,[o("shadow")]:e.primaryShadow,[`&${t}-variant-solid`]:{[o("text-color")]:e.primaryColor,[o("text-color-hover")]:s("text-color"),[o("text-color-active")]:s("text-color")}},[`&${t}-color-dangerous`]:{[o("color-base")]:e.colorError,[o("color-hover")]:e.colorErrorHover,[o("color-active")]:e.colorErrorActive,[o("color-light")]:e.colorErrorBg,[o("color-light-hover")]:e.colorErrorBgFilledHover,[o("color-light-active")]:e.colorErrorBgActive,[o("shadow")]:e.dangerShadow,[`&${t}-variant-solid`]:{[o("text-color")]:e.dangerColor,[o("text-color-hover")]:s("text-color"),[o("text-color-active")]:s("text-color")}},[`&${t}-color-default`]:{[o("solid-bg-color")]:e.colorBgSolid,[o("solid-bg-color-hover")]:e.colorBgSolidHover,[o("solid-bg-color-active")]:e.colorBgSolidActive,[o("color-base")]:e.defaultBorderColor,[o("color-hover")]:e.defaultHoverBorderColor,[o("color-active")]:e.defaultActiveBorderColor,[o("color-light")]:e.colorFillTertiary,[o("color-light-hover")]:e.colorFillSecondary,[o("color-light-active")]:e.colorFill,[o("text-color")]:e.defaultColor,[o("text-color-hover")]:e.defaultHoverColor,[o("text-color-active")]:e.defaultActiveColor,[o("shadow")]:e.defaultShadow,[`&${t}-variant-outlined`]:{[o("bg-color-disabled")]:e.defaultBgDisabled},[`&${t}-variant-solid`]:{[o("text-color")]:e.solidTextColor,[o("text-color-hover")]:s("text-color"),[o("text-color-active")]:s("text-color")},[`&${t}-variant-filled, &${t}-variant-text`]:{[o("text-color-hover")]:s("text-color"),[o("text-color-active")]:s("text-color")},[`&${t}-variant-outlined, &${t}-variant-dashed`]:{[o("text-color")]:e.defaultColor,[o("text-color-hover")]:e.defaultHoverColor,[o("text-color-active")]:e.defaultActiveColor,[o("bg-color-container")]:e.defaultBg,[o("bg-color-hover")]:e.defaultHoverBg,[o("bg-color-active")]:e.defaultActiveBg},[`&${t}-variant-text`]:{[o("text-color")]:e.textTextColor,[o("text-color-hover")]:e.textTextHoverColor,[o("text-color-active")]:e.textTextActiveColor,[o("bg-color-hover")]:e.textHoverBg},[`&${t}-background-ghost`]:{[`&${t}-variant-outlined, &${t}-variant-dashed`]:{[o("text-color")]:e.defaultGhostColor,[o("border-color")]:e.defaultGhostBorderColor}}}},Oo.map(i=>{const l=e[`${i}6`],c=e[`${i}1`],u=e[`${i}Hover`],d=e[`${i}2`],m=e[`${i}3`],f=e[`${i}Active`],p=e[`${i}ShadowColor`];return{[`&${t}-color-${i}`]:{[o("color-base")]:l,[o("color-hover")]:u,[o("color-active")]:f,[o("color-light")]:c,[o("color-light-hover")]:d,[o("color-light-active")]:m,[o("shadow")]:p}}}),{[`&:disabled, &${e.componentCls}-disabled`]:{cursor:"not-allowed",borderColor:e.colorBorderDisabled,background:s("bg-color-disabled"),color:e.colorTextDisabled,boxShadow:"none"}},{[`&${t}-background-ghost`]:{[o("bg-color")]:e.ghostBg,[o("bg-color-hover")]:e.ghostBg,[o("bg-color-active")]:e.ghostBg,[o("shadow")]:"none",[`&${t}-variant-outlined, &${t}-variant-dashed`]:{[o("bg-color-hover")]:e.ghostBg,[o("bg-color-active")]:e.ghostBg}}}]}},OL=e=>{const{componentCls:t,iconCls:n,fontWeight:r,opacityLoading:o,motionDurationSlow:s,motionEaseInOut:i,iconGap:l,calc:c}=e;return{[t]:{outline:"none",position:"relative",display:"inline-flex",gap:l,alignItems:"center",justifyContent:"center",fontWeight:r,whiteSpace:"nowrap",textAlign:"center",backgroundImage:"none",cursor:"pointer",transition:`all ${e.motionDurationMid} ${e.motionEaseInOut}`,userSelect:"none",touchAction:"manipulation",...Sd(),"&:disabled > *":{pointerEvents:"none"},[`${t}-icon > svg`]:Kc(),[`${t}-icon`]:{display:"inline-flex",alignItems:"center",justifyContent:"center",[n]:{verticalAlign:"middle","&:before":{content:'"\\a0"',display:"inline-block",width:0}}},"> a":{color:"currentColor"},"&:not(:disabled)":Br(e),[`&${t}-two-chinese-chars::first-letter`]:{letterSpacing:"0.34em"},[`&${t}-two-chinese-chars > *:not(${n})`]:{marginInlineEnd:"-0.34em",letterSpacing:"0.34em"},[`&${t}-icon-only`]:{paddingInline:0,[`&${t}-compact-item`]:{flex:"none"}},[`&${t}-loading`]:{opacity:o,cursor:"default"},[`${t}-loading-icon`]:{transition:["width","opacity","margin"].map(u=>`${u} ${s} ${i}`).join(",")},[`&:not(${t}-icon-end)`]:{[`${t}-loading-icon-motion`]:{"&-appear-start, &-enter-start":{marginInlineEnd:c(l).mul(-1).equal()},"&-appear-active, &-enter-active":{marginInlineEnd:0},"&-leave-start":{marginInlineEnd:0},"&-leave-active":{marginInlineEnd:c(l).mul(-1).equal()}}},"&-icon-end":{flexDirection:"row-reverse",[`${t}-loading-icon-motion`]:{"&-appear-start, &-enter-start":{marginInlineStart:c(l).mul(-1).equal()},"&-appear-active, &-enter-active":{marginInlineStart:0},"&-leave-start":{marginInlineStart:0},"&-leave-active":{marginInlineStart:c(l).mul(-1).equal()}}}}}},_L=e=>({minWidth:e.controlHeight,paddingInline:0,borderRadius:"50%"}),yx=(e,t="")=>{const{componentCls:n,controlHeight:r,fontSize:o,borderRadius:s,buttonPaddingHorizontal:i,iconCls:l,buttonPaddingVertical:c,buttonIconOnlyFontSize:u}=e;return[{[t]:{fontSize:o,height:r,padding:`${G(c)} ${G(i)}`,borderRadius:s,[`&${n}-icon-only`]:{width:r,[l]:{fontSize:u}}}},{[`${n}${n}-circle${t}`]:_L(e)},{[`${n}${n}-round${t}`]:{borderRadius:e.controlHeight,[`&:not(${n}-icon-only)`]:{paddingInline:e.buttonPaddingHorizontal}}}]},zL=e=>{const t=Rt(e,{fontSize:e.contentFontSize});return yx(t,e.componentCls)},jL=e=>{const t=Rt(e,{controlHeight:e.controlHeightSM,fontSize:e.contentFontSizeSM,padding:e.paddingXS,buttonPaddingHorizontal:e.paddingInlineSM,buttonPaddingVertical:0,borderRadius:e.borderRadiusSM,buttonIconOnlyFontSize:e.onlyIconSizeSM});return yx(t,`${e.componentCls}-sm`)},BL=e=>{const t=Rt(e,{controlHeight:e.controlHeightLG,fontSize:e.contentFontSizeLG,buttonPaddingHorizontal:e.paddingInlineLG,buttonPaddingVertical:0,borderRadius:e.borderRadiusLG,buttonIconOnlyFontSize:e.onlyIconSizeLG});return yx(t,`${e.componentCls}-lg`)},LL=e=>{const{componentCls:t}=e;return{[t]:{[`&${t}-block`]:{width:"100%"}}}},kL=Tt("Button",e=>{const t=XN(e);return[OL(t),zL(t),jL(t),BL(t),LL(t),ML(t),SL(t)]},YN,{unitless:{fontWeight:!0,contentLineHeight:!0,contentLineHeightSM:!0,contentLineHeightLG:!0}});function AL(e,t,n,r){const{focusElCls:o,focus:s,borderElCls:i}=n,l=i?"> *":"",c=l?` ${l}`:"",u=f=>f.filter(Boolean).map(p=>`&:${p}${c}`).join(","),d=u(["hover",o?`hover${o}`:null]),m=u([s?"focus":null,"active"]);return{[`&-item:not(${t}-last-item)`]:{marginInlineEnd:e.calc(e.lineWidth).mul(-1).equal()},[`&-item:not(${r}-status-success)`]:{zIndex:2},"&-item":{[m]:{zIndex:3},[d]:{zIndex:4},...o?{[`&${o}`]:{zIndex:3}}:{},[`&[disabled] ${l}`]:{zIndex:0}}}}function DL(e,t,n){const{borderElCls:r}=n,o=r?`> ${r}`:"";return{[`&-item:not(${t}-first-item):not(${t}-last-item) ${o}`]:{borderRadius:0},[`&-item:not(${t}-last-item)${t}-first-item`]:{[`& ${o}, &${e}-sm ${o}, &${e}-lg ${o}`]:{borderStartEndRadius:0,borderEndEndRadius:0}},[`&-item:not(${t}-first-item)${t}-last-item`]:{[`& ${o}, &${e}-sm ${o}, &${e}-lg ${o}`]:{borderStartStartRadius:0,borderEndStartRadius:0}}}}function Qc(e,t={focus:!0}){const{componentCls:n}=e,{componentCls:r}=t,o=r||n,s=`${o}-compact`;return{[s]:{...AL(e,s,t,o),...DL(o,s,t)}}}function FL(e,t,n){return{[`&-item:not(${t}-last-item)`]:{marginBottom:e.calc(e.lineWidth).mul(-1).equal()},[`&-item:not(${n}-status-success)`]:{zIndex:2},"&-item":{"&:focus,&:active":{zIndex:3},"&:hover":{zIndex:4},"&[disabled]":{zIndex:0}}}}function HL(e,t){return{[`&-item:not(${t}-first-item):not(${t}-last-item)`]:{borderRadius:0},[`&-item${t}-first-item:not(${t}-last-item)`]:{[`&, &${e}-sm, &${e}-lg`]:{borderEndEndRadius:0,borderEndStartRadius:0}},[`&-item${t}-last-item:not(${t}-first-item)`]:{[`&, &${e}-sm, &${e}-lg`]:{borderStartStartRadius:0,borderStartEndRadius:0}}}}function VL(e){const t=`${e.componentCls}-compact-vertical`;return{[t]:{...FL(e,t,e.componentCls),...HL(e.componentCls,t)}}}const WL=e=>{const{antCls:t,componentCls:n,lineWidth:r,calc:o,colorBgContainer:s}=e,i=`${n}-variant-solid:not([disabled])`,l=o(r).mul(-1).equal(),[c,u]=rn(t,"btn"),d=m=>({[`${n}-compact${m?"-vertical":""}-item`]:{[c("compact-connect-border-color")]:u("bg-color-hover"),[`&${i}`]:{transition:"none",[`& + ${i}:before`]:[{position:"absolute",backgroundColor:u("compact-connect-border-color"),content:'""'},m?{top:l,insetInline:l,height:r}:{insetBlock:l,insetInlineStart:l,width:r}],"&:hover:before":{display:"none"}}}});return[d(),d(!0),{[`${i}${n}-color-default`]:{[c("compact-connect-border-color")]:`color-mix(in srgb, ${u("bg-color-hover")} 75%, ${s})`}}]},KL=Ks(["Button","compact"],e=>{const t=XN(e);return[Qc(t),VL(t),WL(t)]},YN);function UL(e){if(dt(e)){let t=e==null?void 0:e.delay;return t=Rn(t)?t:0,{loading:t<=0,delay:t}}return{loading:!!e,delay:0}}const qL={default:["default","outlined"],primary:["primary","solid"],dashed:["default","dashed"],link:["link","link"],text:["default","text"]},GL=J.forwardRef((e,t)=>{const{_skipSemantic:n,loading:r=!1,prefixCls:o,color:s,variant:i,type:l,danger:c=!1,shape:u,size:d,disabled:m,className:f,rootClassName:p,children:y,icon:b,iconPosition:x,iconPlacement:v,ghost:g=!1,block:h=!1,htmlType:$="button",classNames:C,styles:N,style:S,autoInsertSpace:E,autoFocus:w,...R}=e,P=zn(y),T=l||"default",{getPrefixCls:M,direction:z,autoInsertSpace:B,className:F,style:L,classNames:j,styles:O,loadingIcon:A,shape:k,color:_,variant:D}=Pt("button"),V=u||k||"default",[W,K]=a.useMemo(()=>{if(s&&i)return[s,i];if(l||c){const ce=qL[T]||[];return c?["danger",ce[1]]:ce}return i==="solid"?["primary",i]:_&&D?[_,D]:D==="solid"?["primary",D]:["default","outlined"]},[s,i,l,c,_,D,T]),[q,Y]=a.useMemo(()=>g&&K==="solid"?[W,"outlined"]:[W,K],[W,K,g]),ee=q==="danger",ie=ee?"dangerous":q,ae=E??B??!0,U=M("btn",o),[Q,Z]=kL(U),ne=a.useContext(cr),oe=m??ne,le=a.useContext(DN),re=a.useMemo(()=>UL(r),[r]),[X,se]=a.useState(re.loading),[ge,de]=a.useState(!1),Se=a.useRef(null),ue=$o(t,Se),be=P.length===1&&!b&&!Gp(Y),Ne=a.useRef(!0);J.useEffect(()=>(Ne.current=!1,()=>{Ne.current=!0}),[]),It(()=>{let ce=null;re.delay>0?ce=setTimeout(()=>{ce=null,se(!0)},re.delay):se(re.loading);function Pe(){ce&&(clearTimeout(ce),ce=null)}return Pe},[re.delay,re.loading]),a.useEffect(()=>{if(!Se.current||!ae)return;const ce=Se.current.textContent||"";be&&Ny(ce)?ge||de(!0):ge&&de(!1)}),a.useEffect(()=>{var ce;w&&((ce=Se.current)==null||ce.focus())},[]);const we=J.useCallback(ce=>{var Pe;if(X||oe){ce.preventDefault();return}(Pe=e.onClick)==null||Pe.call(e,("href"in e,ce))},[e.onClick,X,oe]),{compactSize:ze,compactItemClassnames:he}=qs(U,z),ke=Cn(ce=>d??ze??le??ce),Oe=X?"loading":b,Ce=v??x??"start",Me=Dt(R,["navigate"]),xe={...e,type:T,color:q,variant:Y,danger:ee,shape:V,size:ke,disabled:oe,loading:X,iconPlacement:Ce},Ee=Mt(L),Ve=Mt(S),[qe,me]=Ot([n?void 0:j,C],[n?void 0:O,Ee,N,Ve],{props:xe}),Re=H(U,Q,Z,{[`${U}-${V}`]:V!=="default"&&V!=="square"&&V,[`${U}-${T}`]:T,[`${U}-dangerous`]:c,[`${U}-color-${ie}`]:ie,[`${U}-variant-${Y}`]:Y,[`${U}-lg`]:ke==="large",[`${U}-sm`]:ke==="small",[`${U}-icon-only`]:!y&&y!==0&&!!Oe,[`${U}-background-ghost`]:g&&!Gp(Y),[`${U}-loading`]:X,[`${U}-two-chinese-chars`]:ge&&ae&&!X,[`${U}-block`]:h,[`${U}-rtl`]:z==="rtl",[`${U}-icon-end`]:Ce==="end"},he,f,p,F,qe.root),Te={className:qe.icon,style:me.icon},Ue=ce=>J.createElement(FN,{prefixCls:U,...Te},ce),Ge=J.createElement(ZB,{existIcon:!!b,prefixCls:U,loading:X,mount:Ne.current,...Te}),Fe=dt(r)&&r.icon||A;let et;b&&!X?et=Ue(b):r&&Fe?et=Ue(Fe):et=Ge;const ve=$n(y)?JB(y,be&&ae,me.content,qe.content):null;if(Me.href!==void 0)return J.createElement("a",{...Me,className:H(Re,{[`${U}-disabled`]:oe}),href:oe?void 0:Me.href,style:me.root,onClick:we,ref:ue,tabIndex:oe?-1:0,"aria-disabled":oe},et,ve);let je=J.createElement("button",{...R,type:$,className:Re,style:me.root,onClick:we,disabled:oe,ref:ue},et,ve,he&&J.createElement(KL,{prefixCls:U}));return Gp(Y)||(je=J.createElement(Sm,{component:"Button",disabled:X},je)),je}),Xe=GL;Xe.Group=YB;Xe.__ANT_BUTTON=!0;const vx=e=>{const{type:t,children:n,prefixCls:r,buttonProps:o,close:s,autoFocus:i,emitEvent:l,isSilent:c,quitOnNullishReturnValue:u,actionFn:d}=e,m=a.useRef(!1),f=a.useRef(null),[p,y]=_b(!1),b=(...g)=>{s==null||s(...g)};a.useEffect(()=>{let g=null;return i&&(g=setTimeout(()=>{var h;(h=f.current)==null||h.focus({preventScroll:!0})})),()=>{g&&clearTimeout(g)}},[i]);const x=g=>{Vp(g)&&(y(!0),g.then((...h)=>{y(!1,!0),b.apply(void 0,h),m.current=!1},h=>{if(y(!1,!0),m.current=!1,!(c!=null&&c()))return Promise.reject(h)}))},v=g=>{if(m.current)return;if(m.current=!0,!d){b();return}let h;if(l){if(h=d(g),u&&!Vp(h)){m.current=!1,b(g);return}}else if(d.length)h=d(s),m.current=!1;else if(h=d(),!Vp(h)){b();return}x(h)};return a.createElement(Xe,{...px(t),onClick:v,loading:p,prefixCls:r,...o,ref:f},n)},Jc=J.createContext({}),{Provider:QN}=Jc,X1=()=>{const{autoFocusButton:e,cancelButtonProps:t,cancelTextLocale:n,isSilent:r,mergedOkCancel:o,rootPrefixCls:s,close:i,onCancel:l,onConfirm:c,onClose:u}=a.useContext(Jc);return o?J.createElement(vx,{isSilent:r,actionFn:l,close:(...d)=>{i==null||i(...d),c==null||c(!1),u==null||u()},autoFocus:e==="cancel",buttonProps:t,prefixCls:`${s}-btn`},n):null},Y1=()=>{const{autoFocusButton:e,close:t,isSilent:n,okButtonProps:r,rootPrefixCls:o,okTextLocale:s,okType:i,onConfirm:l,onOk:c,onClose:u}=a.useContext(Jc);return J.createElement(vx,{isSilent:n,type:i||"primary",actionFn:c,close:(...d)=>{t==null||t(...d),l==null||l(!0),u==null||u()},autoFocus:e==="ok",buttonProps:r,prefixCls:`${o}-btn`},s)},JN=a.createContext({});function Q1(e,t,n){let r=t;return!r&&n&&(r=`${e}-${n}`),r}function J1(e,t){let n=e[`page${t?"Y":"X"}Offset`];const r=`scroll${t?"Top":"Left"}`;if(typeof n!="number"){const o=e.document;n=o.documentElement[r],typeof n!="number"&&(n=o.body[r])}return n}function XL(e){const t=e.getBoundingClientRect(),n={left:t.left,top:t.top},r=e.ownerDocument,o=r.defaultView||r.parentWindow;return n.left+=J1(o),n.top+=J1(o,!0),n}const YL=a.memo(({children:e})=>e,(e,{shouldUpdate:t})=>!t);function ff(){return ff=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,className:r,style:o,title:s,ariaId:i,footer:l,closable:c,closeIcon:u,onClose:d,children:m,bodyStyle:f,bodyProps:p,modalRender:y,onMouseDown:b,onMouseUp:x,holderRef:v,visible:g,forceRender:h,width:$,height:C,classNames:N,styles:S,isFixedPos:E,focusTrap:w}=e,{panel:R}=J.useContext(JN),P=a.useRef(null),T=$o(v,R,P),[M]=PO(g&&E&&w!==!1,()=>P.current);J.useImperativeHandle(t,()=>({focus:()=>{var _;(_=P.current)==null||_.focus({preventScroll:!0})}}));const z={};$!==void 0&&(z.width=$),C!==void 0&&(z.height=C);const B=l?J.createElement("div",{className:H(`${n}-footer`,N==null?void 0:N.footer),style:{...S==null?void 0:S.footer}},l):null,F=s?J.createElement("div",{className:H(`${n}-header`,N==null?void 0:N.header),style:{...S==null?void 0:S.header}},J.createElement("div",{className:H(`${n}-title`,N==null?void 0:N.title),id:i,style:{...S==null?void 0:S.title}},s)):null,L=a.useMemo(()=>typeof c=="object"&&c!==null?c:c?{closeIcon:u??J.createElement("span",{className:`${n}-close-x`})}:{},[c,u,n]),j=Nn(L,!0),O=typeof c=="object"&&c.disabled,A=c?J.createElement("button",ff({type:"button",onClick:d,"aria-label":"Close"},j,{className:H(`${n}-close`,N==null?void 0:N.close),disabled:O,style:S==null?void 0:S.close}),L.closeIcon):null,k=J.createElement("div",{className:H(`${n}-container`,N==null?void 0:N.container),style:S==null?void 0:S.container},A,F,J.createElement("div",ff({className:H(`${n}-body`,N==null?void 0:N.body),style:{...f,...S==null?void 0:S.body}},p),m),B);return J.createElement("div",{key:"dialog-element",role:"dialog","aria-labelledby":s?i:null,"aria-modal":"true",ref:T,style:{...o,...z},className:H(n,r),onMouseDown:b,onMouseUp:x,tabIndex:-1,onFocus:_=>{M(_.target)}},J.createElement(YL,{shouldUpdate:g||h},y?y(k):k))});function Ty(){return Ty=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,title:r,style:o,className:s,visible:i,forceRender:l,destroyOnHidden:c,motionName:u,ariaId:d,onVisibleChanged:m,mousePosition:f}=e,p=a.useRef(null),y=a.useRef(null);a.useImperativeHandle(t,()=>({...y.current,inMotion:p.current.inMotion,enableMotion:p.current.enableMotion}));const[b,x]=a.useState(),v={};b&&(v.transformOrigin=b);function g(){var $;if(!(($=p.current)!=null&&$.nativeElement))return;const h=XL(p.current.nativeElement);x(f&&(f.x||f.y)?`${f.x-h.left}px ${f.y-h.top}px`:"")}return a.createElement(fr,{visible:i,onVisibleChanged:m,onAppearPrepare:g,onEnterPrepare:g,forceRender:l,motionName:u,removeOnLeave:c,ref:p},({className:h,style:$},C)=>a.createElement(ZN,Ty({},e,{ref:y,title:r,ariaId:d,prefixCls:n,holderRef:C,style:{...$,...o,...v},className:H(s,h)})))});function My(){return My=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:t,style:n,visible:r,maskProps:o,motionName:s,className:i}=e;return a.createElement(fr,{key:"mask",visible:r,motionName:s,leavedClassName:`${t}-mask-hidden`},({className:l,style:c},u)=>a.createElement("div",My({ref:u,style:{...c,...n},className:H(`${t}-mask`,l,i)},o)))};function ql(){return ql=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:t="rc-dialog",zIndex:n,visible:r=!1,focusTriggerAfterClose:o=!0,wrapStyle:s,wrapClassName:i,wrapProps:l,onClose:c,afterOpenChange:u,afterClose:d,transitionName:m,animation:f,closable:p=!0,mask:y=!0,maskTransitionName:b,maskAnimation:x,maskClosable:v=!0,maskStyle:g,maskProps:h,rootClassName:$,rootStyle:C,classNames:N,styles:S}=e,E=a.useRef(null),w=a.useRef(null),R=a.useRef(null),[P,T]=a.useState(r),[M,z]=a.useState(!1),B=jo();function F(){Th(w.current,document.activeElement)||(E.current=document.activeElement)}function L(){var W;Th(w.current,document.activeElement)||(W=R.current)==null||W.focus()}function j(){if(T(!1),y&&E.current&&o){try{E.current.focus({preventScroll:!0})}catch{}E.current=null}P&&(d==null||d())}function O(W){W?L():j(),u==null||u(W)}function A(W){c==null||c(W)}const k=a.useRef(!1);let _=null;v&&(_=W=>{w.current===W.target&&k.current&&A(W)});function D(W){k.current=W.target===w.current}a.useEffect(()=>{if(r){if(k.current=!1,T(!0),F(),w.current){const W=getComputedStyle(w.current);z(W.position==="fixed")}}else P&&R.current.enableMotion()&&!R.current.inMotion()&&j()},[r]);const V={zIndex:n,...s,...S==null?void 0:S.wrapper,display:P?null:"none"};return a.createElement("div",ql({className:H(`${t}-root`,$),style:C},Nn(e,{data:!0})),a.createElement(JL,{prefixCls:t,visible:y&&r,motionName:Q1(t,b,x),style:{zIndex:n,...g,...S==null?void 0:S.mask},maskProps:h,className:N==null?void 0:N.mask}),a.createElement("div",ql({className:H(`${t}-wrap`,i,N==null?void 0:N.wrapper),ref:w,onClick:_,onMouseDown:D,style:V},l),a.createElement(QL,ql({},e,{isFixedPos:M,ref:R,closable:p,ariaId:B,prefixCls:t,visible:r&&P,onClose:A,onVisibleChanged:O,motionName:Q1(t,m,f)}))))};function Oy(){return Oy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{visible:t,getContainer:n,forceRender:r,destroyOnHidden:o=!1,afterClose:s,closable:i,panelRef:l,keyboard:c=!0,scrollLock:u=!0,onClose:d}=e,{scrollLock:m,...f}=e,[p,y]=a.useState(t),b=a.useMemo(()=>({panel:l}),[l]),x=({top:v,event:g})=>{if(v&&c){g.stopPropagation(),d==null||d(g);return}};return a.useEffect(()=>{t&&y(!0)},[t]),!r&&o&&!p?null:a.createElement(JN.Provider,{value:b},a.createElement(vm,{open:t||r||p,onEsc:x,autoDestroy:!1,getContainer:n,autoLock:u&&(t||p)},a.createElement(ZL,Oy({},f,{destroyOnHidden:o,afterClose:()=>{const v=i&&typeof i=="object"?i:{},{afterClose:g}=v||{};g==null||g(),s==null||s(),y(!1)}}))))},fi="RC_FORM_INTERNAL_HOOKS",Jt=()=>{fn(!1,"Can not find FormContext. Please make sure you wrap Field under Form.")},Ni=a.createContext({getFieldValue:Jt,getFieldsValue:Jt,getFieldError:Jt,getFieldWarning:Jt,getFieldsError:Jt,isFieldsTouched:Jt,isFieldTouched:Jt,isFieldValidating:Jt,isFieldsValidating:Jt,resetFields:Jt,setFields:Jt,setFieldValue:Jt,setFieldsValue:Jt,validateFields:Jt,submit:Jt,getInternalHooks:()=>(Jt(),{dispatch:Jt,initEntityValue:Jt,registerField:Jt,useSubscribe:Jt,setInitialValues:Jt,destroyForm:Jt,setCallbacks:Jt,registerWatch:Jt,getFields:Jt,setValidateMessages:Jt,setPreserve:Jt,getInitialValue:Jt})}),Rc=a.createContext(null);function _y(e){return e==null?[]:Array.isArray(e)?e:[e]}function tk(e){return e&&!!e._init}function zy(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",tel:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone(){const e=JSON.parse(JSON.stringify(this));return e.clone=this.clone,e}}}const Jp=zy(),nk=/%[sdj%]/g;let rk=()=>{};function jy(e){if(!e||!e.length)return null;const t={};return e.forEach(n=>{const r=n.field;t[r]=t[r]||[],t[r].push(n)}),t}function Or(e,...t){let n=0;const r=t.length;return typeof e=="function"?e.apply(null,t):typeof e=="string"?e.replace(nk,s=>{if(s==="%%")return"%";if(n>=r)return s;switch(s){case"%s":return String(t[n++]);case"%d":return Number(t[n++]);case"%j":try{return JSON.stringify(t[n++])}catch{return"[Circular]"}break;default:return s}}):e}function ok(e){return e==="string"||e==="url"||e==="hex"||e==="email"||e==="date"||e==="pattern"||e==="tel"}function jn(e,t){return!!(e==null||t==="array"&&Array.isArray(e)&&!e.length||ok(t)&&typeof e=="string"&&!e)}function sk(e,t,n){const r=[];let o=0;const s=e.length;function i(l){r.push(...l||[]),o++,o===s&&n(r)}e.forEach(l=>{t(l,i)})}function Z1(e,t,n){let r=0;const o=e.length;function s(i){if(i&&i.length){n(i);return}const l=r;r=r+1,l{t.push(...e[n]||[])}),t}class eC extends Error{constructor(n,r){super("Async Validation Error");fe(this,"errors");fe(this,"fields");this.errors=n,this.fields=r}}function ak(e,t,n,r,o){if(t.first){const m=new Promise((f,p)=>{const y=x=>(r(x),x.length?p(new eC(x,jy(x))):f(o)),b=ik(e);Z1(b,n,y)});return m.catch(f=>f),m}const s=t.firstFields===!0?Object.keys(e):t.firstFields||[],i=Object.keys(e),l=i.length;let c=0;const u=[],d=new Promise((m,f)=>{const p=y=>{if(u.push.apply(u,y),c++,c===l)return r(u),u.length?f(new eC(u,jy(u))):m(o)};i.length||(r(u),m(o)),i.forEach(y=>{const b=e[y];s.indexOf(y)!==-1?Z1(b,n,p):sk(b,n,p)})});return d.catch(m=>m),d}function lk(e){return!!(e&&e.message!==void 0)}function ck(e,t){let n=e;for(let r=0;r{let r;return e.fullFields?r=ck(t,e.fullFields):r=t[n.field||e.fullField],lk(n)?(n.field=n.field||e.fullField,n.fieldValue=r,n):{message:typeof n=="function"?n():n,fieldValue:r,field:n.field||e.fullField}}}function nC(e,t){if(t){for(const n in t)if(t.hasOwnProperty(n)){const r=t[n];typeof r=="object"&&typeof e[n]=="object"?e[n]={...e[n],...r}:e[n]=r}}return e}const Yi="enum",uk=(e,t,n,r,o)=>{e[Yi]=Array.isArray(e[Yi])?e[Yi]:[],e[Yi].indexOf(t)===-1&&r.push(Or(o.messages[Yi],e.fullField,e[Yi].join(", ")))},dk=(e,t,n,r,o)=>{e.pattern&&(e.pattern instanceof RegExp?(e.pattern.lastIndex=0,e.pattern.test(t)||r.push(Or(o.messages.pattern.mismatch,e.fullField,t,e.pattern))):typeof e.pattern=="string"&&(new RegExp(e.pattern).test(t)||r.push(Or(o.messages.pattern.mismatch,e.fullField,t,e.pattern))))},fk=(e,t,n,r,o)=>{const s=typeof e.len=="number",i=typeof e.min=="number",l=typeof e.max=="number",c=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g;let u=t,d=null;const m=typeof t=="number",f=typeof t=="string",p=Array.isArray(t);if(m?d="number":f?d="string":p&&(d="array"),!d)return!1;p&&(u=t.length),f&&(u=t.replace(c,"_").length),s?u!==e.len&&r.push(Or(o.messages[d].len,e.fullField,e.len)):i&&!l&&ue.max?r.push(Or(o.messages[d].max,e.fullField,e.max)):i&&l&&(ue.max)&&r.push(Or(o.messages[d].range,e.fullField,e.min,e.max))},eR=(e,t,n,r,o,s)=>{e.required&&(!n.hasOwnProperty(e.field)||jn(t,s||e.type))&&r.push(Or(o.messages.required,e.fullField))};let Du;const mk=()=>{if(Du)return Du;const e="[a-fA-F\\d:]",t=C=>C&&C.includeBoundaries?`(?:(?<=\\s|^)(?=${e})|(?<=${e})(?=\\s|$))`:"",n="(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}",r="[a-fA-F\\d]{1,4}",i=`(?:${[`(?:${r}:){7}(?:${r}|:)`,`(?:${r}:){6}(?:${n}|:${r}|:)`,`(?:${r}:){5}(?::${n}|(?::${r}){1,2}|:)`,`(?:${r}:){4}(?:(?::${r}){0,1}:${n}|(?::${r}){1,3}|:)`,`(?:${r}:){3}(?:(?::${r}){0,2}:${n}|(?::${r}){1,4}|:)`,`(?:${r}:){2}(?:(?::${r}){0,3}:${n}|(?::${r}){1,5}|:)`,`(?:${r}:){1}(?:(?::${r}){0,4}:${n}|(?::${r}){1,6}|:)`,`(?::(?:(?::${r}){0,5}:${n}|(?::${r}){1,7}|:))`].join("|")})(?:%[0-9a-zA-Z]{1,})?`,l=new RegExp(`(?:^${n}$)|(?:^${i}$)`),c=new RegExp(`^${n}$`),u=new RegExp(`^${i}$`),d=C=>C&&C.exact?l:new RegExp(`(?:${t(C)}${n}${t(C)})|(?:${t(C)}${i}${t(C)})`,"g");d.v4=C=>C&&C.exact?c:new RegExp(`${t(C)}${n}${t(C)}`,"g"),d.v6=C=>C&&C.exact?u:new RegExp(`${t(C)}${i}${t(C)}`,"g");const m="(?:(?:[a-z]+:)?//)",f="(?:\\S+(?::\\S*)?@)?",p=d.v4().source,y=d.v6().source,$=`(?:${m}|www\\.)${f}(?:localhost|${p}|${y}|(?:(?:[a-z\\u00a1-\\uffff0-9][-_]*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:[/?#][^\\s"]*)?`;return Du=new RegExp(`(?:^${$}$)`,"i"),Du},Zp={email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/,tel:/^(\+[0-9]{1,3}[-\s\u2011]?)?(\([0-9]{1,4}\)[-\s\u2011]?)?([0-9]+[-\s\u2011]?)*[0-9]+$/,hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},Ml={integer(e){return Ml.number(e)&&parseInt(e,10)===e},float(e){return Ml.number(e)&&!Ml.integer(e)},array(e){return Array.isArray(e)},regexp(e){if(e instanceof RegExp)return!0;try{return!!new RegExp(e)}catch{return!1}},date(e){return typeof e.getTime=="function"&&typeof e.getMonth=="function"&&typeof e.getYear=="function"&&!isNaN(e.getTime())},number(e){return isNaN(e)?!1:typeof e=="number"},object(e){return typeof e=="object"&&!Ml.array(e)},method(e){return typeof e=="function"},email(e){return typeof e=="string"&&e.length<=320&&!!e.match(Zp.email)},tel(e){return typeof e=="string"&&e.length<=32&&!!e.match(Zp.tel)},url(e){return typeof e=="string"&&e.length<=2048&&!!e.match(mk())},hex(e){return typeof e=="string"&&!!e.match(Zp.hex)}},pk=(e,t,n,r,o)=>{if(e.required&&t===void 0){eR(e,t,n,r,o);return}const s=["integer","float","array","regexp","object","method","email","tel","number","date","url","hex"],i=e.type;s.indexOf(i)>-1?Ml[i](t)||r.push(Or(o.messages.types[i],e.fullField,e.type)):i&&typeof t!==e.type&&r.push(Or(o.messages.types[i],e.fullField,e.type))},gk=(e,t,n,r,o)=>{(/^\s+$/.test(t)||t==="")&&r.push(Or(o.messages.whitespace,e.fullField))},At={required:eR,whitespace:gk,type:pk,range:fk,enum:uk,pattern:dk},hk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o)}n(s)},yk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(t==null&&!e.required)return n();At.required(e,t,r,s,o,"array"),t!=null&&(At.type(e,t,r,s,o),At.range(e,t,r,s,o))}n(s)},vk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&At.type(e,t,r,s,o)}n(s)},bk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t,"date")&&!e.required)return n();if(At.required(e,t,r,s,o),!jn(t,"date")){let l;t instanceof Date?l=t:l=new Date(t),At.type(e,l,r,s,o),l&&At.range(e,l.getTime(),r,s,o)}}n(s)},xk="enum",$k=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&At[xk](e,t,r,s,o)}n(s)},Sk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&(At.type(e,t,r,s,o),At.range(e,t,r,s,o))}n(s)},Ck=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&(At.type(e,t,r,s,o),At.range(e,t,r,s,o))}n(s)},wk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&At.type(e,t,r,s,o)}n(s)},Ek=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(t===""&&(t=void 0),jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&(At.type(e,t,r,s,o),At.range(e,t,r,s,o))}n(s)},Ik=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),t!==void 0&&At.type(e,t,r,s,o)}n(s)},Pk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t,"string")&&!e.required)return n();At.required(e,t,r,s,o),jn(t,"string")||At.pattern(e,t,r,s,o)}n(s)},Nk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t)&&!e.required)return n();At.required(e,t,r,s,o),jn(t)||At.type(e,t,r,s,o)}n(s)},Rk=(e,t,n,r,o)=>{const s=[],i=Array.isArray(t)?"array":typeof t;At.required(e,t,r,s,o,i),n(s)},Tk=(e,t,n,r,o)=>{const s=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t,"string")&&!e.required)return n();At.required(e,t,r,s,o,"string"),jn(t,"string")||(At.type(e,t,r,s,o),At.range(e,t,r,s,o),At.pattern(e,t,r,s,o),e.whitespace===!0&&At.whitespace(e,t,r,s,o))}n(s)},Fu=(e,t,n,r,o)=>{const s=e.type,i=[];if(e.required||!e.required&&r.hasOwnProperty(e.field)){if(jn(t,s)&&!e.required)return n();At.required(e,t,r,i,o,s),jn(t,s)||At.type(e,t,r,i,o)}n(i)},bl={string:Tk,method:wk,number:Ek,boolean:vk,regexp:Nk,integer:Ck,float:Sk,array:yk,object:Ik,enum:$k,pattern:Pk,date:bk,url:Fu,hex:Fu,email:Fu,tel:Fu,required:Rk,any:hk},vs=class vs{constructor(t){fe(this,"rules",null);fe(this,"_messages",Jp);this.define(t)}define(t){if(!t)throw new Error("Cannot configure a schema with no rules");if(typeof t!="object"||Array.isArray(t))throw new Error("Rules must be an object");this.rules={},Object.keys(t).forEach(n=>{const r=t[n];this.rules[n]=Array.isArray(r)?r:[r]})}messages(t){return t&&(this._messages=nC(zy(),t)),this._messages}validate(t,n={},r=()=>{}){let o=t,s=n,i=r;if(typeof s=="function"&&(i=s,s={}),!this.rules||Object.keys(this.rules).length===0)return i&&i(null,o),Promise.resolve(o);function l(m){let f=[],p={};function y(b){Array.isArray(b)?f=f.concat(...b):f.push(b)}for(let b=0;b{const f=this.rules[m];let p=o[m];f.forEach(y=>{let b=y;typeof b.transform=="function"&&(o===t&&(o={...o}),p=o[m]=b.transform(p),p!=null&&(b.type=b.type||(Array.isArray(p)?"array":typeof p))),typeof b=="function"?b={validator:b}:b={...b},b.validator=this.getValidationMethod(b),b.validator&&(b.field=m,b.fullField=b.fullField||m,b.type=this.getType(b),c[m]=c[m]||[],c[m].push({rule:b,value:p,source:o,field:m}))})});const d={};return ak(c,s,(m,f)=>{var g;const p=m.rule;let y=(p.type==="object"||p.type==="array")&&(typeof p.fields=="object"||typeof p.defaultField=="object");y=y&&(p.required||!p.required&&m.value),p.field=m.field;function b(h,$){return{...$,fullField:`${p.fullField}.${h}`,fullFields:p.fullFields?[...p.fullFields,h]:[h]}}function x(h=[]){let $=Array.isArray(h)?h:[h];!s.suppressWarning&&$.length&&vs.warning("async-validator:",$),$.length&&p.message!==void 0&&p.message!==null&&($=[].concat(p.message));let C=$.map(tC(p,o));if(s.first&&C.length)return d[p.field]=1,f(C);if(!y)f(C);else{if(p.required&&!m.value)return p.message!==void 0?C=[].concat(p.message).map(tC(p,o)):s.error&&(C=[s.error(p,Or(s.messages.required,p.field))]),f(C);let N={};p.defaultField&&Object.keys(m.value).map(w=>{N[w]=p.defaultField}),N={...N,...m.rule.fields};const S={};Object.keys(N).forEach(w=>{const R=N[w],P=Array.isArray(R)?R:[R];S[w]=P.map(b.bind(null,w))});const E=new vs(S);E.messages(s.messages),m.rule.options&&(m.rule.options.messages=s.messages,m.rule.options.error=s.error),E.validate(m.value,m.rule.options||s,w=>{const R=[];C&&C.length&&R.push(...C),w&&w.length&&R.push(...w),f(R.length?R:null)})}}let v;if(p.asyncValidator)v=p.asyncValidator(p,m.value,x,m.source,s);else if(p.validator){try{v=p.validator(p,m.value,x,m.source,s)}catch(h){(g=console.error)==null||g.call(console,h),s.suppressValidatorError||setTimeout(()=>{throw h},0),x(h.message)}v===!0?x():v===!1?x(typeof p.message=="function"?p.message(p.fullField||p.field):p.message||`${p.fullField||p.field} fails`):v instanceof Array?x(v):v instanceof Error&&x(v.message)}v&&v.then&&v.then(()=>x(),h=>x(h))},m=>{l(m)},o)}getType(t){if(t.type===void 0&&t.pattern instanceof RegExp&&(t.type="pattern"),typeof t.validator!="function"&&t.type&&!bl.hasOwnProperty(t.type))throw new Error(Or("Unknown rule type %s",t.type));return t.type||"string"}getValidationMethod(t){if(typeof t.validator=="function")return t.validator;const n=Object.keys(t),r=n.indexOf("message");return r!==-1&&n.splice(r,1),n.length===1&&n[0]==="required"?bl.required:bl[this.getType(t)]||void 0}};fe(vs,"register",function(n,r){if(typeof r!="function")throw new Error("Cannot register a validator by type, validator is not a function");bl[n]=r}),fe(vs,"warning",rk),fe(vs,"messages",Jp),fe(vs,"validators",bl);let By=vs;const gr="'${name}' is not a valid ${type}",tR={default:"Validation error on field '${name}'",required:"'${name}' is required",enum:"'${name}' must be one of [${enum}]",whitespace:"'${name}' cannot be empty",date:{format:"'${name}' is invalid for format date",parse:"'${name}' could not be parsed as date",invalid:"'${name}' is invalid date"},types:{string:gr,method:gr,array:gr,object:gr,number:gr,date:gr,boolean:gr,integer:gr,float:gr,regexp:gr,email:gr,tel:gr,url:gr,hex:gr},string:{len:"'${name}' must be exactly ${len} characters",min:"'${name}' must be at least ${min} characters",max:"'${name}' cannot be longer than ${max} characters",range:"'${name}' must be between ${min} and ${max} characters"},number:{len:"'${name}' must equal ${len}",min:"'${name}' cannot be less than ${min}",max:"'${name}' cannot be greater than ${max}",range:"'${name}' must be between ${min} and ${max}"},array:{len:"'${name}' must be exactly ${len} in length",min:"'${name}' cannot be less than ${min} in length",max:"'${name}' cannot be greater than ${max} in length",range:"'${name}' must be between ${min} and ${max} in length"},pattern:{mismatch:"'${name}' does not match pattern ${pattern}"}},rC=By;function Mk(e,t){return e.replace(/\\?\$\{\w+\}/g,n=>{if(n.startsWith("\\"))return n.slice(1);const r=n.slice(2,-1);return t[r]})}const oC="CODE_LOGIC_ERROR";async function Ly(e,t,n,r,o){const s={...n};if(delete s.ruleIndex,rC.warning=()=>{},s.validator){const f=s.validator;s.validator=(...p)=>{try{return f(...p)}catch(y){return console.error(y),Promise.reject(oC)}}}let i=null;s&&s.type==="array"&&s.defaultField&&(i=s.defaultField,delete s.defaultField);const l=new rC({[e]:[s]}),c=ba(tR,r.validateMessages);l.messages(c);let u=[];try{await Promise.resolve(l.validate({[e]:t},{...r}))}catch(f){f.errors&&(u=f.errors.map(({message:p},y)=>{const b=p===oC?c.default:p;return a.isValidElement(b)?a.cloneElement(b,{key:`error_${y}`}):b}))}if(!u.length&&i&&Array.isArray(t)&&t.length>0)return(await Promise.all(t.map((p,y)=>Ly(`${e}.${y}`,p,i,r,o)))).reduce((p,y)=>[...p,...y],[]);const d={...n,name:e,enum:(n.enum||[]).join(", "),...o};return u.map(f=>typeof f=="string"?Mk(f,d):f)}function Ok(e,t,n,r,o,s){const i=e.join("."),l=n.map((u,d)=>{const m=u.validator,f={...u,ruleIndex:d};return m&&(f.validator=(p,y,b)=>{let x=!1;const g=m(p,y,(...h)=>{Promise.resolve().then(()=>{fn(!x,"Your validator function has already return a promise. `callback` will be ignored."),x||b(...h)})});x=g&&typeof g.then=="function"&&typeof g.catch=="function",fn(x,"`callback` is deprecated. Please return a promise instead."),x&&g.then(()=>{b()}).catch(h=>{b(h||" ")})}),f}).sort(({warningOnly:u,ruleIndex:d},{warningOnly:m,ruleIndex:f})=>!!u==!!m?d-f:u?1:-1);let c;if(o===!0)c=new Promise(async(u,d)=>{for(let m=0;mLy(i,t,d,r,s).then(m=>({errors:m,rule:d})));c=(o?zk(u):_k(u)).then(d=>Promise.reject(d))}return c.catch(u=>u),c}async function _k(e){return Promise.all(e).then(t=>[].concat(...t))}async function zk(e){let t=0;return new Promise(n=>{e.forEach(r=>{r.then(o=>{o.errors.length&&n([o]),t+=1,t===e.length&&n([])})})})}function yn(e){return _y(e)}function sC(e,t){let n={};return t.forEach(r=>{const o=Kn(e,r);n=hr(n,r,o)}),n}function Ra(e,t,n=!1){return e&&e.some(r=>mf(t,r,n))}function mf(e,t,n=!1){return!e||!t||!n&&e.length!==t.length?!1:t.every((r,o)=>e[o]===r)}function jk(e,t){if(e===t)return!0;if(!e&&t||e&&!t||!e||!t||typeof e!="object"||typeof t!="object")return!1;const n=Object.keys(e),r=Object.keys(t);return[...new Set([...n,...r])].every(s=>{const i=e[s],l=t[s];return typeof i=="function"&&typeof l=="function"?!0:i===l})}function Bk(e,...t){const n=t[0];return n&&n.target&&typeof n.target=="object"&&e in n.target?n.target[e]:n}function iC(e,t,n){const{length:r}=e;if(t<0||t>=r||n<0||n>=r)return e;const o=e[t],s=t-n;return s>0?[...e.slice(0,n),o,...e.slice(n,t),...e.slice(t+1,r)]:s<0?[...e.slice(0,t),...e.slice(t+1,n+1),o,...e.slice(n+1,r)]:e}const nR=e=>{const t=new MessageChannel;t.port1.onmessage=e,t.port2.postMessage(null)};class Lk{constructor(t){fe(this,"namePathList",[]);fe(this,"taskId",0);fe(this,"watcherList",new Set);fe(this,"form");this.form=t}register(t){return this.watcherList.add(t),()=>{this.watcherList.delete(t)}}notify(t){t.forEach(n=>{this.namePathList.every(r=>!mf(r,n))&&this.namePathList.push(n)}),this.doBatch()}doBatch(){this.taskId+=1;const t=this.taskId;nR(()=>{if(t===this.taskId&&this.watcherList.size){const n=this.form.getForm(),r=n.getFieldsValue(),o=n.getFieldsValue(!0);this.watcherList.forEach(s=>{s(r,o,this.namePathList)}),this.namePathList=[]}})}}async function kk(){return new Promise(e=>{nR(()=>{Ct(()=>{e()})})})}function ky(){return ky=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{preserve:n,isListField:r,name:o}=this.props;this.cancelRegisterFunc&&this.cancelRegisterFunc(r,n,yn(o)),this.cancelRegisterFunc=null});fe(this,"getNamePath",()=>{const{name:n,fieldContext:r}=this.props,{prefixName:o=[]}=r;return n!==void 0?[...o,...n]:[]});fe(this,"getRules",()=>{const{rules:n=[],fieldContext:r}=this.props;return n.map(o=>typeof o=="function"?o(r):o)});fe(this,"refresh",()=>{this.mounted&&this.setState(({resetCount:n})=>({resetCount:n+1}))});fe(this,"metaCache",null);fe(this,"triggerMetaEvent",n=>{const{onMetaChange:r}=this.props;if(r){const o={...this.getMeta(),destroy:n};ho(this.metaCache,o)||r(o),this.metaCache=o}else this.metaCache=null});fe(this,"onStoreChange",(n,r,o)=>{const{shouldUpdate:s,dependencies:i=[],onReset:l}=this.props,{store:c}=o,u=this.getNamePath(),d=this.getValue(n),m=this.getValue(c),f=r&&Ra(r,u);switch(o.type==="valueUpdate"&&o.source==="external"&&!ho(d,m)&&(this.touched=!0,this.dirty=!0,this.validatePromise=null,this.errors=Ys,this.warnings=xl,this.triggerMetaEvent()),o.type){case"reset":if(!r||f){this.touched=!1,this.dirty=!1,this.validatePromise=void 0,this.errors=Ys,this.warnings=xl,this.triggerMetaEvent(),l==null||l(),this.refresh();return}break;case"remove":{if(s&&eg(s,n,c,d,m,o)){this.reRender();return}break}case"setField":{const{data:p}=o;if(f){"touched"in p&&(this.touched=p.touched),"validating"in p&&!("originRCField"in p)&&(this.validatePromise=p.validating?Promise.resolve([]):null),"errors"in p&&(this.errors=p.errors||Ys),"warnings"in p&&(this.warnings=p.warnings||xl),this.dirty=!0,this.triggerMetaEvent(),this.reRender();return}else if("value"in p&&Ra(r,u,!0)){this.reRender();return}if(s&&!u.length&&eg(s,n,c,d,m,o)){this.reRender();return}break}case"dependenciesUpdate":{if(i.map(yn).some(y=>Ra(o.relatedFields,y))){this.reRender();return}break}default:if(f||(!i.length||u.length||s)&&eg(s,n,c,d,m,o)){this.reRender();return}break}s===!0&&this.reRender()});fe(this,"validateRules",n=>{const r=this.getNamePath(),o=this.getValue(),{triggerName:s,validateOnly:i=!1,delayFrame:l}=n||{},c=Promise.resolve().then(async()=>{if(!this.mounted)return[];const{validateFirst:u=!1,messageVariables:d,validateDebounce:m}=this.props;l&&await kk();let f=this.getRules();if(s&&(f=f.filter(y=>y).filter(y=>{const{validateTrigger:b}=y;return b?_y(b).includes(s):!0})),m&&s&&(await new Promise(y=>{setTimeout(y,m)}),this.validatePromise!==c))return[];const p=Ok(r,o,f,n,u,d);return p.catch(y=>y).then((y=Ys)=>{var b;if(this.validatePromise===c){this.validatePromise=null;const x=[],v=[];(b=y.forEach)==null||b.call(y,({rule:{warningOnly:g},errors:h=Ys})=>{g?v.push(...h):x.push(...h)}),this.errors=x,this.warnings=v,this.triggerMetaEvent(),this.reRender()}}),p});return i||(this.validatePromise=c,this.dirty=!0,this.errors=Ys,this.warnings=xl,this.triggerMetaEvent(),this.reRender()),c});fe(this,"isFieldValidating",()=>!!this.validatePromise);fe(this,"isFieldTouched",()=>this.touched);fe(this,"isFieldDirty",()=>{if(this.dirty||this.props.initialValue!==void 0)return!0;const{fieldContext:n}=this.props,{getInitialValue:r}=n.getInternalHooks(fi);return r(this.getNamePath())!==void 0});fe(this,"getErrors",()=>this.errors);fe(this,"getWarnings",()=>this.warnings);fe(this,"isListField",()=>this.props.isListField);fe(this,"isList",()=>this.props.isList);fe(this,"isPreserve",()=>this.props.preserve);fe(this,"getMeta",()=>(this.prevValidating=this.isFieldValidating(),{touched:this.isFieldTouched(),validating:this.prevValidating,errors:this.errors,warnings:this.warnings,name:this.getNamePath(),validated:this.validatePromise===null}));fe(this,"getOnlyChild",n=>{if(typeof n=="function"){const o=this.getMeta();return{...this.getOnlyChild(n(this.getControlled(),o,this.props.fieldContext)),isFunction:!0}}const r=zn(n);return r.length!==1||!a.isValidElement(r[0])?{child:r,isFunction:!1}:{child:r[0],isFunction:!1}});fe(this,"getValue",n=>{const{getFieldsValue:r}=this.props.fieldContext,o=this.getNamePath();return Kn(n||r(!0),o)});fe(this,"getControlled",(n={})=>{const{name:r,trigger:o="onChange",validateTrigger:s,getValueFromEvent:i,normalize:l,valuePropName:c="value",getValueProps:u,fieldContext:d}=this.props,m=s!==void 0?s:d.validateTrigger,f=this.getNamePath(),{getInternalHooks:p,getFieldsValue:y}=d,{dispatch:b}=p(fi),x=this.getValue(),v=u||(N=>({[c]:N})),g=n[o],h=r!==void 0?v(x):{},$={...n,...h};return $[o]=(...N)=>{this.touched=!0,this.dirty=!0,this.triggerMetaEvent();let S;i?S=i(...N):S=Bk(c,...N),l&&(S=l(S,x,y(!0))),S!==x&&b({type:"updateValue",namePath:f,value:S}),g&&g(...N)},_y(m||[]).forEach(N=>{const S=$[N];$[N]=(...E)=>{S&&S(...E);const{rules:w}=this.props;w&&w.length&&b({type:"validateField",namePath:f,triggerName:N})}}),$});if(n.fieldContext){const{getInternalHooks:r}=n.fieldContext,{initEntityValue:o}=r(fi);o(this)}}componentDidMount(){const{shouldUpdate:n,fieldContext:r}=this.props;if(this.mounted=!0,r){const{getInternalHooks:o}=r,{registerField:s}=o(fi);this.cancelRegisterFunc=s(this)}n===!0&&this.reRender()}componentWillUnmount(){this.cancelRegister(),this.triggerMetaEvent(!0),this.mounted=!1}reRender(){this.mounted&&this.forceUpdate()}render(){const{resetCount:n}=this.state,{children:r}=this.props,{child:o,isFunction:s}=this.getOnlyChild(r);let i;return s?i=o:a.isValidElement(o)?i=a.cloneElement(o,this.getControlled(o.props)):(fn(!o,"`children` of Field is not validate ReactElement."),i=o),a.createElement(a.Fragment,{key:n},i)}}fe(rR,"contextType",Ni);function bx({name:e,...t}){const n=a.useContext(Ni),r=a.useContext(Rc),o=e!==void 0?yn(e):void 0,s=t.isListField??!!r;let i="keep";return s||(i=`_${(o||[]).join("_")}`),a.createElement(rR,ky({key:i,name:o,isListField:s},t,{fieldContext:n}))}function oR({name:e,initialValue:t,children:n,rules:r,validateTrigger:o,isListField:s}){const i=a.useContext(Ni),l=a.useContext(Rc),u=a.useRef({keys:[],id:0}).current,d=a.useMemo(()=>[...yn(i.prefixName)||[],...yn(e)],[i.prefixName,e]),m=a.useMemo(()=>({...i,prefixName:d}),[i,d]),f=a.useMemo(()=>({getKey:y=>{const b=d.length,x=y[b];return[u.keys[x],y.slice(b+1)]}}),[u,d]);if(typeof n!="function")return fn(!1,"Form.List only accepts function as children."),null;const p=(y,b,{source:x})=>x==="internal"?!1:y!==b;return a.createElement(Rc.Provider,{value:f},a.createElement(Ni.Provider,{value:m},a.createElement(bx,{name:[],shouldUpdate:p,rules:r,validateTrigger:o,initialValue:t,isList:!0,isListField:s??!!l},({value:y=[],onChange:b},x)=>{const{getFieldValue:v}=i,g=()=>v(d||[])||[],h={add:(C,N)=>{const S=g();N>=0&&N<=S.length?(u.keys=[...u.keys.slice(0,N),u.id,...u.keys.slice(N)],b([...S.slice(0,N),C,...S.slice(N)])):(u.keys=[...u.keys,u.id],b([...S,C])),u.id+=1},remove:C=>{const N=g(),S=new Set(Array.isArray(C)?C:[C]);S.size<=0||(u.keys=u.keys.filter((E,w)=>!S.has(w)),b(N.filter((E,w)=>!S.has(w))))},move(C,N){if(C===N)return;const S=g();C<0||C>=S.length||N<0||N>=S.length||(u.keys=iC(u.keys,C,N),b(iC(S,C,N)))}};let $=y||[];return Array.isArray($)||($=[]),n($.map((C,N)=>{let S=u.keys[N];return S===void 0&&(u.keys[N]=u.id,S=u.keys[N],u.id+=1),{name:N,key:S,isListField:!0}}),h,x)})))}function Ak(e){let t=!1,n=e.length;const r=[];return e.length?new Promise((o,s)=>{e.forEach((i,l)=>{i.catch(c=>(t=!0,c)).then(c=>{n-=1,r[l]=c,!(n>0)&&(t&&s(r),o(r))})})}):Promise.resolve([])}const Ay="__@field_split__";function Hu(e){return e.map(t=>`${typeof t}:${t}`).join(Ay)}class Qi{constructor(){fe(this,"kvs",new Map)}set(t,n){this.kvs.set(Hu(t),n)}get(t){return this.kvs.get(Hu(t))}getAsPrefix(t){const n=Hu(t),r=n+Ay,o=[],s=this.kvs.get(n);return s!==void 0&&o.push(s),this.kvs.forEach((i,l)=>{l.startsWith(r)&&o.push(i)}),o}update(t,n){const r=this.get(t),o=n(r);o?this.set(t,o):this.delete(t)}delete(t){this.kvs.delete(Hu(t))}map(t){return[...this.kvs.entries()].map(([n,r])=>{const o=n.split(Ay);return t({key:o.map(s=>{const[,i,l]=s.match(/^([^:]*):(.*)$/);return i==="number"?Number(l):l}),value:r})})}toJSON(){const t={};return this.map(({key:n,value:r})=>(t[n.join(".")]=r,null)),t}}class Dk{constructor(t){fe(this,"formHooked",!1);fe(this,"forceRootUpdate");fe(this,"subscribable",!0);fe(this,"store",{});fe(this,"fieldEntities",[]);fe(this,"initialValues",{});fe(this,"callbacks",{});fe(this,"validateMessages",null);fe(this,"preserve",null);fe(this,"lastValidatePromise",null);fe(this,"watcherCenter",new Lk(this));fe(this,"getForm",()=>({getFieldValue:this.getFieldValue,getFieldsValue:this.getFieldsValue,getFieldError:this.getFieldError,getFieldWarning:this.getFieldWarning,getFieldsError:this.getFieldsError,isFieldsTouched:this.isFieldsTouched,isFieldTouched:this.isFieldTouched,isFieldValidating:this.isFieldValidating,isFieldsValidating:this.isFieldsValidating,resetFields:this.resetFields,setFields:this.setFields,setFieldValue:this.setFieldValue,setFieldsValue:this.setFieldsValue,validateFields:this.validateFields,submit:this.submit,_init:!0,getInternalHooks:this.getInternalHooks}));fe(this,"getInternalHooks",t=>t===fi?(this.formHooked=!0,{dispatch:this.dispatch,initEntityValue:this.initEntityValue,registerField:this.registerField,useSubscribe:this.useSubscribe,setInitialValues:this.setInitialValues,destroyForm:this.destroyForm,setCallbacks:this.setCallbacks,setValidateMessages:this.setValidateMessages,getFields:this.getFields,setPreserve:this.setPreserve,getInitialValue:this.getInitialValue,registerWatch:this.registerWatch}):(fn(!1,"`getInternalHooks` is internal usage. Should not call directly."),null));fe(this,"useSubscribe",t=>{this.subscribable=t});fe(this,"prevWithoutPreserves",null);fe(this,"setInitialValues",(t,n)=>{var r;if(this.initialValues=t||{},n){let o=ba(t,this.store);(r=this.prevWithoutPreserves)==null||r.map(({key:s})=>{o=hr(o,s,Kn(t,s))}),this.prevWithoutPreserves=null,this.updateStore(o)}});fe(this,"destroyForm",t=>{if(t)this.updateStore({});else{const n=new Qi;this.getFieldEntities(!0).forEach(r=>{this.isMergedPreserve(r.isPreserve())||n.set(r.getNamePath(),!0)}),this.prevWithoutPreserves=n}});fe(this,"getInitialValue",t=>{const n=Kn(this.initialValues,t);return t.length?ba(n):n});fe(this,"setCallbacks",t=>{this.callbacks=t});fe(this,"setValidateMessages",t=>{this.validateMessages=t});fe(this,"setPreserve",t=>{this.preserve=t});fe(this,"registerWatch",t=>this.watcherCenter.register(t));fe(this,"notifyWatch",(t=[])=>{this.watcherCenter.notify(t)});fe(this,"timeoutId",null);fe(this,"warningUnhooked",()=>{});fe(this,"updateStore",t=>{this.store=t});fe(this,"getFieldEntities",(t=!1)=>t?this.fieldEntities.filter(n=>n.getNamePath().length):this.fieldEntities);fe(this,"getFieldsMap",(t=!1)=>{const n=new Qi;return this.getFieldEntities(t).forEach(r=>{const o=r.getNamePath();n.set(o,r)}),n});fe(this,"getFieldEntitiesForNamePathList",(t,n=!1)=>{if(!t)return this.getFieldEntities(!0);const r=this.getFieldsMap(!0);return n?t.flatMap(o=>{const s=yn(o),i=r.getAsPrefix(s);return i.length?i:[{INVALIDATE_NAME_PATH:s}]}):t.map(o=>{const s=yn(o);return r.get(s)||{INVALIDATE_NAME_PATH:yn(o)}})});fe(this,"getFieldsValue",(t,n)=>{this.warningUnhooked();let r,o;if(t===!0||Array.isArray(t)?(r=t,o=n):t&&typeof t=="object"&&(o=t.filter),r===!0&&!o)return this.store;const s=this.getFieldEntitiesForNamePathList(Array.isArray(r)?r:null,!0),i=[],l=[];s.forEach(u=>{var m;const d=u.INVALIDATE_NAME_PATH||u.getNamePath();if((m=u.isList)!=null&&m.call(u)){l.push(d);return}if(!o)i.push(d);else{const f="getMeta"in u?u.getMeta():null;o(f)&&i.push(d)}});let c=sC(this.store,i.map(yn));return l.forEach(u=>{Kn(c,u)||(c=hr(c,u,[]))}),c});fe(this,"getFieldValue",t=>{this.warningUnhooked();const n=yn(t);return Kn(this.store,n)});fe(this,"getFieldsError",t=>(this.warningUnhooked(),this.getFieldEntitiesForNamePathList(t).map((r,o)=>r&&!r.INVALIDATE_NAME_PATH?{name:r.getNamePath(),errors:r.getErrors(),warnings:r.getWarnings()}:{name:yn(t[o]),errors:[],warnings:[]})));fe(this,"getFieldError",t=>{this.warningUnhooked();const n=yn(t);return this.getFieldsError([n])[0].errors});fe(this,"getFieldWarning",t=>{this.warningUnhooked();const n=yn(t);return this.getFieldsError([n])[0].warnings});fe(this,"isFieldsTouched",(...t)=>{this.warningUnhooked();const[n,r]=t;let o,s=!1;t.length===0?o=null:t.length===1?Array.isArray(n)?(o=n.map(yn),s=!1):(o=null,s=n):(o=n.map(yn),s=r);const i=this.getFieldEntities(!0),l=m=>m.isFieldTouched();if(!o)return s?i.every(m=>l(m)||m.isList()):i.some(l);const c=new Qi;o.forEach(m=>{c.set(m,[])}),i.forEach(m=>{const f=m.getNamePath();o.forEach(p=>{p.every((y,b)=>f[b]===y)&&c.update(p,y=>[...y,m])})});const u=m=>m.some(l),d=c.map(({value:m})=>m);return s?d.every(u):d.some(u)});fe(this,"isFieldTouched",t=>(this.warningUnhooked(),this.isFieldsTouched([t])));fe(this,"isFieldsValidating",t=>{this.warningUnhooked();const n=this.getFieldEntities();if(!t)return n.some(o=>o.isFieldValidating());const r=t.map(yn);return n.some(o=>{const s=o.getNamePath();return Ra(r,s)&&o.isFieldValidating()})});fe(this,"isFieldValidating",t=>(this.warningUnhooked(),this.isFieldsValidating([t])));fe(this,"resetWithFieldInitialValue",(t={})=>{const n=new Qi,r=this.getFieldEntities(!0);r.forEach(i=>{const{initialValue:l}=i.props,c=i.getNamePath();if(l!==void 0){const u=n.get(c)||new Set;u.add({entity:i,value:l}),n.set(c,u)}});const o=i=>{i.forEach(l=>{const{initialValue:c}=l.props;if(c!==void 0){const u=l.getNamePath();if(this.getInitialValue(u)!==void 0)fn(!1,`Form already set 'initialValues' with path '${u.join(".")}'. Field can not overwrite it.`);else{const m=n.get(u);if(m&&m.size>1)fn(!1,`Multiple Field with path '${u.join(".")}' set 'initialValue'. Can not decide which one to pick.`);else if(m){const f=this.getFieldValue(u);!l.isListField()&&(!t.skipExist||f===void 0)&&this.updateStore(hr(this.store,u,[...m][0].value))}}}})};let s;t.entities?s=t.entities:t.namePathList?(s=[],t.namePathList.forEach(i=>{const l=n.get(i);l&&s.push(...[...l].map(c=>c.entity))})):s=r,o(s)});fe(this,"resetFields",t=>{this.warningUnhooked();const n=this.store;if(!t){this.updateStore(ba(this.initialValues)),this.resetWithFieldInitialValue(),this.notifyObservers(n,null,{type:"reset"}),this.notifyWatch();return}const r=t.map(yn);r.forEach(o=>{const s=this.getInitialValue(o);this.updateStore(hr(this.store,o,s))}),this.resetWithFieldInitialValue({namePathList:r}),this.notifyObservers(n,r,{type:"reset"}),this.notifyWatch(r)});fe(this,"setFields",t=>{this.warningUnhooked();const n=this.store,r=[];t.forEach(o=>{const{name:s,...i}=o,l=yn(s);r.push(l),"value"in i&&this.updateStore(hr(this.store,l,i.value)),this.notifyObservers(n,[l],{type:"setField",data:o})}),this.notifyWatch(r)});fe(this,"getFields",()=>this.getFieldEntities(!0).map(r=>{const o=r.getNamePath(),i={...r.getMeta(),name:o,value:this.getFieldValue(o)};return Object.defineProperty(i,"originRCField",{value:!0}),i}));fe(this,"initEntityValue",t=>{const{initialValue:n}=t.props;if(n!==void 0){const r=t.getNamePath();Kn(this.store,r)===void 0&&this.updateStore(hr(this.store,r,n))}});fe(this,"isMergedPreserve",t=>(t!==void 0?t:this.preserve)??!0);fe(this,"registerField",t=>{this.fieldEntities.push(t);const n=t.getNamePath();if(this.notifyWatch([n]),t.props.initialValue!==void 0){const r=this.store;this.resetWithFieldInitialValue({entities:[t],skipExist:!0}),this.notifyObservers(r,[t.getNamePath()],{type:"valueUpdate",source:"internal"})}return(r,o,s=[])=>{if(this.fieldEntities=this.fieldEntities.filter(i=>i!==t),!this.isMergedPreserve(o)&&(!r||s.length>1)){const i=r?void 0:this.getInitialValue(n);if(n.length&&this.getFieldValue(n)!==i&&this.fieldEntities.every(l=>!mf(l.getNamePath(),n))){const l=this.store;this.updateStore(hr(l,n,i,!0)),this.notifyObservers(l,[n],{type:"remove"}),this.triggerDependenciesUpdate(l,n)}}this.notifyWatch([n])}});fe(this,"dispatch",t=>{switch(t.type){case"updateValue":{const{namePath:n,value:r}=t;this.updateValue(n,r);break}case"validateField":{const{namePath:n,triggerName:r}=t;this.validateFields([n],{triggerName:r});break}}});fe(this,"notifyObservers",(t,n,r)=>{if(this.subscribable){const o={...r,store:this.getFieldsValue(!0)};this.getFieldEntities().forEach(({onStoreChange:s})=>{s(t,n,o)})}else this.forceRootUpdate()});fe(this,"triggerDependenciesUpdate",(t,n)=>{const r=this.getDependencyChildrenFields(n);return r.length&&this.validateFields(r,{delayFrame:!0}),this.notifyObservers(t,r,{type:"dependenciesUpdate",relatedFields:[n,...r]}),r});fe(this,"updateValue",(t,n)=>{const r=yn(t),o=this.store;this.updateStore(hr(this.store,r,n)),this.notifyObservers(o,[r],{type:"valueUpdate",source:"internal"}),this.notifyWatch([r]);const s=this.triggerDependenciesUpdate(o,r),{onValuesChange:i}=this.callbacks;if(i){const l=sC(this.store,[r]),c=this.getFieldsValue(),u=hr(c,r,Kn(l,r));i(l,u)}this.triggerOnFieldsChange([r,...s])});fe(this,"setFieldsValue",t=>{this.warningUnhooked();const n=this.store;if(t){const r=ba(this.store,t);this.updateStore(r)}this.notifyObservers(n,null,{type:"valueUpdate",source:"external"}),this.notifyWatch()});fe(this,"setFieldValue",(t,n)=>{this.setFields([{name:t,value:n,errors:[],warnings:[],touched:!0}])});fe(this,"getDependencyChildrenFields",t=>{const n=new Set,r=[],o=new Qi;this.getFieldEntities().forEach(i=>{const{dependencies:l}=i.props;(l||[]).forEach(c=>{const u=yn(c);o.update(u,(d=new Set)=>(d.add(i),d))})});const s=i=>{(o.get(i)||new Set).forEach(c=>{if(!n.has(c)){n.add(c);const u=c.getNamePath();c.isFieldDirty()&&u.length&&(r.push(u),s(u))}})};return s(t),r});fe(this,"triggerOnFieldsChange",(t,n)=>{const{onFieldsChange:r}=this.callbacks;if(r){const o=this.getFields();if(n){const i=new Qi;n.forEach(({name:l,errors:c})=>{i.set(l,c)}),o.forEach(l=>{l.errors=i.get(l.name)||l.errors})}const s=o.filter(({name:i})=>Ra(t,i));s.length&&r(s,o)}});fe(this,"validateFields",(t,n)=>{this.warningUnhooked();let r,o;Array.isArray(t)||typeof t=="string"||typeof n=="string"?(r=t,o=n):o=t;const s=!!r,i=s?r.map(yn):[],l=[...i],c=[],u=String(Date.now()),d=new Set,{recursive:m,dirty:f}=o||{};this.getFieldEntities(!0).forEach(x=>{const v=x.getNamePath();if(s||((!x.isList()||!i.some(g=>mf(g,v,!0)))&&l.push(v),i.push(v)),!(!x.props.rules||!x.props.rules.length)&&!(f&&!x.isFieldDirty())&&(d.add(v.join(u)),!s||Ra(i,v,m))){const g=x.validateRules({validateMessages:{...tR,...this.validateMessages},...o});c.push(g.then(()=>({name:v,errors:[],warnings:[]})).catch(h=>{var N;const $=[],C=[];return(N=h.forEach)==null||N.call(h,({rule:{warningOnly:S},errors:E})=>{S?C.push(...E):$.push(...E)}),$.length?Promise.reject({name:v,errors:$,warnings:C}):{name:v,errors:$,warnings:C}}))}});const p=Ak(c);this.lastValidatePromise=p,p.catch(x=>x).then(x=>{const v=x.map(({name:g})=>g);this.notifyObservers(this.store,v,{type:"validateFinish"}),this.triggerOnFieldsChange(v,x)});const y=p.then(()=>this.lastValidatePromise===p?Promise.resolve(this.getFieldsValue(l)):Promise.reject([])).catch(x=>{var h,$;const v=x.filter(C=>C&&C.errors.length),g=($=(h=v[0])==null?void 0:h.errors)==null?void 0:$[0];return Promise.reject({message:g,values:this.getFieldsValue(i),errorFields:v,outOfDate:this.lastValidatePromise!==p})});y.catch(x=>x);const b=i.filter(x=>d.has(x.join(u)));return this.triggerOnFieldsChange(b),y});fe(this,"submit",()=>{this.warningUnhooked(),this.validateFields().then(t=>{const{onFinish:n}=this.callbacks;if(n)try{n(t)}catch(r){console.error(r)}}).catch(t=>{const{onFinishFailed:n}=this.callbacks;n&&n(t)})});this.forceRootUpdate=t}}function xx(e){const t=a.useRef(null),[,n]=a.useState({});if(!t.current)if(e)t.current=e;else{const r=()=>{n({})},o=new Dk(r);t.current=o.getForm()}return[t.current]}const Dy=a.createContext({triggerFormChange:()=>{},triggerFormFinish:()=>{},registerForm:()=>{},unregisterForm:()=>{}}),sR=({validateMessages:e,onFormChange:t,onFormFinish:n,children:r})=>{const o=a.useContext(Dy),s=a.useRef({});return a.createElement(Dy.Provider,{value:{...o,validateMessages:{...o.validateMessages,...e},triggerFormChange:(i,l)=>{t&&t(i,{changedFields:l,forms:s.current}),o.triggerFormChange(i,l)},triggerFormFinish:(i,l)=>{n&&n(i,{values:l,forms:s.current}),o.triggerFormFinish(i,l)},registerForm:(i,l)=>{i&&(s.current={...s.current,[i]:l}),o.registerForm(i,l)},unregisterForm:i=>{const l={...s.current};delete l[i],s.current=l,o.unregisterForm(i)}}},r)};function Fy(){return Fy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{name:n,initialValues:r,fields:o,form:s,preserve:i,children:l,component:c="form",validateMessages:u,validateTrigger:d="onChange",onValuesChange:m,onFieldsChange:f,onFinish:p,onFinishFailed:y,clearOnDestroy:b,...x}=e,v=a.useRef(null),g=a.useContext(Dy),[h]=xx(s),{useSubscribe:$,setInitialValues:C,setCallbacks:N,setValidateMessages:S,setPreserve:E,destroyForm:w}=h.getInternalHooks(fi);a.useImperativeHandle(t,()=>({...h,nativeElement:v.current})),a.useEffect(()=>(g.registerForm(n,h),()=>{g.unregisterForm(n)}),[g,h,n]),S({...g.validateMessages,...u}),N({onValuesChange:m,onFieldsChange:(F,...L)=>{g.triggerFormChange(n,F),f&&f(F,...L)},onFinish:F=>{g.triggerFormFinish(n,F),p&&p(F)},onFinishFailed:y}),E(i);const R=a.useRef(null);C(r,!R.current),R.current||(R.current=!0),a.useEffect(()=>()=>w(b),[]);let P;const T=typeof l=="function";if(T){const F=h.getFieldsValue(!0);P=l(F,h)}else P=l;$(!T);const M=a.useRef(null);a.useEffect(()=>{jk(M.current||[],o||[])||h.setFields(o||[]),M.current=o},[o,h]);const z=a.useMemo(()=>({...h,validateTrigger:d}),[h,d]),B=a.createElement(Rc.Provider,{value:null},a.createElement(Ni.Provider,{value:z},P));return c===!1?B:a.createElement(c,Fy({},x,{ref:v,onSubmit:F=>{F.preventDefault(),F.stopPropagation(),h.submit()},onReset:F=>{var L;F.preventDefault(),h.resetFields(),(L=x.onReset)==null||L.call(x,F)}}),B)};function tg(e){try{return JSON.stringify(e)}catch{return Math.random()}}function iR(...e){const[t,n={}]=e,r=tk(n)?{form:n}:n,o=r.form,[s,i]=a.useState(()=>typeof t=="function"?t({}):void 0),l=a.useMemo(()=>tg(s),[s]),c=a.useRef(l);c.current=l;const u=a.useContext(Ni),d=o||u,m=d&&d._init,{getFieldsValue:f,getInternalHooks:p}=d,{registerWatch:y}=p(fi),b=vt((v,g)=>{const h=r.preserve?g??f(!0):v??f(),$=typeof t=="function"?t(h):Kn(h,yn(t));tg(s)!==tg($)&&i($)}),x=typeof t=="function"?t:JSON.stringify(t);return a.useEffect(()=>{m&&b()},[m,x]),a.useEffect(()=>m?y((g,h)=>{b(g,h)}):void 0,[m]),s}const Hk=a.forwardRef(Fk),el=Hk;el.FormProvider=sR;el.Field=bx;el.List=oR;el.useForm=xx;el.useWatch=iR;const _o=a.createContext({labelAlign:"right",layout:"horizontal",itemRef:()=>{}}),aR=a.createContext(null),lR=e=>{const t=Dt(e,["prefixCls"]);return a.createElement(sR,{...t})},$x=a.createContext({prefixCls:""}),Hn=a.createContext({}),cR=({children:e,status:t,override:n})=>{const r=a.useContext(Hn),o=a.useMemo(()=>{const s={...r};return n&&delete s.isFormItemInput,t&&(delete s.status,delete s.hasFeedback,delete s.feedbackIcon),s},[t,n,r]);return a.createElement(Hn.Provider,{value:o},e)},uR=a.createContext(void 0),Ri=e=>{const{space:t,form:n,children:r}=e;if(!$n(r))return null;let o=r;return n&&(o=J.createElement(cR,{override:!0,status:!0},o)),t&&(o=J.createElement(GB,null,o)),o},Vk=()=>lr()&&window.document.documentElement;function Wk(e,t,n){return a.useMemo(()=>({...{trap:t??!0,focusTriggerAfterClose:n??!0},...e}),[e,t,n])}const Rm=e=>{const{prefixCls:t,className:n,style:r,size:o,shape:s}=e,i=H({[`${t}-lg`]:o==="large",[`${t}-sm`]:o==="small"}),l=H({[`${t}-circle`]:s==="circle",[`${t}-square`]:s==="square",[`${t}-round`]:s==="round"}),c=a.useMemo(()=>Rn(o)?{width:o,height:o,lineHeight:`${o}px`}:{},[o]);return a.createElement("span",{className:H(t,i,l,n),style:{...c,...r}})},Kk=new Ht("ant-skeleton-loading",{"0%":{backgroundPosition:"100% 50%"},"100%":{backgroundPosition:"0 50%"}}),Sx=e=>({height:e,lineHeight:G(e)}),zs=e=>({width:e,...Sx(e)}),Uk=e=>({background:e.skeletonLoadingBackground,backgroundSize:"400% 100%",animationName:Kk,animationDuration:e.skeletonLoadingMotionDuration,animationTimingFunction:"ease",animationIterationCount:"infinite"}),ng=(e,t)=>({width:t(e).mul(5).equal(),minWidth:t(e).mul(5).equal(),...Sx(e)}),qk=e=>{const{skeletonAvatarCls:t,gradientFromColor:n,controlHeight:r,controlHeightLG:o,controlHeightSM:s}=e;return{[t]:{display:"inline-block",verticalAlign:"top",background:n,...zs(r)},[`${t}${t}-circle`]:{borderRadius:"50%"},[`${t}${t}-lg`]:{...zs(o)},[`${t}${t}-sm`]:{...zs(s)}}},Gk=e=>{const{controlHeight:t,borderRadiusSM:n,skeletonInputCls:r,controlHeightLG:o,controlHeightSM:s,gradientFromColor:i,calc:l}=e;return{[r]:{display:"inline-block",verticalAlign:"top",background:i,borderRadius:n,...ng(t,l)},[`${r}-lg`]:{...ng(o,l)},[`${r}-sm`]:{...ng(s,l)}}},dR=e=>{const{gradientFromColor:t,borderRadiusSM:n,imageSizeBase:r,calc:o}=e;return{display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"middle",background:t,borderRadius:n,...zs(o(r).mul(2).equal())}},Xk=e=>({[e.skeletonNodeCls]:{...dR(e)}}),Yk=e=>{const{skeletonImageCls:t,imageSizeBase:n,calc:r}=e;return{[t]:{...dR(e),[`${t}-path`]:{fill:"#bfbfbf"},[`${t}-svg`]:{...zs(n),maxWidth:r(n).mul(4).equal(),maxHeight:r(n).mul(4).equal()},[`${t}-svg${t}-svg-circle`]:{borderRadius:"50%"}},[`${t}${t}-circle`]:{borderRadius:"50%"}}},rg=(e,t,n)=>{const{skeletonButtonCls:r}=e;return{[`${n}${r}-circle`]:{width:t,minWidth:t,borderRadius:"50%"},[`${n}${r}-round`]:{borderRadius:t}}},og=(e,t)=>({width:t(e).mul(2).equal(),minWidth:t(e).mul(2).equal(),...Sx(e)}),Qk=e=>{const{borderRadiusSM:t,skeletonButtonCls:n,controlHeight:r,controlHeightLG:o,controlHeightSM:s,gradientFromColor:i,calc:l}=e;return{[n]:{display:"inline-block",verticalAlign:"top",background:i,borderRadius:t,width:l(r).mul(2).equal(),minWidth:l(r).mul(2).equal(),...og(r,l)},...rg(e,r,n),[`${n}-lg`]:{...og(o,l)},...rg(e,o,`${n}-lg`),[`${n}-sm`]:{...og(s,l)},...rg(e,s,`${n}-sm`)}},Jk=e=>{const{componentCls:t,skeletonAvatarCls:n,skeletonTitleCls:r,skeletonParagraphCls:o,skeletonButtonCls:s,skeletonInputCls:i,skeletonNodeCls:l,skeletonImageCls:c,controlHeight:u,controlHeightLG:d,controlHeightSM:m,gradientFromColor:f,padding:p,marginSM:y,borderRadius:b,titleHeight:x,blockRadius:v,paragraphLiHeight:g,controlHeightXS:h,paragraphMarginTop:$}=e;return{[t]:{display:"table",width:"100%",[`${t}-header`]:{display:"table-cell",paddingInlineEnd:p,verticalAlign:"top",[n]:{display:"inline-block",verticalAlign:"top",background:f,...zs(u)},[`${n}-circle`]:{borderRadius:"50%"},[`${n}-lg`]:{...zs(d)},[`${n}-sm`]:{...zs(m)}},[`${t}-section`]:{display:"table-cell",width:"100%",verticalAlign:"top",[r]:{width:"100%",height:x,background:f,borderRadius:v,[`+ ${o}`]:{marginBlockStart:m}},[o]:{padding:0,"> li":{width:"100%",height:g,listStyle:"none",background:f,borderRadius:v,"+ li":{marginBlockStart:h}}},[`${o}> li:last-child:not(:first-child):not(:nth-child(2))`]:{width:"61%"}},[`&-round ${t}-section`]:{[`${r}, ${o} > li`]:{borderRadius:b}}},[`${t}-with-avatar ${t}-section`]:{[r]:{marginBlockStart:y,[`+ ${o}`]:{marginBlockStart:$}}},[`${t}${t}-element`]:{display:"inline-block",width:"auto",...Qk(e),...qk(e),...Gk(e),...Xk(e),...Yk(e)},[`${t}${t}-block`]:{width:"100%",[s]:{width:"100%"},[i]:{width:"100%"}},[`${t}${t}-active`]:{[` + ${r}, + ${o} > li, + ${n}, + ${s}, + ${i}, + ${l}, + ${c} + `]:{...Uk(e)}}}},Zk=e=>{const{colorFillContent:t,colorFill:n}=e,r=t,o=n;return{color:r,colorGradientEnd:o,gradientFromColor:r,gradientToColor:o,titleHeight:e.controlHeight/2,blockRadius:e.borderRadiusSM,paragraphMarginTop:e.marginLG+e.marginXXS,paragraphLiHeight:e.controlHeight/2}},Zc=Tt("Skeleton",e=>{const{componentCls:t,calc:n}=e,r=Rt(e,{skeletonAvatarCls:`${t}-avatar`,skeletonTitleCls:`${t}-title`,skeletonParagraphCls:`${t}-paragraph`,skeletonButtonCls:`${t}-button`,skeletonInputCls:`${t}-input`,skeletonNodeCls:`${t}-node`,skeletonImageCls:`${t}-image`,imageSizeBase:n(e.controlHeight).mul(1.5).equal(),borderRadius:100,skeletonLoadingBackground:`linear-gradient(90deg, ${e.gradientFromColor} 25%, ${e.gradientToColor} 37%, ${e.gradientFromColor} 63%)`,skeletonLoadingMotionDuration:"1.4s"});return Jk(r)},Zk,{deprecatedTokens:[["color","gradientFromColor"],["colorGradientEnd","gradientToColor"]]}),eA=e=>{const{prefixCls:t,className:n,classNames:r,rootClassName:o,active:s,style:i,styles:l,shape:c="circle",size:u,...d}=e,{getPrefixCls:m}=a.useContext(ct),f=m("skeleton",t),[p,y]=Zc(f),b=Cn(v=>u??v),x=H(f,`${f}-element`,{[`${f}-active`]:s},r==null?void 0:r.root,n,o,p,y);return a.createElement("div",{className:x,style:l==null?void 0:l.root},a.createElement(Rm,{prefixCls:`${f}-avatar`,className:r==null?void 0:r.content,style:{...l==null?void 0:l.content,...i},shape:c,size:b,...d}))},tA=e=>{const{prefixCls:t,className:n,rootClassName:r,classNames:o,active:s,style:i,styles:l,block:c=!1,size:u,...d}=e,{getPrefixCls:m}=a.useContext(ct),f=m("skeleton",t),[p,y]=Zc(f),b=Cn(v=>u??v),x=H(f,`${f}-element`,{[`${f}-active`]:s,[`${f}-block`]:c},o==null?void 0:o.root,n,r,p,y);return a.createElement("div",{className:x,style:l==null?void 0:l.root},a.createElement(Rm,{prefixCls:`${f}-button`,className:o==null?void 0:o.content,style:{...l==null?void 0:l.content,...i},size:b,...d}))},fR=e=>{const{prefixCls:t,className:n,classNames:r,rootClassName:o,internalClassName:s,style:i,styles:l,active:c,children:u}=e,{getPrefixCls:d}=a.useContext(ct),m=d("skeleton",t),[f,p]=Zc(m),y=H(m,`${m}-element`,{[`${m}-active`]:c},f,r==null?void 0:r.root,n,o,p);return a.createElement("div",{className:y,style:l==null?void 0:l.root},a.createElement("div",{className:H(r==null?void 0:r.content,s||`${m}-node`),style:{...l==null?void 0:l.content,...i}},u))},nA=e=>{const{getPrefixCls:t}=a.useContext(ct),n=t("skeleton",e.prefixCls);return a.createElement(fR,{...e,internalClassName:`${n}-image`},a.createElement("svg",{viewBox:"0 0 1098 1024",xmlns:"http://www.w3.org/2000/svg",className:`${n}-image-svg`},a.createElement("title",null,"Image placeholder"),a.createElement("path",{d:"M365.7 329.1q0 45.8-32 77.7t-77.7 32-77.7-32-32-77.7 32-77.6 77.7-32 77.7 32 32 77.6M951 548.6v256H146.3V694.9L329 512l91.5 91.4L713 311zm54.8-402.3H91.4q-7.4 0-12.8 5.4T73 164.6v694.8q0 7.5 5.5 12.9t12.8 5.4h914.3q7.5 0 12.9-5.4t5.4-12.9V164.6q0-7.5-5.4-12.9t-12.9-5.4m91.4 18.3v694.8q0 37.8-26.8 64.6t-64.6 26.9H91.4q-37.7 0-64.6-26.9T0 859.4V164.6q0-37.8 26.8-64.6T91.4 73h914.3q37.8 0 64.6 26.9t26.8 64.6",className:`${n}-image-path`})))},rA=e=>{const{prefixCls:t,className:n,classNames:r,rootClassName:o,active:s,block:i,style:l,styles:c,size:u,...d}=e,{getPrefixCls:m}=a.useContext(ct),f=m("skeleton",t),[p,y]=Zc(f),b=Cn(v=>u??v),x=H(f,`${f}-element`,{[`${f}-active`]:s,[`${f}-block`]:i},r==null?void 0:r.root,n,o,p,y);return a.createElement("div",{className:x,style:c==null?void 0:c.root},a.createElement(Rm,{prefixCls:`${f}-input`,className:r==null?void 0:r.content,style:{...c==null?void 0:c.content,...l},size:b,...d}))},oA=(e,t)=>{const{width:n,rows:r=2}=t;if(Array.isArray(n))return n[e];if(r-1===e)return n},sA=e=>{const{prefixCls:t,className:n,style:r,rows:o=0}=e,s=Array.from({length:o}).map((i,l)=>a.createElement("li",{key:l,style:{width:oA(l,e)}}));return a.createElement("ul",{className:H(t,n),style:r},s)},iA=({prefixCls:e,className:t,width:n,style:r})=>a.createElement("h3",{className:H(e,t),style:{width:n,...r}});function sg(e){return dt(e)?e:{}}function aA(e,t){return e&&!t?{size:"large",shape:"square"}:{size:"large",shape:"circle"}}function lA(e,t){return!e&&t?{width:"38%"}:e&&t?{width:"50%"}:{}}function cA(e,t){const n={};return(!e||!t)&&(n.width="61%"),!e&&t?n.rows=3:n.rows=2,n}const Ai=e=>{const{prefixCls:t,loading:n,className:r,rootClassName:o,classNames:s,style:i,styles:l,children:c,avatar:u=!1,title:d=!0,paragraph:m=!0,active:f,round:p}=e,{getPrefixCls:y,direction:b,className:x,style:v,classNames:g,styles:h}=Pt("skeleton"),$=y("skeleton",t),[C,N]=Zc($),S={...e,avatar:u,title:d,paragraph:m},E=Mt(v),w=Mt(i),[R,P]=Ot([g,s],[h,E,l,w],{props:S});if(n||!("loading"in e)){const T=!!u,M=!!d,z=!!m;let B;if(T){const j={className:R.avatar,prefixCls:`${$}-avatar`,...aA(M,z),...sg(u),style:P.avatar};B=a.createElement("div",{className:H(R.header,`${$}-header`),style:P.header},a.createElement(Rm,{...j}))}let F;if(M||z){let j;if(M){const A={className:R.title,prefixCls:`${$}-title`,...lA(T,z),...sg(d),style:P.title};j=a.createElement(iA,{...A})}let O;if(z){const A={className:R.paragraph,prefixCls:`${$}-paragraph`,...cA(T,M),...sg(m),style:P.paragraph};O=a.createElement(sA,{...A})}F=a.createElement("div",{className:H(R.section,`${$}-section`),style:P.section},j,O)}const L=H($,{[`${$}-with-avatar`]:T,[`${$}-active`]:f,[`${$}-rtl`]:b==="rtl",[`${$}-round`]:p},R.root,x,r,o,C,N);return a.createElement("div",{className:L,style:P.root},B,F)}return c??null};Ai.Button=tA;Ai.Avatar=eA;Ai.Input=rA;Ai.Image=nA;Ai.Node=fR;function aC(){}const uA=a.createContext({add:aC,remove:aC});function dA(e){const t=a.useContext(uA),n=a.useRef(null);return vt(o=>{if(o){const s=e?o.querySelector(e):o;s&&(t.add(s),n.current=s)}else t.remove(n.current)})}const lC=()=>{const{cancelButtonProps:e,cancelTextLocale:t,onCancel:n}=a.useContext(Jc);return J.createElement(Xe,{onClick:n,...e},t)},cC=()=>{const{confirmLoading:e,okButtonProps:t,okType:n,okTextLocale:r,onOk:o}=a.useContext(Jc);return J.createElement(Xe,{...px(n),loading:e,onClick:o,...t},r)};function mR(e,t){return J.createElement("span",{className:`${e}-close-x`},t||J.createElement(Us,{className:`${e}-close-icon`}))}const pR=e=>{const{okText:t,okType:n="primary",cancelText:r,confirmLoading:o,onOk:s,onCancel:i,okButtonProps:l,cancelButtonProps:c,footer:u}=e,[d]=Ar("Modal",JP()),m=t||(d==null?void 0:d.okText),f=r||(d==null?void 0:d.cancelText),p=J.useMemo(()=>({confirmLoading:o,okButtonProps:l,cancelButtonProps:c,okTextLocale:m,cancelTextLocale:f,okType:n,onOk:s,onCancel:i}),[o,l,c,m,f,n,s,i]);let y;return bt(u)||typeof u>"u"?(y=J.createElement(J.Fragment,null,J.createElement(lC,null),J.createElement(cC,null)),bt(u)&&(y=u(y,{OkBtn:cC,CancelBtn:lC})),y=J.createElement(QN,{value:p},y)):y=u,J.createElement(ix,{disabled:!1},y)},fA=e=>{const{componentCls:t}=e;return{[t]:{display:"flex",flexFlow:"row wrap",minWidth:0,"&::before, &::after":{display:"flex"},"&-no-wrap":{flexWrap:"nowrap"},"&-start":{justifyContent:"flex-start"},"&-center":{justifyContent:"center"},"&-end":{justifyContent:"flex-end"},"&-space-between":{justifyContent:"space-between"},"&-space-around":{justifyContent:"space-around"},"&-space-evenly":{justifyContent:"space-evenly"},"&-top":{alignItems:"flex-start"},"&-middle":{alignItems:"center"},"&-bottom":{alignItems:"flex-end"}}}},mA=e=>{const{componentCls:t}=e;return{[t]:{position:"relative",maxWidth:"100%",minHeight:1}}},pA=(e,t)=>{const{componentCls:n,gridColumns:r,antCls:o}=e,[s,i]=rn(o,"grid"),[,l]=rn(o,"col"),c={};for(let u=r;u>=0;u--)u===0?(c[`${n}${t}-${u}`]={display:"none"},c[`${n}-push-${u}`]={insetInlineStart:"auto"},c[`${n}-pull-${u}`]={insetInlineEnd:"auto"},c[`${n}${t}-push-${u}`]={insetInlineStart:"auto"},c[`${n}${t}-pull-${u}`]={insetInlineEnd:"auto"},c[`${n}${t}-offset-${u}`]={marginInlineStart:0},c[`${n}${t}-order-${u}`]={order:0}):(c[`${n}${t}-${u}`]=[{[s("display")]:"block",display:"block"},{display:i("display"),flex:`0 0 ${u/r*100}%`,maxWidth:`${u/r*100}%`}],c[`${n}${t}-push-${u}`]={insetInlineStart:`${u/r*100}%`},c[`${n}${t}-pull-${u}`]={insetInlineEnd:`${u/r*100}%`},c[`${n}${t}-offset-${u}`]={marginInlineStart:`${u/r*100}%`},c[`${n}${t}-order-${u}`]={order:u});return c[`${n}${t}-flex`]={flex:l(`${t.replace(/-/,"")}-flex`)},c},Hy=(e,t)=>pA(e,t),gA=(e,t,n)=>({[`@media (min-width: ${G(t)})`]:{...Hy(e,n)}}),hA=()=>({}),yA=()=>({}),vA=Tt("Grid",fA,hA),gR=e=>({xs:e.screenXSMin,sm:e.screenSMMin,md:e.screenMDMin,lg:e.screenLGMin,xl:e.screenXLMin,xxl:e.screenXXLMin,xxxl:e.screenXXXLMin}),bA=Tt("Grid",e=>{const t=Rt(e,{gridColumns:24}),n=gR(t);return delete n.xs,[mA(t),Hy(t,""),Hy(t,"-xs"),Object.keys(n).map(r=>gA(t,n[r],`-${r}`)).reduce((r,o)=>({...r,...o}),{})]},yA);function uC(e){return{position:e,inset:0}}const xA=e=>{const{componentCls:t,antCls:n}=e;return[{[`${t}-root`]:{[`${t}${n}-zoom-enter, ${t}${n}-zoom-appear`]:{transform:"none",opacity:0,animationDuration:e.motionDurationSlow,userSelect:"none"},[`${t}${n}-zoom-leave ${t}-container`]:{pointerEvents:"none"},[`${t}-mask`]:{...uC("fixed"),zIndex:e.zIndexPopupBase,height:"100%",backgroundColor:e.colorBgMask,pointerEvents:"none",[`&${t}-mask-blur`]:{backdropFilter:"blur(4px)"},[`${t}-hidden`]:{display:"none"}},[`${t}-wrap`]:{...uC("fixed"),zIndex:e.zIndexPopupBase,overflow:"auto",outline:0,WebkitOverflowScrolling:"touch"}}},{[`${t}-root`]:HN(e)}]},$A=e=>{const{componentCls:t,motionDurationMid:n}=e;return[{[`${t}-root`]:{[`${t}-wrap-rtl`]:{direction:"rtl"},[`${t}-centered`]:{textAlign:"center","&::before":{display:"inline-block",width:0,height:"100%",verticalAlign:"middle",content:'""'},[t]:{top:0,display:"inline-block",paddingBottom:0,textAlign:"start",verticalAlign:"middle"}},[`@media (max-width: ${e.screenSMMax}px)`]:{[t]:{maxWidth:"calc(100vw - 16px)",margin:`${G(e.marginXS)} auto`},[`${t}-centered`]:{[t]:{flex:1}}}}},{[t]:{...Ft(e),pointerEvents:"none",position:"relative",top:100,width:"auto",maxWidth:`calc(100vw - ${G(e.calc(e.margin).mul(2).equal())})`,margin:"0 auto","&:focus-visible":{borderRadius:e.borderRadiusLG,...jr(e)},[`${t}-title`]:{margin:0,color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.titleFontSize,lineHeight:e.titleLineHeight,wordWrap:"break-word"},[`${t}-container`]:{position:"relative",backgroundColor:e.contentBg,backgroundClip:"padding-box",border:0,borderRadius:e.borderRadiusLG,boxShadow:e.boxShadow,pointerEvents:"auto",padding:e.contentPadding},[`${t}-close`]:{position:"absolute",top:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),insetInlineEnd:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),zIndex:e.calc(e.zIndexPopupBase).add(10).equal(),padding:0,color:e.modalCloseIconColor,fontWeight:e.fontWeightStrong,lineHeight:1,textDecoration:"none",background:"transparent",borderRadius:e.borderRadiusSM,width:e.modalCloseBtnSize,height:e.modalCloseBtnSize,border:0,outline:0,cursor:"pointer",transition:["color","background-color"].map(r=>`${r} ${n}`).join(", "),"&-x":{display:"flex",fontSize:e.fontSizeLG,fontStyle:"normal",lineHeight:G(e.modalCloseBtnSize),justifyContent:"center",textTransform:"none",textRendering:"auto"},"&:disabled":{pointerEvents:"none"},"&:hover":{color:e.modalCloseIconHoverColor,backgroundColor:e.colorBgTextHover,textDecoration:"none"},"&:active":{backgroundColor:e.colorBgTextActive},...Br(e)},[`${t}-header`]:{color:e.colorText,background:e.headerBg,borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0`,marginBottom:e.headerMarginBottom,padding:e.headerPadding,borderBottom:e.headerBorderBottom},[`${t}-body`]:{fontSize:e.fontSize,lineHeight:e.lineHeight,wordWrap:"break-word",padding:e.bodyPadding,[`${t}-body-skeleton`]:{width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",margin:`${G(e.margin)} auto`}},[`${t}-footer`]:{textAlign:"end",background:e.footerBg,marginTop:e.footerMarginTop,padding:e.footerPadding,borderTop:e.footerBorderTop,borderRadius:e.footerBorderRadius,[`> ${e.antCls}-btn + ${e.antCls}-btn`]:{marginInlineStart:e.marginXS}},[`${t}-open`]:{overflow:"hidden"}}},{[`${t}-pure-panel`]:{top:"auto",padding:0,display:"flex",flexDirection:"column",[`${t}-container, + ${t}-body, + ${t}-confirm-body-wrapper`]:{display:"flex",flexDirection:"column",flex:"auto"},[`${t}-confirm-body`]:{marginBottom:"auto"}}}]},SA=e=>{const{componentCls:t}=e;return{[`${t}-root`]:{[`${t}-wrap-rtl`]:{direction:"rtl",[`${t}-confirm-body`]:{direction:"rtl"}}}}},CA=e=>{const{componentCls:t}=e,n=gR(e),r={...n};delete r.xs;const o=`--${t.replace(".","")}-`,s=Object.keys(r).map(i=>({[`@media (min-width: ${G(r[i])})`]:{width:`var(${o}${i}-width)`}}));return{[`${t}-root`]:{[t]:[].concat($t(Object.keys(n).map((i,l)=>{const c=Object.keys(n)[l-1];return c?{[`${o}${i}-width`]:`var(${o}${c}-width)`}:null})),[{width:`var(${o}xs-width)`}],$t(s))}}},hR=e=>{const t=e.padding,n=e.fontSizeHeading5,r=e.lineHeightHeading5;return Rt(e,{modalHeaderHeight:e.calc(e.calc(r).mul(n).equal()).add(e.calc(t).mul(2).equal()).equal(),modalFooterBorderColorSplit:e.colorSplit,modalFooterBorderStyle:e.lineType,modalFooterBorderWidth:e.lineWidth,modalCloseIconColor:e.colorIcon,modalCloseIconHoverColor:e.colorIconHover,modalCloseBtnSize:e.controlHeight,modalConfirmIconSize:e.fontHeight,modalTitleHeight:e.calc(e.titleFontSize).mul(e.titleLineHeight).equal()})},yR=e=>({footerBg:"transparent",headerBg:"transparent",titleLineHeight:e.lineHeightHeading5,titleFontSize:e.fontSizeHeading5,contentBg:e.colorBgElevated,titleColor:e.colorTextHeading,contentPadding:e.wireframe?0:`${G(e.paddingMD)} ${G(e.paddingContentHorizontalLG)}`,headerPadding:e.wireframe?`${G(e.padding)} ${G(e.paddingLG)}`:0,headerBorderBottom:e.wireframe?`${G(e.lineWidth)} ${e.lineType} ${e.colorSplit}`:"none",headerMarginBottom:e.wireframe?0:e.marginXS,bodyPadding:e.wireframe?e.paddingLG:0,footerPadding:e.wireframe?`${G(e.paddingXS)} ${G(e.padding)}`:0,footerBorderTop:e.wireframe?`${G(e.lineWidth)} ${e.lineType} ${e.colorSplit}`:"none",footerBorderRadius:e.wireframe?`0 0 ${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)}`:0,footerMarginTop:e.wireframe?0:e.marginSM,confirmBodyPadding:e.wireframe?`${G(e.padding*2)} ${G(e.padding*2)} ${G(e.paddingLG)}`:0,confirmIconMarginInlineEnd:e.wireframe?e.margin:e.marginSM,confirmBtnsMarginTop:e.wireframe?e.marginLG:e.marginSM,mask:!0}),vR=Tt("Modal",e=>{const t=hR(e);return[$A(t),SA(t),xA(t),Yc(t,"zoom"),CA(t)]},yR,{unitless:{titleLineHeight:!0}});let Vy;const wA=e=>{Vy={x:e.pageX,y:e.pageY},setTimeout(()=>{Vy=null},100)};Vk()&&document.documentElement.addEventListener("click",wA,!0);const bR=e=>{const{prefixCls:t,className:n,rootClassName:r,open:o,wrapClassName:s,centered:i,getContainer:l,style:c,width:u=520,footer:d,classNames:m,styles:f,children:p,loading:y,confirmLoading:b,zIndex:x,mousePosition:v,onOk:g,onCancel:h,okButtonProps:$,cancelButtonProps:C,destroyOnHidden:N,destroyOnClose:S,panelRef:E=null,closable:w,mask:R,modalRender:P,maskClosable:T,_semanticOmit:M,scrollLock:z,focusTriggerAfterClose:B,focusable:F,_renderSemanticContent:L,...j}=e,{getPopupContainer:O,getPrefixCls:A,direction:k,className:_,style:D,classNames:V,styles:W,centered:K,cancelButtonProps:q,okButtonProps:Y,mask:ee,focusable:ie}=Pt("modal"),{modal:ae}=a.useContext(ct),[U,Q]=a.useMemo(()=>typeof w=="boolean"?[void 0,void 0]:[w==null?void 0:w.afterClose,w==null?void 0:w.onClose],[w]),Z=A("modal",t),ne=A(),[oe,le,re]=Qj(R,ee,Z,T),X=Wk({...ie,...F},oe,B),se=ce=>{b||(h==null||h(ce),Q==null||Q())},ge=ce=>{g==null||g(ce),Q==null||Q()},de=on(Z),[Se,ue]=vR(Z,de),be=H(s,{[`${Z}-centered`]:i??K,[`${Z}-wrap-rtl`]:k==="rtl"}),Ne=d!==null&&!y?a.createElement(pR,{...e,okButtonProps:{...Y,...$},onOk:ge,cancelButtonProps:{...q,...C},onCancel:se}):null,[we,ze,he,ke]=NN(Ua(e),Ua(ae),{closable:!0,closeIcon:a.createElement(Us,{className:`${Z}-close-icon`}),closeIconRender:ce=>mR(Z,ce)}),Oe=we?{disabled:he,closeIcon:ze,afterClose:U,...ke}:!1,Ce=P?ce=>a.createElement("div",{className:`${Z}-render`},P(ce)):void 0,Me=`.${Z}-${P?"render":"container"}`,xe=dA(Me),Ee=Tn(E,xe),[Ve,qe]=Xc("Modal",x),me={...e,width:u,panelRef:E,focusTriggerAfterClose:X.focusTriggerAfterClose,focusable:X,mask:oe,maskClosable:re,zIndex:Ve},[Re,Te]=Ot([V,m,le],[W,f],{props:me}),Ue=M?Dt(Re,M):Re,Ge=M?Dt(Te,M):Te,Fe=L?L({classNames:Re,styles:Te}):p,[et,ve]=a.useMemo(()=>dt(u)?[void 0,u]:[u,void 0],[u]),je=a.useMemo(()=>{const ce={};return ve&&Object.keys(ve).forEach(Pe=>{const pe=ve[Pe];bn(pe)&&(ce[`--${Z}-${Pe}-width`]=Rn(pe)?`${pe}px`:pe)}),ce},[Z,ve]);return a.createElement(Ri,{form:!0,space:!0},a.createElement(xm.Provider,{value:qe},a.createElement(ek,{width:et,...j,zIndex:Ve,getContainer:l===void 0?O:l,prefixCls:Z,rootClassName:H(Se,r,ue,de,Ue.root),rootStyle:Ge.root,footer:Ne,visible:o,mousePosition:v??Vy,onClose:se,closable:Oe,closeIcon:ze,transitionName:ks(ne,"zoom",e.transitionName),maskTransitionName:ks(ne,"fade",e.maskTransitionName),mask:oe,maskClosable:re,scrollLock:z,className:H(Se,n,_),style:{...D,...c,...je},classNames:{...Ue,wrapper:H(Ue.wrapper,be)},styles:Ge,panelRef:Ee,destroyOnHidden:N??S,modalRender:Ce,focusTriggerAfterClose:X.focusTriggerAfterClose,focusTrap:X.trap},y?a.createElement(Ai,{active:!0,title:!1,paragraph:{rows:4},className:`${Z}-body-skeleton`}):Fe)))},EA=e=>{const{componentCls:t,titleFontSize:n,titleLineHeight:r,modalConfirmIconSize:o,fontSize:s,lineHeight:i,modalTitleHeight:l,fontHeight:c,confirmBodyPadding:u}=e,d=`${t}-confirm`;return{[d]:{"&-rtl":{direction:"rtl"},[`${e.antCls}-modal-header`]:{display:"none"},[`${d}-body-wrapper`]:{...Ls()},[`&${t} ${t}-body`]:{padding:u},[`${d}-body`]:{display:"flex",flexWrap:"nowrap",alignItems:"start",[`> ${e.iconCls}`]:{flex:"none",fontSize:o,marginInlineEnd:e.confirmIconMarginInlineEnd,marginTop:e.calc(e.calc(c).sub(o).equal()).div(2).equal()},[`&-has-title > ${e.iconCls}`]:{marginTop:e.calc(e.calc(l).sub(o).equal()).div(2).equal()}},[`${d}-paragraph`]:{display:"flex",flexDirection:"column",flex:"auto",rowGap:e.marginXS,maxWidth:`calc(100% - ${G(e.marginSM)})`},[`${d}-body-no-icon ${d}-paragraph`]:{maxWidth:"100%"},[`${e.iconCls} + ${d}-paragraph`]:{maxWidth:`calc(100% - ${G(e.calc(e.modalConfirmIconSize).add(e.marginSM).equal())})`},[`${d}-title`]:{color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:n,lineHeight:r},[`${d}-container`]:{color:e.colorText,fontSize:s,lineHeight:i},[`${d}-btns`]:{textAlign:"end",marginTop:e.confirmBtnsMarginTop,[`${e.antCls}-btn + ${e.antCls}-btn`]:{marginBottom:0,marginInlineStart:e.marginXS}}},[`${d}-error ${d}-body > ${e.iconCls}`]:{color:e.colorError},[`${d}-warning ${d}-body > ${e.iconCls}, + ${d}-confirm ${d}-body > ${e.iconCls}`]:{color:e.colorWarning},[`${d}-info ${d}-body > ${e.iconCls}`]:{color:e.colorInfo},[`${d}-success ${d}-body > ${e.iconCls}`]:{color:e.colorSuccess}}},IA=Ks(["Modal","confirm"],e=>{const t=hR(e);return EA(t)},yR,{order:-1e3}),PA=["body"],xR=e=>{const{prefixCls:t,icon:n,okText:r,cancelText:o,confirmPrefixCls:s,type:i,okCancel:l,footer:c,locale:u,autoFocusButton:d,focusable:m,contentClassName:f,contentStyle:p,...y}=e,{infoIcon:b,successIcon:x,errorIcon:v,warningIcon:g}=Pt("modal");let h=n;if(n===void 0)switch(i){case"info":h=Ur(b,a.createElement(ym,null));break;case"success":h=Ur(x,a.createElement(Uc,null));break;case"error":h=Ur(v,a.createElement(Bi,null));break;default:h=Ur(g,a.createElement(Li,null))}const $=l??i==="confirm",C=a.useMemo(()=>{const L=(m==null?void 0:m.autoFocusButton)||d;return L||L===null?L:"ok"},[d,m==null?void 0:m.autoFocusButton]),[N]=Ar("Modal"),S=u||N,E=r||($?S==null?void 0:S.okText:S==null?void 0:S.justOkText),w=o||(S==null?void 0:S.cancelText),{closable:R}=y,{onClose:P}=dt(R)?R:{},T=a.useMemo(()=>({autoFocusButton:C,cancelTextLocale:w,okTextLocale:E,mergedOkCancel:$,onClose:P,...y}),[C,w,E,$,P,y]),M=a.createElement(a.Fragment,null,a.createElement(X1,null),a.createElement(Y1,null)),z=$n(e.title),B=$n(h),F=`${s}-body`;return a.createElement("div",{className:`${s}-body-wrapper`},a.createElement("div",{className:H(F,{[`${F}-has-title`]:z,[`${F}-no-icon`]:!B})},h,a.createElement("div",{className:`${s}-paragraph`},z&&a.createElement("span",{className:`${s}-title`},e.title),a.createElement("div",{className:H(`${s}-content`,f),style:p},e.content))),c===void 0||bt(c)?a.createElement(QN,{value:T},a.createElement("div",{className:`${s}-btns`},bt(c)?c(M,{OkBtn:Y1,CancelBtn:X1}):M)):c,a.createElement(IA,{prefixCls:t}))},NA=e=>{const{close:t,zIndex:n,maskStyle:r,direction:o,prefixCls:s,wrapClassName:i,rootPrefixCls:l,bodyStyle:c,closable:u=!1,onConfirm:d,styles:m,title:f,mask:p,maskClosable:y,okButtonProps:b,cancelButtonProps:x}=e,{cancelButtonProps:v,okButtonProps:g}=Pt("modal"),h=`${s}-confirm`,$=e.width||416,C=e.style||{},N=bt(m)?T=>({body:c,mask:r,...m(T)}):{body:c,mask:r,...m},S=Dt(e,["bodyStyle","maskStyle"]),E=H(h,`${h}-${e.type}`,{[`${h}-rtl`]:o==="rtl"},e.className),w=a.useMemo(()=>{const T=Py(p,y);return T.closable??(T.closable=!1),T},[p,y]),[,R]=Yn(),P=a.useMemo(()=>n!==void 0?n:R.zIndexPopupBase+ax,[n,R]);return a.createElement(bR,{...S,className:E,wrapClassName:H({[`${h}-centered`]:!!e.centered},i),onCancel:()=>{t==null||t({triggerCancel:!0}),d==null||d(!1)},title:f,footer:null,transitionName:ks(l||"","zoom",e.transitionName),maskTransitionName:ks(l||"","fade",e.maskTransitionName),mask:w,style:C,styles:N,width:$,zIndex:P,closable:u,_semanticOmit:PA,_renderSemanticContent:({classNames:T,styles:M})=>a.createElement(xR,{...e,confirmPrefixCls:h,okButtonProps:{...g,...b},cancelButtonProps:{...v,...x},contentClassName:T.body,contentStyle:M.body})})},$R=e=>{const{rootPrefixCls:t,iconPrefixCls:n,direction:r,theme:o}=e;return a.createElement(to,{prefixCls:t,iconPrefixCls:n,direction:r,theme:o},a.createElement(NA,{...e}))},mi=[];let SR="";function CR(){return SR}const RA=e=>{var u;const{prefixCls:t,getContainer:n,direction:r}=e,o=JP(),s=a.useContext(ct),i=CR()||s.getPrefixCls(),l=t||`${i}-modal`;let c=n;return c===!1&&(c=void 0),J.createElement($R,{...e,rootPrefixCls:i,prefixCls:l,iconPrefixCls:s.iconPrefixCls,theme:s.theme,direction:r??s.direction,locale:((u=s.locale)==null?void 0:u.Modal)??o,getContainer:c})};function eu(e){const t=gN(),n=document.createDocumentFragment();let r={...e,close:l,open:!0},o;function s(...u){var m;u.some(f=>f==null?void 0:f.triggerCancel)&&((m=e.onCancel)==null||m.call(e,()=>{},...u.slice(1)));for(let f=0;f{})}const i=u=>{clearTimeout(o),o=setTimeout(()=>{const d=t.getPrefixCls(void 0,CR()),m=t.getIconPrefixCls(),f=t.getTheme(),p=J.createElement(RA,{...u});Wb(J.createElement(to,{prefixCls:d,iconPrefixCls:m,theme:f},bt(t.holderRender)?t.holderRender(p):p),n)})};function l(...u){r={...r,open:!1,afterClose:()=>{bt(e.afterClose)&&e.afterClose(),s.apply(this,u)}},i(r)}function c(u){bt(u)?r=u(r):r={...r,...u},i(r)}return i(r),mi.push(l),{destroy:l,update:c}}function wR(e){return{...e,type:"warning"}}function ER(e){return{...e,type:"info"}}function IR(e){return{...e,type:"success"}}function PR(e){return{...e,type:"error"}}function NR(e){return{...e,type:"confirm"}}function TA({rootPrefixCls:e}){SR=e}const MA=a.forwardRef((e,t)=>{const{afterClose:n,config:r,...o}=e,[s,i]=a.useState(!0),[l,c]=a.useState(r),{direction:u,getPrefixCls:d}=a.useContext(ct),m=d("modal"),f=d(),p=()=>{var v;n(),(v=l.afterClose)==null||v.call(l)},y=(...v)=>{var h;i(!1),v.some($=>$==null?void 0:$.triggerCancel)&&((h=l.onCancel)==null||h.call(l,()=>{},...v.slice(1)))};a.useImperativeHandle(t,()=>({destroy:y,update:v=>{c(g=>{const h=bt(v)?v(g):v;return{...g,...h}})}}));const b=l.okCancel??l.type==="confirm",[x]=Ar("Modal",Zr.Modal);return a.createElement($R,{prefixCls:m,rootPrefixCls:f,...l,close:y,open:s,afterClose:p,okText:l.okText||(b?x==null?void 0:x.okText:x==null?void 0:x.justOkText),direction:l.direction||u,cancelText:l.cancelText||(x==null?void 0:x.cancelText),...o})});let dC=0;const OA=a.memo(a.forwardRef((e,t)=>{const[n,r]=Zj();return a.useImperativeHandle(t,()=>({patchElement:r}),[r]),a.createElement(a.Fragment,null,n)}));function RR(){const e=a.useRef(null),[t,n]=a.useState([]);a.useEffect(()=>{t.length&&($t(t).forEach(i=>{i()}),n([]))},[t]);const r=a.useCallback(s=>function(l){var b;dC+=1;const c=a.createRef();let u;const d=new Promise(x=>{u=x});let m=!1,f;const p=a.createElement(MA,{key:`modal-${dC}`,config:s(l),ref:c,afterClose:()=>{f==null||f()},isSilent:()=>m,onConfirm:x=>{u(x)}});return f=(b=e.current)==null?void 0:b.patchElement(p),f&&mi.push(f),{destroy:()=>{function x(){var v;(v=c.current)==null||v.destroy()}c.current?x():n(v=>[].concat($t(v),[x]))},update:x=>{function v(){var g;(g=c.current)==null||g.update(x)}c.current?v():n(g=>[].concat($t(g),[v]))},then:x=>(m=!0,d.then(x))}},[]);return[a.useMemo(()=>({info:r(ER),success:r(IR),error:r(PR),warning:r(wR),confirm:r(NR)}),[r]),a.createElement(OA,{key:"modal-holder",ref:e})]}const _A={info:a.createElement(ym,null),success:a.createElement(Uc,null),error:a.createElement(Bi,null),warning:a.createElement(Li,null),loading:a.createElement(ki,null)};function TR(e,t){return t===null||t===!1?null:t||a.createElement(Us,{className:`${e}-close-icon`})}const zA=4.5,jA="topRight",BA={offset:8},LA=({children:e,prefixCls:t})=>{const n=on(t),[r,o]=EB(t,n);return J.createElement(vN,{classNames:{list:H(r,o,n)}},e)},kA=(e,{prefixCls:t,key:n})=>J.createElement(LA,{prefixCls:t,key:n},e),AA=J.forwardRef((e,t)=>{const{top:n,bottom:r,prefixCls:o,getContainer:s,maxCount:i,rtl:l,onAllRemoved:c,stack:u,duration:d=zA,pauseOnHover:m=!0,showProgress:f}=e,{getPrefixCls:p,getPopupContainer:y,direction:b}=Pt("notification"),{notification:x}=a.useContext(ct),v=o||p("notification"),g=a.useMemo(()=>Rn(d)&&d>0?d:!1,[d]),[h,$]=Ot([x==null?void 0:x.classNames,e==null?void 0:e.classNames],[x==null?void 0:x.styles,e==null?void 0:e.styles],{props:e}),C=()=>CN(n,r),N=()=>H({[`${v}-rtl`]:l??b==="rtl"}),S=()=>Wj(v),E=SN(u,BA),[w,R]=$N({prefixCls:v,style:C,className:N,motion:S,closable:{closeIcon:TR(v)},duration:g,getContainer:()=>(s==null?void 0:s())||(y==null?void 0:y())||document.body,maxCount:i,pauseOnHover:m,showProgress:f,classNames:h,styles:$,onAllRemoved:c,renderNotifications:kA,stack:E});return J.useImperativeHandle(t,()=>({...w,prefixCls:v,notification:x})),R});function DA(e){const t=J.useRef(null);yo();const{notification:n}=J.useContext(ct);return[J.useMemo(()=>{const o=c=>{if(!t.current)return;const{open:u,prefixCls:d,notification:m}=t.current,f=(m==null?void 0:m.className)||{},p=(m==null?void 0:m.style)||{},y=`${d}-notice`,{title:b,message:x,description:v,icon:g,type:h,btn:$,actions:C,className:N,style:S,role:E="alert",closeIcon:w,closable:R,classNames:P={},styles:T={},...M}=c,z=b??x,B=$n(z),F=C??$,L=TR(y,Kj(w,e,m)),[j,O,,A]=PN(Ua({...e||{},...c}),Ua(n),{closable:!0,closeIcon:L}),k=j?{onClose:dt(R)?R.onClose:void 0,closeIcon:O,...A}:!1,_=Ka(P,{props:c}),D=Ka(T,{props:c}),V=g||(h?_A[h]:null),W=!g&&h?`${y}-icon-${h}`:void 0;return u({placement:(e==null?void 0:e.placement)??jA,...M,title:B?z:null,description:v,icon:V,actions:F,role:E,classNames:{..._,icon:H(W,_.icon)},styles:{...D,root:{...p,...D.root}},className:H({[`${y}-${h}`]:h},N,f),style:S,closable:k})},i={open:o,destroy:c=>{var u,d;c!==void 0?(u=t.current)==null||u.close(c):(d=t.current)==null||d.destroy()}};return["success","info","warning","error"].forEach(c=>{i[c]=u=>o({...u,type:c})}),i},[e,n]),J.createElement(AA,{key:"notification-holder",...e,ref:t})]}function FA(e){return DA(e)}const Wy=J.createContext({}),MR=J.createContext({message:{},notification:{},modal:{}}),HA=e=>{const{componentCls:t,colorText:n,fontSize:r,lineHeight:o,fontFamily:s}=e;return{[t]:{color:n,fontSize:r,lineHeight:o,fontFamily:s,[`&${t}-rtl`]:{direction:"rtl"}}}},VA=()=>({}),WA=Tt("App",HA,VA),KA=J.forwardRef((e,t)=>{const{prefixCls:n,children:r,className:o,rootClassName:s,message:i,notification:l,style:c,component:u="div"}=e,{direction:d,getPrefixCls:m,className:f,style:p}=Pt("app"),y=m("app",n),[b,x]=WA(y),v=H(b,y,o,s,x,{[`${y}-rtl`]:d==="rtl"}),g=a.useContext(Wy),h=J.useMemo(()=>({message:{...g.message,...i},notification:{...g.notification,...l}}),[i,l,g.message,g.notification]),[$,C]=AN(h.message),[N,S]=FA(h.notification),[E,w]=RR(),R=J.useMemo(()=>({message:$,notification:N,modal:E}),[$,N,E]);yo()(!(x&&u===!1),"usage","When using cssVar, ensure `component` is assigned a valid React component string."),yo()(!t||u!==!1,"usage","`ref` is not supported when `component` is `false`. Please provide a valid `component` instead.");const P=u===!1?J.Fragment:u,T={className:H(f,v),style:{...p,...c}};return J.createElement(MR.Provider,{value:R},J.createElement(Wy.Provider,{value:h},J.createElement(P,{...u===!1?void 0:{...T,ref:t}},w,C,S,r)))}),UA=()=>J.useContext(MR),Lo=KA;Lo.useApp=UA;function OR(e){return t=>a.createElement(to,{theme:{token:{motion:!1,zIndexPopupBase:0}}},a.createElement(e,{...t}))}const _R=(e,t,n,r,o)=>OR(i=>{const{prefixCls:l,style:c}=i,u=a.useRef(null),[d,m]=a.useState(0),[f,p]=a.useState(0),[y,b]=nn(!1,i.open),{getPrefixCls:x}=a.useContext(ct),v=x(r||"select",l);a.useEffect(()=>{if(b(!0),typeof ResizeObserver<"u"){const $=new ResizeObserver(N=>{const S=N[0].target;m(S.offsetHeight+8),p(S.offsetWidth)}),C=setInterval(()=>{var E;const N=o?`.${o(v)}`:`.${v}-dropdown`,S=(E=u.current)==null?void 0:E.querySelector(N);S&&(clearInterval(C),$.observe(S))},10);return()=>{clearInterval(C),$.disconnect()}}},[v]);let g={...i,style:{...c,margin:0},open:y,getPopupContainer:()=>u.current};t&&(g={...g,[t]:{overflow:{adjustX:!1,adjustY:!1}}});const h={paddingBottom:d,position:"relative",minWidth:f};return a.createElement("div",{ref:u,style:h},a.createElement(e,{...g}))}),qA=(e,t,n,r,o=!1,s,i)=>{const l=a.useMemo(()=>typeof n=="boolean"?{allowClear:n}:n&&typeof n=="object"?n:{allowClear:!1},[n]);return a.useMemo(()=>{const c=!o&&l.allowClear!==!1&&(t.length||s)&&!(i==="combobox"&&s==="");return{allowClear:c,clearIcon:c?l.clearIcon||r||"×":null}},[l,r,o,t.length,s,i])},zR=a.createContext(null);function Di(){return a.useContext(zR)}function GA(e=250){const t=a.useRef(null),n=a.useRef(null);a.useEffect(()=>()=>{window.clearTimeout(n.current)},[]);function r(o){(o||t.current===null)&&(t.current=o),window.clearTimeout(n.current),n.current=window.setTimeout(()=>{t.current=null},e)}return[()=>t.current,r]}function jR(e,t){return e.filter(n=>n).some(n=>n.contains(t)||n===t)}function XA(e,t,n,r){const o=vt(s=>{if(r)return;let i=s.target;i.shadowRoot&&s.composed&&(i=s.composedPath()[0]||i),s._ori_target&&(i=s._ori_target),t&&!jR(e(),i)&&n(!1)});a.useEffect(()=>(window.addEventListener("mousedown",o),()=>window.removeEventListener("mousedown",o)),[o])}function Ky(){return Ky=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const t=e===!0?0:1;return{bottomLeft:{points:["tl","bl"],offset:[0,4],overflow:{adjustX:t,adjustY:1},htmlRegion:"scroll"},bottomRight:{points:["tr","br"],offset:[0,4],overflow:{adjustX:t,adjustY:1},htmlRegion:"scroll"},topLeft:{points:["bl","tl"],offset:[0,-4],overflow:{adjustX:t,adjustY:1},htmlRegion:"scroll"},topRight:{points:["br","tr"],offset:[0,-4],overflow:{adjustX:t,adjustY:1},htmlRegion:"scroll"}}},QA=(e,t)=>{const{prefixCls:n,disabled:r,visible:o,children:s,popupElement:i,animation:l,transitionName:c,popupStyle:u,popupClassName:d,direction:m="ltr",placement:f,builtinPlacements:p,popupMatchSelectWidth:y,popupRender:b,popupAlign:x,getPopupContainer:v,empty:g,onPopupVisibleChange:h,onPopupMouseEnter:$,onPopupMouseDown:C,onPopupBlur:N,...S}=e,E=`${n}-dropdown`;let w=i;b&&(w=b(i));const R=a.useMemo(()=>p||YA(y),[p,y]),P=l?`${E}-${l}`:c,T=typeof y=="number",M=a.useMemo(()=>y===!1||T?"minWidth":"width",[y,T]);let z=u;T&&(z={...u,width:y});const B=a.useRef(null);return a.useImperativeHandle(t,()=>({getPopupElement:()=>{var F;return(F=B.current)==null?void 0:F.popupElement}})),a.createElement(bm,Ky({},S,{showAction:h?["click"]:[],hideAction:h?["click"]:[],popupPlacement:f||(m==="rtl"?"bottomRight":"bottomLeft"),builtinPlacements:R,prefixCls:E,popupMotion:{motionName:P},popup:a.createElement("div",{onMouseEnter:$,onMouseDown:C,onBlur:N},w),ref:B,stretch:M,popupAlign:x,popupVisible:o,getPopupContainer:v,popupClassName:H(d,{[`${E}-empty`]:g}),popupStyle:z,onPopupVisibleChange:h}),s)},JA=a.forwardRef(QA);function fC(e,t){const{key:n}=e;let r;return"value"in e&&({value:r}=e),n??(r!==void 0?r:`rc-index-key-${t}`)}function Uy(e){return typeof e<"u"&&!Number.isNaN(e)}function BR(e,t){const{label:n,value:r,options:o,groupLabel:s}=e||{},i=n||(t?"children":"label");return{label:i,value:r||"value",options:o||"options",groupLabel:s||i}}function ZA(e,{fieldNames:t,childrenAsData:n}={}){const r=[],{label:o,value:s,options:i,groupLabel:l}=BR(t,!1);function c(u,d){Array.isArray(u)&&u.forEach(m=>{if(d||!(i in m)){const f=m[s];r.push({key:fC(m,r.length),groupOption:d,data:m,label:m[o],value:f})}else{let f=m[l];f===void 0&&n&&(f=m.label),r.push({key:fC(m,r.length),group:!0,data:m,label:f}),c(m[i],!0)}})}return c(e,!1),r}function qy(e){const t={...e};return"props"in t||Object.defineProperty(t,"props",{get(){return fn(!1,"Return type is option instead of Option instance. Please read value directly instead of reading from `props`."),t}}),t}const eD=(e,t,n)=>{if(!t||!t.length)return null;let r=!1;const o=(i,[l,...c])=>{if(!l)return[i];const u=i.split(l);return r=r||u.length>1,u.reduce((d,m)=>[...d,...o(m,c)],[]).filter(Boolean)},s=o(e,t);return r?typeof n<"u"?s.slice(0,n):s:null};function tD(e){const{visible:t,values:n}=e;if(!t)return null;const r=50;return a.createElement("span",{"aria-live":"polite",style:{width:0,height:0,position:"absolute",overflow:"hidden",opacity:0}},`${n.slice(0,r).map(({label:o,value:s})=>["number","string"].includes(typeof o)?o:s).join(", ")}`,n.length>r?", ...":null)}const nD=e=>{const t=new MessageChannel;t.port1.onmessage=e,t.port2.postMessage(null)},Cx=(e,t=1)=>{if(t<=0){e();return}nD(()=>{Cx(e,t-1)})};function rD(e,t,n,r){const[o,s]=a.useState(!1);a.useEffect(()=>{s(!0)},[]);const[i,l]=nn(e,t),[c,u]=a.useState(!1),d=o?i:!1,m=r(d),f=a.useRef(0),p=vt(b=>{n&&m!==b&&n(b),l(b)}),y=vt((b,x={})=>{const{cancelFun:v}=x;f.current+=1;const g=f.current,h=typeof b=="boolean"?b:!m;u(!h);function $(){g===f.current&&!(v!=null&&v())&&(p(h),u(!1))}h?$():Cx(()=>{$()})});return[d,m,y,c]}function ig(e){const{children:t,...n}=e;return t?a.createElement("div",n,t):null}const LR=a.createContext(null);function tu(){return a.useContext(LR)}const kR=a.forwardRef((e,t)=>{const{onChange:n,onKeyDown:r,onBlur:o,style:s,syncWidth:i,value:l,className:c,autoComplete:u,...d}=e,{prefixCls:m,mode:f,onSearch:p,onSearchSubmit:y,onInputBlur:b,autoFocus:x,tokenWithEnter:v,placeholder:g,components:{input:h="input"}}=tu(),{id:$,classNames:C,styles:N,open:S,activeDescendantId:E,role:w,disabled:R}=Di()||{},P=H(`${m}-input`,C==null?void 0:C.input,c),T=a.useRef(!1),M=a.useRef(null),z=a.useRef(null);a.useImperativeHandle(t,()=>z.current);const B=W=>{let{value:K}=W.target;if(v&&M.current&&/[\r\n]/.test(M.current)){const q=M.current.replace(/[\r\n]+$/,"").replace(/\r\n/g," ").replace(/[\r\n]/g," ");K=K.replace(q,M.current)}M.current=null,p&&p(K,!0,T.current),n==null||n(W)},F=W=>{const{key:K}=W,{value:q}=W.currentTarget;K==="Enter"&&f==="tags"&&!S&&!T.current&&y&&y(q),r==null||r(W)},L=W=>{b==null||b(),o==null||o(W)},j=()=>{T.current=!0},O=W=>{if(T.current=!1,f!=="combobox"){const{value:K}=W.currentTarget;p==null||p(K,!0,!1)}},A=W=>{const{clipboardData:K}=W,q=K==null?void 0:K.getData("text");M.current=q||""},[k,_]=a.useState(void 0);It(()=>{const W=z.current;if(i&&W){W.style.width="0px";const K=W.scrollWidth;_(K),W.style.width=""}},[i,l]);const D={id:$,type:"text",...d,ref:z,style:{...N==null?void 0:N.input,...s,"--select-input-width":k},autoFocus:x,autoComplete:u||"new-password",className:P,disabled:R,value:l||"",onChange:B,onKeyDown:F,onBlur:L,onPaste:A,onCompositionStart:j,onCompositionEnd:O,role:w||"combobox","aria-expanded":S||!1,"aria-haspopup":"listbox","aria-owns":S?`${$}_list`:void 0,"aria-autocomplete":"list","aria-controls":S?`${$}_list`:void 0,"aria-activedescendant":S?E:void 0};if(a.isValidElement(h)){const W=h.props||{},K={placeholder:e.placeholder||g,...D,...W};return Object.keys(W).forEach(q=>{const Y=W[q];typeof Y=="function"&&(K[q]=(...ee)=>{var ie;Y(...ee),(ie=D[q])==null||ie.call(D,...ee)})}),K.ref=Tn(h.ref,D.ref),a.cloneElement(h,K)}const V=h;return a.createElement(V,D)});function AR(e){const{prefixCls:t,placeholder:n,displayValues:r}=tu(),{classNames:o,styles:s}=Di(),{show:i=!0}=e;return r.length?null:a.createElement("div",{className:H(`${t}-placeholder`,o==null?void 0:o.placeholder),style:{visibility:i?"visible":"hidden",...s==null?void 0:s.placeholder}},n)}const wx=a.createContext(null);function DR(e){return Array.isArray(e)?e:e!==void 0?[e]:[]}function oD(e){return e!=null}function sD(e){return!e&&e!==0}function mC(e){return["string","number"].includes(typeof e)}function Gy(e){let t;return e&&(mC(e.title)?t=e.title.toString():mC(e.label)&&(t=e.label.toString())),t}function Xy(){return Xy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,searchValue:r,activeValue:o,displayValues:s,maxLength:i,mode:l,components:c}=tu(),{triggerOpen:u,title:d,showSearch:m,classNames:f,styles:p}=Di(),y=a.useContext(wx),[b,x]=a.useState(!1),v=l==="combobox",g=s[0],h=a.useMemo(()=>v&&o&&!b&&u?o:m?r:"",[v,o,b,u,r,m]),[$,C,N,S]=a.useMemo(()=>{let P,T,M;if(g&&(y!=null&&y.flattenOptions)){const B=y.flattenOptions.find(F=>F.value===g.value);B!=null&&B.data&&(P=B.data.className,T=B.data.style,M=Gy(B.data))}return g&&!M&&(M=Gy(g)),d!==void 0&&(M=d),[P,T,M,!!P||!!T]},[g,y==null?void 0:y.flattenOptions,d]);a.useEffect(()=>{v&&x(!1)},[v,o]);const E=g&&g.label!==null&&g.label!==void 0&&String(g.label).trim()!=="",R=!(v&&(c!=null&&c.input))?g?S?a.createElement("div",{className:H(`${n}-content-value`,$),style:{...h?{visibility:"hidden"}:{},...C},title:N},g.label):g.label:a.createElement(AR,{show:!h}):null;return a.createElement("div",{className:H(`${n}-content`,E&&`${n}-content-has-value`,h&&`${n}-content-has-search-value`,S&&`${n}-content-has-option-style`,f==null?void 0:f.content),style:p==null?void 0:p.content,title:S?void 0:N},R,a.createElement(kR,Xy({ref:t},e,{value:h,maxLength:l==="combobox"?i:void 0,onChange:P=>{var T;x(!0),(T=e.onChange)==null||T.call(e,P)}})))}),Ji=void 0;function aD(e,t){const{prefixCls:n,invalidate:r,item:o,renderItem:s,responsive:i,responsiveDisabled:l,registerSize:c,itemKey:u,className:d,style:m,children:f,display:p,order:y,component:b="div",...x}=e,v=i&&!p;function g(S){c(u,S)}a.useEffect(()=>()=>{g(null)},[]);const h=s&&o!==Ji?s(o,{index:y}):f;let $;r||($={opacity:v?0:1,height:v?0:Ji,overflowY:v?"hidden":Ji,order:i?y:Ji,pointerEvents:v?"none":Ji,position:v?"absolute":Ji});const C={};v&&(C["aria-hidden"]=!0);let N=a.createElement(b,qr({className:H(!r&&n,d),style:{...$,...m}},C,x,{ref:t}),h);return i&&(N=a.createElement(ir,{onResize:({offsetWidth:S})=>{g(S)},disabled:l},N)),N}const Ol=a.forwardRef(aD);function lD(e){if(typeof MessageChannel>"u")Ct(e);else{const t=new MessageChannel;t.port1.onmessage=()=>e(),t.port2.postMessage(void 0)}}function cD(){const e=a.useRef(null);return n=>{e.current||(e.current=[],lD(()=>{ss.unstable_batchedUpdates(()=>{e.current.forEach(r=>{r()}),e.current=null})})),e.current.push(n)}}function Zi(e,t){const[n,r]=a.useState(t),o=vt(s=>{e(()=>{r(s)})});return[n,o]}const pf=J.createContext(null),uD=(e,t)=>{const n=a.useContext(pf);if(!n){const{component:l="div",...c}=e;return a.createElement(l,qr({},c,{ref:t}))}const{className:r,...o}=n,{className:s,...i}=e;return a.createElement(pf.Provider,{value:null},a.createElement(Ol,qr({ref:t,className:H(r,s)},o,i)))},dD=a.forwardRef(uD),FR="responsive",HR="invalidate";function fD(e){return`+ ${e.length} ...`}function mD(e,t){const{prefixCls:n="rc-overflow",data:r=[],renderItem:o,renderRawItem:s,itemKey:i,itemWidth:l=10,ssr:c,style:u,className:d,maxCount:m,renderRest:f,renderRawRest:p,prefix:y,suffix:b,component:x="div",itemComponent:v,onVisibleChange:g,...h}=e,$=c==="full",C=cD(),[N,S]=Zi(C,null),E=N||0,[w,R]=Zi(C,new Map),[P,T]=Zi(C,0),[M,z]=Zi(C,0),[B,F]=Zi(C,0),[L,j]=Zi(C,0),[O,A]=a.useState(null),[k,_]=a.useState(null),D=a.useMemo(()=>k===null&&$?Number.MAX_SAFE_INTEGER:k||0,[k,N]),[V,W]=a.useState(!1),K=`${n}-item`,q=Math.max(P,M),Y=m===FR,ee=r.length&&Y,ie=m===HR,ae=ee||typeof m=="number"&&r.length>m,U=a.useMemo(()=>{let Oe=r;return ee?N===null&&$?Oe=r:Oe=r.slice(0,Math.min(r.length,E/l)):typeof m=="number"&&(Oe=r.slice(0,m)),Oe},[r,l,N,m,ee]),Q=a.useMemo(()=>ee?r.slice(D+1):r.slice(U.length),[r,U,ee,D]),Z=a.useCallback((Oe,Ce)=>typeof i=="function"?i(Oe):(i&&(Oe==null?void 0:Oe[i]))??Ce,[i]),ne=a.useCallback(o||(Oe=>Oe),[o]);function oe(Oe,Ce,Me){k===Oe&&(Ce===void 0||Ce===O)||(_(Oe),Me||(W(Oe{const xe=new Map(Me);return Ce===null?xe.delete(Oe):xe.set(Oe,Ce),xe})}function X(Oe,Ce){z(Ce),T(M)}function se(Oe,Ce){F(Ce)}function ge(Oe,Ce){j(Ce)}function de(Oe){return w.get(Z(U[Oe],Oe))}It(()=>{if(E&&typeof q=="number"&&U){let Oe=B+L;const Ce=U.length,Me=Ce-1;if(!Ce){oe(0,null);return}for(let xe=0;xeE){oe(xe-1,Oe-Ee-L+M);break}}b&&de(0)+L>E&&A(null)}},[E,w,M,B,L,Z,U]);const Se=V&&!!Q.length;let ue={};O!==null&&ee&&(ue={position:"absolute",top:0,insetInlineStart:O});const be={prefixCls:K,responsive:ee,component:v,invalidate:ie},Ne=s?(Oe,Ce)=>{const Me=Z(Oe,Ce);return a.createElement(pf.Provider,{key:Me,value:{...be,order:Ce,item:Oe,itemKey:Me,registerSize:re,display:Ce<=D}},s(Oe,Ce))}:(Oe,Ce)=>{const Me=Z(Oe,Ce);return a.createElement(Ol,qr({},be,{order:Ce,key:Me,item:Oe,renderItem:ne,itemKey:Me,registerSize:re,display:Ce<=D}))},we={order:Se?D:Number.MAX_SAFE_INTEGER,className:`${K}-rest`,registerSize:X,display:Se},ze=f||fD,he=p?a.createElement(pf.Provider,{value:{...be,...we}},p(Q)):a.createElement(Ol,qr({},be,we),typeof ze=="function"?ze(Q):ze),ke=a.createElement(x,qr({className:H(!ie&&n,d),style:u,ref:t},h),y&&a.createElement(Ol,qr({},be,{responsive:Y,responsiveDisabled:!ee,order:-1,className:`${K}-prefix`,registerSize:se,display:!0}),y),U.map(Ne),ae?he:null,b&&a.createElement(Ol,qr({},be,{responsive:Y,responsiveDisabled:!ee,order:D,className:`${K}-suffix`,registerSize:ge,display:!0,style:ue}),b));return Y?a.createElement(ir,{onResize:le,disabled:!ee},ke):ke}const es=a.forwardRef(mD);es.Item=dD;es.RESPONSIVE=FR;es.INVALIDATE=HR;const VR=e=>{const{className:t,style:n,customizeIcon:r,customizeIconProps:o,children:s,onMouseDown:i,onClick:l}=e,c=typeof r=="function"?r(o):r;return a.createElement("span",{className:t,onMouseDown:u=>{u.preventDefault(),i==null||i(u)},style:{userSelect:"none",WebkitUserSelect:"none",...n},unselectable:"on",onClick:l,"aria-hidden":!0},c!==void 0?c:a.createElement("span",{className:H(t.split(/\s+/).map(u=>`${u}-icon`))},s))};function Yy(){return Yy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e.preventDefault(),e.stopPropagation()},gD=a.forwardRef(function({inputProps:t},n){const{prefixCls:r,displayValues:o,searchValue:s,mode:i,onSelectorRemove:l,removeIcon:c}=tu(),{disabled:u,showSearch:d,triggerOpen:m,rawOpen:f,toggleOpen:p,autoClearSearchValue:y,tagRender:b,maxTagPlaceholder:x,maxTagTextLength:v,maxTagCount:g,classNames:h,styles:$}=Di(),C=`${r}-selection-item`;let N=s;!f&&i==="multiple"&&y!==!1&&(N="");const S=d&&N||"",E=d&&!u,w=c??"×",R=x??(j=>`+ ${j.length} ...`),P=b,T=j=>{p(j)},M=j=>{l==null||l(j)},z=(j,O,A,k,_)=>a.createElement("span",{title:Gy(j),className:H(C,{[`${C}-disabled`]:A},h==null?void 0:h.item),style:$==null?void 0:$.item},a.createElement("span",{className:H(`${C}-content`,h==null?void 0:h.itemContent),style:$==null?void 0:$.itemContent},O),k&&a.createElement(VR,{className:H(`${C}-remove`,h==null?void 0:h.itemRemove),style:$==null?void 0:$.itemRemove,onMouseDown:pC,onClick:_,customizeIcon:w},"×")),B=(j,O,A,k,_,D,V)=>{const W=K=>{pC(K),T(!m)};return a.createElement("span",{onMouseDown:W},P({label:O,value:j,index:V==null?void 0:V.index,disabled:A,closable:k,onClose:_,isMaxTag:!!D}))},F=(j,O)=>{const{disabled:A,label:k,value:_}=j,D=!u&&!A;let V=k;if(typeof v=="number"&&(typeof k=="string"||typeof k=="number")){const K=String(V);K.length>v&&(V=`${K.slice(0,v)}...`)}const W=K=>{K&&K.stopPropagation(),M(j)};return typeof P=="function"?B(_,V,A,D,W,void 0,O):z(j,V,A,D,W)},L=j=>{if(!o.length)return null;const O=typeof R=="function"?R(j):R;return typeof P=="function"?B(void 0,O,!1,!1,void 0,!0):z({title:O},O,!1)};return a.createElement(es,{prefixCls:`${r}-content`,className:h==null?void 0:h.content,style:$==null?void 0:$.content,prefix:!o.length&&!S&&a.createElement(AR,null),data:o,renderItem:F,renderRest:L,suffix:a.createElement(kR,Yy({ref:n,disabled:u,readOnly:!E},t,{value:S||"",syncWidth:!0})),itemKey:pD,maxCount:g})}),hD=a.forwardRef(function(t,n){const{multiple:r,onInputKeyDown:o,tabIndex:s}=tu(),i=Di(),{showSearch:l}=i,u={...Nn(i,{aria:!0}),onKeyDown:o,readOnly:!l,tabIndex:s};return r?a.createElement(gD,{ref:n,inputProps:u}):a.createElement(iD,{ref:n,inputProps:u})});function yD(e){return e&&![nt.ESC,nt.SHIFT,nt.BACKSPACE,nt.TAB,nt.WIN_KEY,nt.ALT,nt.META,nt.WIN_KEY_RIGHT,nt.CTRL,nt.SEMICOLON,nt.EQUALS,nt.CAPS_LOCK,nt.CONTEXT_MENU,nt.UP,nt.LEFT,nt.RIGHT,nt.F1,nt.F2,nt.F3,nt.F4,nt.F5,nt.F6,nt.F7,nt.F8,nt.F9,nt.F10,nt.F11,nt.F12].includes(e)}function gf(){return gf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{which:Y}=q,ee=O.current instanceof HTMLTextAreaElement;if(!ee&&P&&(Y===nt.UP||Y===nt.DOWN)&&q.preventDefault(),N&&N(q),ee&&!P&&~[nt.UP,nt.DOWN,nt.LEFT,nt.RIGHT].indexOf(Y))return;!(q.ctrlKey||q.altKey||q.metaKey)&&yD(Y)&&T(!0)});a.useImperativeHandle(n,()=>({focus:q=>{var Y,ee;(ee=(Y=O.current||j.current).focus)==null||ee.call(Y,q)},blur:()=>{var q,Y;(Y=(q=O.current||j.current).blur)==null||Y.call(q)},nativeElement:go(j.current)}));const k=vt(q=>{var Y;if(!z){const ee=go(O.current);q.nativeEvent._ori_target=ee;const ie=ee===q.target||(ee==null?void 0:ee.contains(q.target));ee&&!ie&&q.preventDefault();const Q=P&&!d&&(p==="combobox"||M)||P&&d&&ie;q.nativeEvent._select_lazy?P&&!d&&T(!1):((Y=O.current)==null||Y.focus(),Q||T())}$==null||$(q)}),{root:_}=w,D=Dt(R,vD),V=Nn(D,{aria:!0}),W=Object.keys(V),K={...t,onInputKeyDown:A};if(_){const q=_.props||{},Y={...q,...D};return Object.keys(q).forEach(ee=>{const ie=q[ee],ae=D[ee];typeof ie=="function"&&typeof ae=="function"&&(Y[ee]=(...U)=>{ae(...U),ie(...U)})}),a.isValidElement(_)?a.cloneElement(_,{...Y,ref:Tn(_.ref,j)}):a.createElement(_,gf({},Y,{ref:j}))}return a.createElement(LR.Provider,{value:K},a.createElement("div",gf({},Dt(D,W),{ref:j,className:o,style:s,onMouseDown:k}),a.createElement(ig,{className:H(`${r}-prefix`,F==null?void 0:F.prefix),style:L==null?void 0:L.prefix},i),a.createElement(hD,{ref:O}),a.createElement(ig,{className:H(`${r}-suffix`,{[`${r}-suffix-loading`]:B},F==null?void 0:F.suffix),style:L==null?void 0:L.suffix},l),c&&a.createElement(ig,{className:H(`${r}-clear`,F==null?void 0:F.clear),style:L==null?void 0:L.clear,onMouseDown:q=>{q.nativeEvent._select_lazy=!0,C==null||C(q)}},c),u))});function xD(e,t,n){return a.useMemo(()=>{let{root:r,input:o}=e||{};return n&&(r=n()),t&&(o=t()),{root:r,input:o}},[e,t,n])}function Qy(){return Qy=Object.assign?Object.assign.bind():function(e){for(var t=1;te==="tags"||e==="multiple",$D=a.forwardRef((e,t)=>{const{id:n,prefixCls:r,className:o,styles:s,classNames:i,showSearch:l,tagRender:c,showScrollBar:u="optional",direction:d,omitDomProps:m,displayValues:f,onDisplayValuesChange:p,emptyOptions:y,notFoundContent:b="Not Found",onClear:x,maxCount:v,placeholder:g,mode:h,disabled:$,loading:C,getInputElement:N,getRawInputElement:S,open:E,defaultOpen:w,onPopupVisibleChange:R,activeValue:P,onActiveValueChange:T,activeDescendantId:M,searchValue:z,autoClearSearchValue:B,onSearch:F,onSearchSplit:L,tokenSeparators:j,allowClear:O,prefix:A,suffix:k,suffixIcon:_,clearIcon:D,OptionList:V,animation:W,transitionName:K,popupStyle:q,popupClassName:Y,popupMatchSelectWidth:ee,popupRender:ie,popupAlign:ae,placement:U,builtinPlacements:Q,getPopupContainer:Z,showAction:ne=[],onFocus:oe,onBlur:le,onKeyUp:re,onKeyDown:X,onMouseDown:se,components:ge,...de}=e,Se=Jy(h),ue=a.useRef(null),be=a.useRef(null),Ne=a.useRef(null),[we,ze]=a.useState(!1);a.useImperativeHandle(t,()=>{var Le,Ke;return{focus:(Le=ue.current)==null?void 0:Le.focus,blur:(Ke=ue.current)==null?void 0:Ke.blur,scrollTo:lt=>{var _t;return(_t=Ne.current)==null?void 0:_t.scrollTo(lt)},nativeElement:go(ue.current)}});const he=xD(ge,N,S),ke=a.useMemo(()=>{var Ke;if(h!=="combobox")return z;const Le=(Ke=f[0])==null?void 0:Ke.value;return typeof Le=="string"||typeof Le=="number"?String(Le):""},[z,h,f]),Oe=h==="combobox"&&typeof N=="function"&&N()||null,Ce=!b&&y,[Me,xe,Ee,Ve]=rD(w||!1,E,R,Le=>$||Ce?!1:Le),qe=a.useMemo(()=>typeof j=="function"||(j||[]).some(Le=>[` +`,`\r +`].includes(Le)),[j]),me=a.useMemo(()=>typeof j=="function"?(Le,Ke)=>{const lt=j(Le),_t=Array.isArray(lt)&<.length===1&<[0]===Le;return!Array.isArray(lt)||!lt.length||_t?null:typeof Ke<"u"?lt.slice(0,Ke):lt}:(Le,Ke)=>eD(Le,j,Ke),[j]),Re=(Le,Ke,lt)=>{if(Se&&Uy(v)&&f.length>=v)return;let _t=!0,ft=Le;T==null||T(null);const xt=Uy(v)?v-f.length:void 0,jt=lt?null:me(Le,xt);return h!=="combobox"&&jt&&(ft="",L==null||L(jt),Ee(!1),_t=!1),F&&ke!==ft&&F(ft,{source:Ke?"typing":"effect"}),Le&&Ke&&_t&&Ee(!0),_t},Te=Le=>{!Le||!Le.trim()||F(Le,{source:"submit"})};a.useEffect(()=>{!Me&&!Se&&h!=="combobox"&&Re("",!1,!1)},[Me]),a.useEffect(()=>{$&&(Ee(!1),ze(!1))},[$,xe]);const[Ue,Ge]=GA(),Fe=a.useRef(!1),et=Le=>{var xt;const Ke=Ue(),{key:lt}=Le,_t=lt==="Enter",ft=lt===" ";if(_t||ft){const jt=h==="combobox";(ft&&!(jt||l)||_t&&!jt)&&Le.preventDefault(),xe||Ee(!0)}if(Ge(!!ke),lt==="Backspace"&&!Ke&&Se&&!ke&&f.length){const jt=[...f];let pt=null;for(let qt=jt.length-1;qt>=0;qt-=1){const cn=jt[qt];if(!cn.disabled){jt.splice(qt,1),pt=cn;break}}pt&&p(jt,{type:"remove",values:[pt]})}xe&&(!_t||!Fe.current)&&!ft&&(_t&&(Fe.current=!0),(xt=Ne.current)==null||xt.onKeyDown(Le)),X==null||X(Le)},ve=(Le,...Ke)=>{var lt;xe&&((lt=Ne.current)==null||lt.onKeyUp(Le,...Ke)),Le.key==="Enter"&&(Fe.current=!1),re==null||re(Le,...Ke)},je=vt(Le=>{const Ke=f.filter(lt=>lt!==Le);p(Ke,{type:"remove",values:[Le]})}),ce=()=>{Fe.current=!1},Pe=()=>{var Le;return[go(ue.current),(Le=be.current)==null?void 0:Le.getPopupElement()]};XA(Pe,xe,Ee,!!he.root);const pe=a.useRef(!1),$e=Le=>{ze(!0),$||(ne.includes("focus")&&Ee(!0),oe==null||oe(Le))},_e=()=>{xe&&!pe.current&&Ee(!1,{cancelFun:()=>jR(Pe(),document.activeElement)})},Ie=Le=>{ze(!1),ke&&(h==="tags"?F(ke,{source:"submit"}):h==="multiple"&&F("",{source:"blur"})),_e(),$||le==null||le(Le)},Be=(Le,...Ke)=>{var ft;const{target:lt}=Le,_t=(ft=be.current)==null?void 0:ft.getPopupElement();_t!=null&&_t.contains(lt)&&Ee&&Ee(!0),se==null||se(Le,...Ke),pe.current=!0,Cx(()=>{pe.current=!1})},[,te]=a.useState({});function ye(){te({})}let Ae;he.root&&(Ae=Le=>{Ee(Le)});const Je=a.useMemo(()=>({...e,notFoundContent:b,open:xe,triggerOpen:xe,rawOpen:Me,id:n,showSearch:l,multiple:Se,toggleOpen:Ee,showScrollBar:u,styles:s,classNames:i,lockOptions:Ve}),[e,b,Ee,n,l,Se,xe,Me,u,s,i,Ve]),St=a.useMemo(()=>{const Le=k??_;return typeof Le=="function"?Le({searchValue:ke,open:xe,focused:we,showSearch:l,loading:C}):Le},[k,_,ke,xe,we,l,C]),ht=()=>{var Le;x==null||x(),(Le=ue.current)==null||Le.focus(),p([],{type:"clear",values:f}),Re("",!1,!1)},{allowClear:Nt,clearIcon:yt}=qA(r,f,O,D,$,ke,h),at=a.createElement(V,{ref:Ne}),Ze=H(r,o,{[`${r}-focused`]:we,[`${r}-multiple`]:Se,[`${r}-single`]:!Se,[`${r}-allow-clear`]:Nt,[`${r}-show-arrow`]:St!=null,[`${r}-disabled`]:$,[`${r}-loading`]:C,[`${r}-open`]:xe,[`${r}-customize-input`]:Oe,[`${r}-show-search`]:l});let De=a.createElement(bD,Qy({},de,{ref:ue,prefixCls:r,className:Ze,focused:we,prefix:A,suffix:St,clearIcon:yt,multiple:Se,mode:h,displayValues:f,placeholder:g,searchValue:ke,activeValue:P,onSearch:Re,onSearchSubmit:Te,onInputBlur:ce,onFocus:$e,onBlur:Ie,onClearMouseDown:ht,onKeyDown:et,onKeyUp:ve,onSelectorRemove:je,tokenWithEnter:qe,onMouseDown:Be,components:he}));return De=a.createElement(JA,{ref:be,disabled:$,prefixCls:r,visible:xe,popupElement:at,animation:W,transitionName:K,popupStyle:q,popupClassName:Y,direction:d,popupMatchSelectWidth:ee,popupRender:ie,popupAlign:ae,placement:U,builtinPlacements:Q,getPopupContainer:Z,empty:y,onPopupVisibleChange:Ae,onPopupMouseEnter:ye,onPopupMouseDown:Be,onPopupBlur:_e},De),a.createElement(zR.Provider,{value:Je},a.createElement(tD,{visible:we&&!xe,values:f}),De)}),Ex=()=>null;Ex.isSelectOptGroup=!0;const Ix=()=>null;Ix.isSelectOption=!0;function Zy(){return Zy=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let d={},m={display:"flex",flexDirection:"column"};return t!==void 0&&(d={height:e,position:"relative",overflow:"hidden"},m={...m,transform:`translateY(${t}px)`,[l?"marginRight":"marginLeft"]:-n,position:"absolute",left:0,right:0,top:0}),a.createElement("div",{style:d},a.createElement(ir,{onResize:({offsetHeight:f})=>{f&&s&&s()}},a.createElement("div",Zy({style:m,className:H({[`${o}-holder-inner`]:o}),ref:u},i),r,c)))});WR.displayName="Filler";function SD({children:e,setRef:t}){const n=a.useCallback(r=>{t(r)},[t]);return a.cloneElement(e,{ref:n})}function CD(e,t,n,r,o,s,i,{getKey:l}){return e.slice(t,n+1).map((c,u)=>{const d=t+u,m=i(c,d,{style:{width:r},offsetX:o}),f=l(c);return a.createElement(SD,{key:f,setRef:p=>s(c,p)},m)})}function wD(e,t,n){const r=e.length,o=t.length;let s,i;if(r===0&&o===0)return null;r{const l=wD(r||[],e||[],t);(l==null?void 0:l.index)!==void 0&&i(e[l.index]),o(e)},[e]),[s]}const gC=typeof navigator=="object"&&/Firefox/i.test(navigator.userAgent),KR=(e,t,n,r)=>{const o=a.useRef(!1),s=a.useRef(null);function i(){clearTimeout(s.current),o.current=!0,s.current=setTimeout(()=>{o.current=!1},50)}const l=a.useRef({top:e,bottom:t,left:n,right:r});return l.current.top=e,l.current.bottom=t,l.current.left=n,l.current.right=r,(c,u,d=!1)=>{const m=c?u<0&&l.current.left||u>0&&l.current.right:u<0&&l.current.top||u>0&&l.current.bottom;return d&&m?(clearTimeout(s.current),o.current=!1):(!m||o.current)&&i(),!o.current&&m}};function ID(e,t,n,r,o,s,i){const l=a.useRef(0),c=a.useRef(null),u=a.useRef(null),d=a.useRef(!1),m=KR(t,n,r,o);function f(g,h){if(Ct.cancel(c.current),m(!1,h))return;const $=g;if(!$._virtualHandled)$._virtualHandled=!0;else return;l.current+=h,u.current=h,gC||$.preventDefault(),c.current=Ct(()=>{const C=d.current?10:1;i(l.current*C,!1),l.current=0})}function p(g,h){i(h,!0),gC||g.preventDefault()}const y=a.useRef(null),b=a.useRef(null);function x(g){if(!e)return;Ct.cancel(b.current),b.current=Ct(()=>{y.current=null},2);const{deltaX:h,deltaY:$,shiftKey:C}=g;let N=h,S=$;(y.current==="sx"||!y.current&&C&&$&&!h)&&(N=$,S=0,y.current="sx");const E=Math.abs(N),w=Math.abs(S);y.current===null&&(y.current=s&&E>w?"x":"y"),y.current==="y"?f(g,S):p(g,N)}function v(g){e&&(d.current=g.detail===u.current)}return[x,v]}function PD(e,t,n,r){const[o,s]=a.useMemo(()=>[new Map,[]],[e,n.id,r]);return(l,c=l)=>{let u=o.get(l),d=o.get(c);if(u===void 0||d===void 0){const m=e.length;for(let f=s.length;f{let p=!1;s.current.forEach((y,b)=>{if(y&&y.offsetParent){const{offsetHeight:x}=y,{marginTop:v,marginBottom:g}=getComputedStyle(y),h=hC(v),$=hC(g),C=x+h+$;i.current.get(b)!==C&&(i.current.set(b,C),p=!0)}}),p&&o(y=>y+1)};if(m)f();else{l.current+=1;const p=l.current;Promise.resolve().then(()=>{p===l.current&&f()})}}function d(m,f){const p=e(m);s.current.get(p),f?(s.current.set(p,f),u()):s.current.delete(p)}return a.useEffect(()=>c,[]),[d,u,i.current,r]}const yC=14/15;function TD(e,t,n){const r=a.useRef(!1),o=a.useRef(0),s=a.useRef(0),i=a.useRef(null),l=a.useRef(null);let c;const u=f=>{if(r.current){const p=Math.ceil(f.touches[0].pageX),y=Math.ceil(f.touches[0].pageY);let b=o.current-p,x=s.current-y;const v=Math.abs(b)>Math.abs(x);v?o.current=p:s.current=y;const g=n(v,v?b:x,!1,f);g&&f.preventDefault(),clearInterval(l.current),g&&(l.current=setInterval(()=>{v?b*=yC:x*=yC;const h=Math.floor(v?b:x);(!n(v,h,!0)||Math.abs(h)<=.1)&&clearInterval(l.current)},16))}},d=()=>{r.current=!1,c()},m=f=>{c(),f.touches.length===1&&!r.current&&(r.current=!0,o.current=Math.ceil(f.touches[0].pageX),s.current=Math.ceil(f.touches[0].pageY),i.current=f.target,i.current.addEventListener("touchmove",u,{passive:!1}),i.current.addEventListener("touchend",d,{passive:!0}))};c=()=>{i.current&&(i.current.removeEventListener("touchmove",u),i.current.removeEventListener("touchend",d))},It(()=>(e&&t.current.addEventListener("touchstart",m,{passive:!0}),()=>{var f;(f=t.current)==null||f.removeEventListener("touchstart",m),c(),clearInterval(l.current)}),[e])}function vC(e){return Math.floor(e**.5)}function Cd(e,t){return("touches"in e?e.touches[0]:e)[t?"pageX":"pageY"]-window[t?"scrollX":"scrollY"]}function MD(e,t,n){a.useEffect(()=>{const r=t.current;if(e&&r){let o=!1,s,i;const l=()=>{Ct.cancel(s)},c=()=>{l(),s=Ct(()=>{n(i),c()})},u=()=>{o=!1,l()},d=f=>{if(f.target.draggable||f.button!==0)return;const p=f;p._virtualHandled||(p._virtualHandled=!0,o=!0)},m=f=>{if(o){const p=Cd(f,!1),{top:y,bottom:b}=r.getBoundingClientRect();if(p<=y){const x=y-p;i=-vC(x),c()}else if(p>=b){const x=p-b;i=vC(x),c()}else l()}};return r.addEventListener("mousedown",d),r.ownerDocument.addEventListener("mouseup",u),r.ownerDocument.addEventListener("mousemove",m),r.ownerDocument.addEventListener("dragend",u),()=>{r.removeEventListener("mousedown",d),r.ownerDocument.removeEventListener("mouseup",u),r.ownerDocument.removeEventListener("mousemove",m),r.ownerDocument.removeEventListener("dragend",u),l()}}},[e])}const OD=10;function _D(e,t){const n=typeof e=="function"?e(t):e;return Number.isFinite(n)?n:0}function zD(e,t,n,r,o,s,i,l,c){const u=a.useRef(void 0),[d,m]=a.useState(null);return It(()=>{if(d&&d.times({...N}));return}i();const{targetAlign:f,originAlign:p,index:y,offset:b}=d,x=f||p,v=_D(b,{getSize:s,align:x}),g=e.current.clientHeight;let h=!1,$=f,C=null;if(g){let N=0,S=0,E=0;const w=Math.min(t.length-1,y);for(let P=0;P<=w;P+=1){const T=o(t[P]);S=N;const M=n.get(T);E=S+(M===void 0?r:M),N=E}let R=x==="top"?v:g-v;for(let P=w;P>=0;P-=1){const T=o(t[P]),M=n.get(T);if(M===void 0){h=!0;break}if(R-=M,R<=0)break}switch(x){case"top":C=S-v;break;case"bottom":C=E-g+v;break;default:{const{scrollTop:P}=e.current,T=P+g;ST&&($="bottom")}}C!==null&&l(C),C!==d.lastTop&&(h=!0)}h&&m({...d,times:d.times+1,targetAlign:$,lastTop:C})}},[d,e.current]),f=>{if(f==null){c();return}if(Ct.cancel(u.current),typeof f=="number")l(f);else if(f&&typeof f=="object"){let p;const{align:y}=f;"index"in f?{index:p}=f:p=t.findIndex(x=>o(x)===f.key);const{offset:b=0}=f;m({times:0,index:p,offset:b,originAlign:y})}}}function bC(e,t,n){if(t<=0||n<=0)return 0;const o=Math.max(Math.min(e,n),0)/n;let s=Math.ceil(o*t);return s=Math.max(s,0),s=Math.min(s,t),s}const xC=a.forwardRef((e,t)=>{const{prefixCls:n,rtl:r,scrollOffset:o,scrollRange:s,onStartMove:i,onStopMove:l,onScroll:c,horizontal:u,spinSize:d,containerSize:m,style:f,thumbStyle:p,showScrollBar:y}=e,[b,x]=a.useState(!1),[v,g]=a.useState(null),[h,$]=a.useState(null),C=!r,N=a.useRef(null),S=a.useRef(null),[E,w]=a.useState(y),R=a.useRef(void 0),P=()=>{y===!0||y===!1||(clearTimeout(R.current),w(!0),R.current=setTimeout(()=>{w(!1)},3e3))},T=s-m||0,M=m-d||0,z=a.useMemo(()=>o===0||T===0?0:o/T*M,[o,T,M]),B=W=>{var K;return!!W&&((K=S.current)==null?void 0:K.contains(W))},F=W=>{const K=N.current;if(!K)return;const q=K.getBoundingClientRect(),Y=Cd(W,u);let ee;if(Number.isFinite(Y)){if(u){const ie=C?q.left:q.right;if(!Number.isFinite(ie))return;ee=(C?Y-ie:ie-Y)-d/2}else{if(!Number.isFinite(q.top))return;ee=Y-q.top-d/2}c(bC(ee,T,M),u)}},L=W=>{W.stopPropagation(),W.preventDefault(),!(W.button!==0||B(W.target))&&F(W)},j=a.useRef({top:z,dragging:b,pageY:v,startTop:h});j.current={top:z,dragging:b,pageY:v,startTop:h};const O=vt(W=>{x(!0),g(Cd(W,u)),$(j.current.top),i(),W.stopPropagation(),W.preventDefault()});a.useEffect(()=>{const W=Y=>{Y.preventDefault()},K=N.current,q=S.current;return K.addEventListener("touchstart",W,{passive:!1}),q.addEventListener("touchstart",O,{passive:!1}),()=>{K.removeEventListener("touchstart",W),q.removeEventListener("touchstart",O)}},[O]);const A=a.useRef(void 0);A.current=T;const k=a.useRef(void 0);k.current=M,a.useEffect(()=>{if(b){let W;const K=Y=>{const{dragging:ee,pageY:ie,startTop:ae}=j.current;Ct.cancel(W);const U=N.current.getBoundingClientRect(),Q=m/(u?U.width:U.height);if(ee){const Z=(Cd(Y,u)-ie)*Q;let ne=ae;!C&&u?ne-=Z:ne+=Z;const oe=A.current,le=k.current,re=bC(ne,oe,le);W=Ct(()=>{c(re,u)})}},q=()=>{x(!1),l()};return window.addEventListener("mousemove",K,{passive:!0}),window.addEventListener("touchmove",K,{passive:!0}),window.addEventListener("mouseup",q,{passive:!0}),window.addEventListener("touchend",q,{passive:!0}),()=>{window.removeEventListener("mousemove",K),window.removeEventListener("touchmove",K),window.removeEventListener("mouseup",q),window.removeEventListener("touchend",q),Ct.cancel(W)}}},[b]),a.useEffect(()=>(P(),()=>{clearTimeout(R.current)}),[o]),a.useImperativeHandle(t,()=>({delayHidden:P}));const _=`${n}-scrollbar`,D={position:"absolute",visibility:E?null:"hidden"},V={position:"absolute",borderRadius:99,background:"var(--rc-virtual-list-scrollbar-bg, rgba(0, 0, 0, 0.5))",cursor:"pointer",userSelect:"none"};return u?(Object.assign(D,{height:8,left:0,right:0,bottom:0}),Object.assign(V,{height:"100%",width:d,[C?"left":"right"]:z})):(Object.assign(D,{width:8,top:0,bottom:0,[C?"right":"left"]:0}),Object.assign(V,{width:"100%",height:d,top:z})),a.createElement("div",{ref:N,className:H(_,{[`${_}-horizontal`]:u,[`${_}-vertical`]:!u,[`${_}-visible`]:E}),style:{...D,...f},onMouseDown:L,onMouseMove:P},a.createElement("div",{ref:S,className:H(`${_}-thumb`,{[`${_}-thumb-moving`]:b}),style:{...V,...p},onMouseDown:O}))}),jD=20;function $C(e=0,t=0){let n=e/t*e;return isNaN(n)&&(n=0),n=Math.max(n,jD),Math.floor(n)}function ev(){return ev=Object.assign?Object.assign.bind():function(e){for(var t=1;ttypeof d=="function"?d(pe):pe==null?void 0:pe[d],[d]),[E,w,R,P]=RD(S),T=!!(m!==!1&&o&&s),M=a.useMemo(()=>Object.values(R.maps).reduce((pe,$e)=>pe+$e,0),[R.id,R.maps]),z=T&&c&&(Math.max(s*c.length,M)>o||!!p),B=f==="rtl",F=H(n,{[`${n}-rtl`]:B},r),L=c||BD,j=a.useRef(null),O=a.useRef(null),A=a.useRef(null),[k,_]=a.useState(0),[D,V]=a.useState(0),[W,K]=a.useState(!1),q=()=>{K(!0)},Y=()=>{K(!1)},ee={getKey:S};function ie(pe){_($e=>{let _e;typeof pe=="function"?_e=pe($e):_e=pe;const Ie=we(_e);return j.current.scrollTop=Ie,Ie})}const ae=a.useRef({start:0,end:L.length}),U=a.useRef(void 0),[Q]=ED(L,S);U.current=Q;const{scrollHeight:Z,start:ne,end:oe,offset:le}=a.useMemo(()=>{var te;if(!T)return{scrollHeight:void 0,start:0,end:L.length-1,offset:void 0};if(!z)return{scrollHeight:((te=O.current)==null?void 0:te.offsetHeight)||0,start:0,end:L.length-1,offset:void 0};let pe=0,$e,_e,Ie;const Be=L.length;for(let ye=0;ye=k&&$e===void 0&&($e=ye,_e=pe),ht>k+o&&Ie===void 0&&(Ie=ye),pe=ht}return $e===void 0&&($e=0,_e=0,Ie=Math.ceil(o/s)),Ie===void 0&&(Ie=L.length-1),Ie=Math.min(Ie+1,L.length-1),{scrollHeight:pe,start:$e,end:Ie,offset:_e}},[z,T,k,L,P,o]);ae.current.start=ne,ae.current.end=oe,a.useLayoutEffect(()=>{const pe=R.getRecord();if(pe.size===1){const $e=Array.from(pe.keys())[0],_e=pe.get($e),Ie=L[ne];if(Ie&&_e===void 0&&S(Ie)===$e){const ye=R.get($e)-s;ie(Ae=>Ae+ye)}}R.resetRecord()},[Z]);const[re,X]=a.useState({width:0,height:o}),se=pe=>{X({width:pe.offsetWidth,height:pe.offsetHeight})},ge=a.useRef(null),de=a.useRef(null),Se=a.useMemo(()=>$C(re.width,p),[re.width,p]),ue=a.useMemo(()=>$C(re.height,Z),[re.height,Z]),be=Z-o,Ne=a.useRef(be);Ne.current=be;function we(pe){let $e=pe;return Number.isNaN(Ne.current)||($e=Math.min($e,Ne.current)),$e=Math.max($e,0),$e}const ze=k<=0,he=k>=be,ke=D<=0,Oe=D>=p,Ce=KR(ze,he,ke,Oe),Me=()=>({x:B?-D:D,y:k}),xe=a.useRef(Me()),Ee=vt(pe=>{if(x){const $e={...Me(),...pe};(xe.current.x!==$e.x||xe.current.y!==$e.y)&&(x($e),xe.current=$e)}});function Ve(pe,$e){const _e=pe;$e?(ss.flushSync(()=>{V(_e)}),Ee()):ie(_e)}function qe(pe){const{scrollTop:$e}=pe.currentTarget;$e!==k&&ie($e),b==null||b(pe),Ee()}const me=pe=>{let $e=pe;const _e=p?p-re.width:0;return $e=Math.max($e,0),$e=Math.min($e,_e),$e},Re=vt((pe,$e)=>{$e?(ss.flushSync(()=>{V(_e=>{const Ie=_e+(B?-pe:pe);return me(Ie)})}),Ee()):ie(_e=>_e+pe)}),[Te,Ue]=ID(T,ze,he,ke,Oe,!!p,Re);TD(T,j,(pe,$e,_e,Ie)=>{const Be=Ie;return Ce(pe,$e,_e)?!1:!Be||!Be._virtualHandled?(Be&&(Be._virtualHandled=!0),Te({preventDefault(){},deltaX:pe?$e:0,deltaY:pe?0:$e}),!0):!1}),MD(z,j,pe=>{ie($e=>$e+pe)}),It(()=>{function pe(_e){const Ie=ze&&_e.detail<0,Be=he&&_e.detail>0;T&&!Ie&&!Be&&_e.preventDefault()}const $e=j.current;return $e.addEventListener("wheel",Te,{passive:!1}),$e.addEventListener("DOMMouseScroll",Ue,{passive:!0}),$e.addEventListener("MozMousePixelScroll",pe,{passive:!1}),()=>{$e.removeEventListener("wheel",Te),$e.removeEventListener("DOMMouseScroll",Ue),$e.removeEventListener("MozMousePixelScroll",pe)}},[T,ze,he]),It(()=>{if(p){const pe=me(D);V(pe),Ee({x:pe})}},[re.width,p]);const Ge=()=>{var pe,$e;(pe=ge.current)==null||pe.delayHidden(),($e=de.current)==null||$e.delayHidden()},Fe=PD(L,S,R,s),et=zD(j,L,R,s,S,Fe,()=>w(!0),ie,Ge);a.useImperativeHandle(t,()=>({nativeElement:A.current,getScrollInfo:Me,scrollTo:pe=>{function $e(_e){return _e&&typeof _e=="object"&&("left"in _e||"top"in _e)}$e(pe)?(pe.left!==void 0&&V(me(pe.left)),et(pe.top)):et(pe)}})),It(()=>{if(v){const pe=L.slice(ne,oe+1);v(pe,L)}},[ne,oe,L]);const ve=h==null?void 0:h({start:ne,end:oe,virtual:z,offsetX:D,scrollTop:k,offsetY:le,rtl:B,getSize:Fe}),je=CD(L,ne,oe,p,D,E,u,ee);let ce=null;o&&(ce={[i?"height":"maxHeight"]:o,...LD},T&&(ce.overflowY="hidden",p&&(ce.overflowX="hidden"),W&&(ce.pointerEvents="none")));const Pe={};return B&&(Pe.dir="rtl"),a.createElement("div",ev({ref:A,style:{...l,position:"relative"},className:F},Pe,N),a.createElement(ir,{onResize:se},a.createElement(y,{className:`${n}-holder`,style:ce,ref:j,onScroll:qe,onMouseEnter:Ge},a.createElement(WR,{prefixCls:n,height:Z,offsetX:D,offsetY:le,scrollWidth:p,onInnerResize:w,ref:O,innerProps:g,rtl:B,extra:ve},je))),z&&Z>o&&a.createElement(xC,{ref:ge,prefixCls:n,scrollOffset:k,scrollRange:Z,rtl:B,onScroll:Ve,onStartMove:q,onStopMove:Y,spinSize:ue,containerSize:re.height,style:$==null?void 0:$.verticalScrollBar,thumbStyle:$==null?void 0:$.verticalScrollBarThumb,showScrollBar:C}),z&&p>re.width&&a.createElement(xC,{ref:de,prefixCls:n,scrollOffset:D,scrollRange:p,rtl:B,onScroll:Ve,onStartMove:q,onStopMove:Y,spinSize:Se,containerSize:re.width,horizontal:!0,style:$==null?void 0:$.horizontalScrollBar,thumbStyle:$==null?void 0:$.horizontalScrollBarThumb,showScrollBar:C}))}const Tm=a.forwardRef(UR);Tm.displayName="List";const kD=a.forwardRef((e,t)=>UR({...e,virtual:!1},t));kD.displayName="List";function AD(){return/(mac\sos|macintosh)/i.test(navigator.appVersion)}function Gl(){return Gl=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var ie,ae;const{prefixCls:n,id:r,open:o,multiple:s,mode:i,searchValue:l,toggleOpen:c,notFoundContent:u,onPopupScroll:d,showScrollBar:m,lockOptions:f}=Di(),{maxCount:p,flattenOptions:y,onActiveValue:b,defaultActiveFirstOption:x,onSelect:v,menuItemSelectedIcon:g,rawValues:h,fieldNames:$,virtual:C,direction:N,listHeight:S,listItemHeight:E,optionRender:w,classNames:R,styles:P}=a.useContext(wx),T=`${n}-item`,M=_i(()=>y,[o,f],(U,Q)=>Q[0]&&!Q[1]),z=a.useRef(null),B=a.useMemo(()=>s&&Uy(p)&&(h==null?void 0:h.size)>=p,[s,p,h==null?void 0:h.size]),F=U=>{U.preventDefault()},L=U=>{var Q;(Q=z.current)==null||Q.scrollTo(typeof U=="number"?{index:U}:U)},j=a.useCallback(U=>i==="combobox"?!1:h.has(U),[i,[...h].toString(),h.size]),O=(U,Q=1)=>{const Z=M.length;for(let ne=0;neO(0)),_=(U,Q=!1)=>{k(U);const Z={source:Q?"keyboard":"mouse"},ne=M[U];if(!ne){b(null,-1,Z);return}b(ne.value,U,Z)};a.useEffect(()=>{_(x!==!1?O(0):-1)},[M.length,l]);const D=a.useCallback(U=>i==="combobox"?String(U).toLowerCase()===l.toLowerCase():h.has(U),[i,l,[...h].toString(),h.size]);a.useEffect(()=>{var Q;let U;if(!s&&o&&h.size===1){const Z=Array.from(h)[0],ne=M.findIndex(({data:oe})=>l?String(oe.value).startsWith(l):oe.value===Z);ne!==-1&&(_(ne),U=setTimeout(()=>{L(ne)}))}return o&&((Q=z.current)==null||Q.scrollTo(void 0)),()=>clearTimeout(U)},[o,l]);const V=U=>{U!==void 0&&v(U,{selected:!h.has(U)}),s||c(!1)};if(a.useImperativeHandle(t,()=>({onKeyDown:U=>{const{which:Q,ctrlKey:Z}=U;switch(Q){case nt.N:case nt.P:case nt.UP:case nt.DOWN:{let ne=0;if(Q===nt.UP?ne=-1:Q===nt.DOWN?ne=1:AD()&&Z&&(Q===nt.N?ne=1:Q===nt.P&&(ne=-1)),ne!==0){const oe=O(A+ne,ne);L(oe),_(oe,!0)}break}case nt.TAB:case nt.ENTER:{const ne=M[A];if(!ne||ne.data.disabled)return V(void 0);!B||h.has(ne.value)?V(ne.value):V(void 0),o&&U.preventDefault();break}case nt.ESC:c(!1),o&&U.stopPropagation()}},onKeyUp:()=>{},scrollTo:U=>{L(U)}})),M.length===0)return a.createElement("div",{role:"listbox",id:`${r}_list`,className:`${T}-empty`,onMouseDown:F},u);const W=Object.keys($).map(U=>$[U]),K=U=>U.label;function q(U,Q){const{group:Z}=U;return{role:Z?"presentation":"option",id:`${r}_list_${Q}`}}const Y=U=>{const Q=M[U];if(!Q)return null;const Z=Q.data||{},{value:ne,disabled:oe}=Z,{group:le}=Q,re=Nn(Z,!0),X=K(Q);return Q?a.createElement("div",Gl({"aria-label":typeof X=="string"&&!le?X:null},re,{key:U},q(Q,U),{"aria-selected":D(ne),"aria-disabled":oe}),ne):null},ee={role:"listbox",id:`${r}_list`};return a.createElement(a.Fragment,null,C&&a.createElement("div",Gl({},ee,{style:{height:0,width:0,overflow:"hidden"}}),Y(A-1),Y(A),Y(A+1)),a.createElement(Tm,{itemKey:"key",ref:z,data:M,height:S,itemHeight:E,fullHeight:!1,onMouseDown:F,onScroll:d,virtual:C,direction:N,innerProps:C?null:ee,showScrollBar:m,className:(ie=R==null?void 0:R.popup)==null?void 0:ie.list,style:(ae=P==null?void 0:P.popup)==null?void 0:ae.list},(U,Q)=>{var Ee,Ve;const{group:Z,groupOption:ne,data:oe,label:le,value:re}=U,{key:X}=oe;if(Z){const qe=oe.title??(SC(le)?le.toString():void 0);return a.createElement("div",{className:H(T,`${T}-group`,oe.className),title:qe},le!==void 0?le:X)}const{disabled:se,title:ge,children:de,style:Se,className:ue,...be}=oe,Ne=Dt(be,W),we=j(re),ze=se||!we&&B,he=`${T}-option`,ke=H(T,he,ue,(Ee=R==null?void 0:R.popup)==null?void 0:Ee.listItem,{[`${he}-grouped`]:ne,[`${he}-active`]:A===Q&&!ze,[`${he}-disabled`]:ze,[`${he}-selected`]:we}),Oe=K(U),Ce=!g||typeof g=="function"||we,Me=typeof Oe=="number"?Oe:Oe||re;let xe=SC(Me)?Me.toString():void 0;return ge!==void 0&&(xe=ge),a.createElement("div",Gl({},Nn(Ne),C?{}:q(U,Q),{"aria-selected":C?void 0:D(re),"aria-disabled":ze,className:ke,title:xe,onMouseMove:()=>{A===Q||ze||_(Q)},onClick:()=>{ze||V(re)},style:{...(Ve=P==null?void 0:P.popup)==null?void 0:Ve.listItem,...Se}}),a.createElement("div",{className:`${he}-content`},typeof w=="function"?w(U,{index:Q}):Me),a.isValidElement(g)||we,Ce&&a.createElement(VR,{className:`${T}-option-state`,customizeIcon:g,customizeIconProps:{value:re,disabled:ze,isSelected:we}},we?"✓":null))}))},FD=a.forwardRef(DD),HD=(e,t)=>{const n=a.useRef({values:new Map,options:new Map}),r=a.useMemo(()=>{const{values:s,options:i}=n.current,l=e.map(d=>{var m;return d.label===void 0?{...d,label:(m=s.get(d.value))==null?void 0:m.label}:d}),c=new Map,u=new Map;return l.forEach(d=>{c.set(d.value,d),u.set(d.value,t.get(d.value)||i.get(d.value))}),n.current.values=c,n.current.options=u,l},[e,t]),o=a.useCallback(s=>t.get(s)||n.current.options.get(s),[t]);return[r,o]};function ag(e,t){return DR(e).join("").toUpperCase().includes(t)}const VD=(e,t,n,r,o)=>a.useMemo(()=>{if(!n||r===!1)return e;const{options:s,label:i,value:l}=t,c=[],u=typeof r=="function",d=n.toUpperCase(),m=u?r:(p,y)=>o&&o.length?o.some(b=>ag(y[b],d)):y[s]?ag(y[i!=="children"?i:"label"],d):ag(y[l],d),f=u?p=>qy(p):p=>p;return e.forEach(p=>{if(p[s]){if(m(n,f(p)))c.push(p);else{const b=p[s].filter(x=>m(n,f(x)));b.length&&c.push({...p,[s]:b})}return}m(n,f(p))&&c.push(p)}),c},[e,r,o,n,t]);function WD(e){const{key:t,props:{children:n,value:r,...o}}=e;return{key:t,value:r!==void 0?r:t,children:n,...o}}function qR(e,t=!1){return zn(e).map((n,r)=>{if(!a.isValidElement(n)||!n.type)return null;const{type:{isSelectOptGroup:o},key:s,props:{children:i,...l}}=n;return t||!o?WD(n):{key:`__RC_SELECT_GRP__${s===null?r:s}__`,label:s,...l,options:qR(i)}}).filter(n=>n)}const KD=(e,t,n,r,o)=>a.useMemo(()=>{let s=e;!e&&(s=qR(t));const l=new Map,c=new Map,u=(m,f,p)=>{p&&typeof p=="string"&&m.set(f[p],f)},d=(m,f=!1)=>{for(let p=0;p{u(c,y,b)}),u(c,y,o)):d(y[n.options],!0)}};return d(s),{options:s,valueOptions:l,labelOptions:c}},[e,t,n,r,o]);function CC(e){const t=a.useRef();return t.current=e,a.useCallback((...r)=>t.current(...r),[])}function UD(e,t,n){const{filterOption:r,searchValue:o,optionFilterProp:s,filterSort:i,onSearch:l,autoClearSearchValue:c}=t;return a.useMemo(()=>{const u=typeof e=="object",d={filterOption:r,searchValue:o,optionFilterProp:s,filterSort:i,onSearch:l,autoClearSearchValue:c,...u?e:{}};return[u||n==="combobox"||n==="tags"||n==="multiple"&&e===void 0?!0:e,d]},[n,e,r,o,s,i,l,c])}function tv(){return tv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{id:n,mode:r,prefixCls:o="rc-select",backfill:s,fieldNames:i,showSearch:l,searchValue:c,onSearch:u,autoClearSearchValue:d,filterOption:m,optionFilterProp:f,filterSort:p,onSelect:y,onDeselect:b,onActive:x,popupMatchSelectWidth:v=!0,optionLabelProp:g,options:h,optionRender:$,children:C,defaultActiveFirstOption:N,menuItemSelectedIcon:S,virtual:E,direction:w,listHeight:R=200,listItemHeight:P=20,labelRender:T,value:M,defaultValue:z,labelInValue:B,onChange:F,maxCount:L,classNames:j,styles:O,...A}=e,k={searchValue:c,onSearch:u,autoClearSearchValue:d,filterOption:m,optionFilterProp:f,filterSort:p},[_,D]=UD(l,k,r),{filterOption:V,searchValue:W,optionFilterProp:K,filterSort:q,onSearch:Y,autoClearSearchValue:ee=!0}=D,ie=a.useMemo(()=>K?Array.isArray(K)?K:[K]:[],[K]),ae=jo(n),U=Jy(r),Q=!!(!h&&C),Z=a.useMemo(()=>V===void 0&&r==="combobox"?!1:V,[V,r]),ne=a.useMemo(()=>BR(i,Q),[JSON.stringify(i),Q]),[oe,le]=nn("",W),re=oe||"",X=KD(h,C,ne,ie,g),{valueOptions:se,labelOptions:ge,options:de}=X,Se=a.useCallback(Ie=>DR(Ie).map(te=>{let ye,Ae,Je,St;GD(te)?ye=te:(Ae=te.label,ye=te.value);const ht=se.get(ye);return ht&&(Ae===void 0&&(Ae=ht==null?void 0:ht[g||ne.label]),Je=ht==null?void 0:ht.disabled,St=ht==null?void 0:ht.title),{label:Ae,value:ye,key:ye,disabled:Je,title:St}}),[ne,g,se]),[ue,be]=nn(z,M),Ne=a.useMemo(()=>{var te;const Be=Se(U&&ue===null?[]:ue);return r==="combobox"&&sD((te=Be[0])==null?void 0:te.value)?[]:Be},[ue,Se,r,U]),[we,ze]=HD(Ne,se),he=a.useMemo(()=>{if(!r&&we.length===1){const Ie=we[0];if(Ie.value===null&&(Ie.label===null||Ie.label===void 0))return[]}return we.map(Ie=>({...Ie,label:(typeof T=="function"?T(Ie):Ie.label)??Ie.value}))},[r,we,T]),ke=a.useMemo(()=>new Set(we.map(Ie=>Ie.value)),[we]);a.useEffect(()=>{var Ie;if(r==="combobox"){const Be=(Ie=we[0])==null?void 0:Ie.value;le(oD(Be)?String(Be):"")}},[we]);const Oe=CC((Ie,Be)=>{const te=Be??Ie;return{[ne.value]:Ie,[ne.label]:te}}),Ce=a.useMemo(()=>{if(r!=="tags")return de;const Ie=[...de],Be=te=>se.has(te);return[...we].sort((te,ye)=>te.value{const ye=te.value;Be(ye)||Ie.push(Oe(ye,te.label))}),Ie},[Oe,de,se,we,r]),Me=VD(Ce,ne,re,Z,ie),xe=a.useMemo(()=>{var Be;const Ie=te=>ie.length?ie.some(ye=>(te==null?void 0:te[ye])===re):(te==null?void 0:te.value)===re;return r!=="tags"||!re||Me.some(te=>Ie(te))||Me.some(te=>te[ne.value]===re)||(Be=se.get(re))!=null&&Be.disabled?Me:[Oe(re),...Me]},[Oe,ie,r,Me,re,ne,se]),Ee=Ie=>[...Ie].sort((te,ye)=>q(te,ye,{searchValue:re})).map(te=>Array.isArray(te.options)?{...te,options:te.options.length>0?Ee(te.options):te.options}:te),Ve=a.useMemo(()=>q?Ee(xe):xe,[xe,q,re]),qe=a.useMemo(()=>ZA(Ve,{fieldNames:ne,childrenAsData:Q}),[Ve,ne,Q]),me=Ie=>{const Be=Se(Ie);if(be(Be),F&&(Be.length!==we.length||Be.some((te,ye)=>{var Ae;return((Ae=we[ye])==null?void 0:Ae.value)!==(te==null?void 0:te.value)}))){const te=B?Be.map(({label:Ae,value:Je})=>({label:Ae,value:Je})):Be.map(Ae=>Ae.value),ye=Be.map(Ae=>qy(ze(Ae.value)));F(U?te:te[0],U?ye:ye[0])}},[Re,Te]=a.useState(null),[Ue,Ge]=a.useState(0),Fe=N!==void 0?N:r!=="combobox",et=a.useRef(),ve=a.useCallback((Ie,Be,{source:te="keyboard"}={})=>{Ge(Be),s&&r==="combobox"&&Ie!==null&&te==="keyboard"&&Te(String(Ie));const ye=Promise.resolve().then(()=>{et.current===ye&&(x==null||x(Ie))});et.current=ye},[s,r,x]),je=(Ie,Be,te)=>{const ye=()=>{const Ae=ze(Ie);return[B?{label:Ae==null?void 0:Ae[ne.label],value:Ie}:Ie,qy(Ae)]};if(Be&&y){const[Ae,Je]=ye();y(Ae,Je)}else if(!Be&&b&&te!=="clear"){const[Ae,Je]=ye();b(Ae,Je)}},ce=CC((Ie,Be)=>{let te;const ye=U?Be.selected:!0;ye?te=U?[...we,Ie]:[Ie]:te=we.filter(Ae=>Ae.value!==Ie),me(te),je(Ie,ye),r==="combobox"?Te(""):(!Jy||ee)&&(le(""),Te(""))}),Pe=(Ie,Be)=>{me(Ie);const{type:te,values:ye}=Be;(te==="remove"||te==="clear")&&ye.forEach(Ae=>{je(Ae.value,!1,te)})},pe=(Ie,Be)=>{var te;if(le(Ie),Te(null),Be.source==="submit"){const ye=(Ie||"").trim();if(ye){if((te=se.get(ye))!=null&&te.disabled){le("");return}const Ae=Array.from(new Set([...ke,ye]));me(Ae),je(ye,!0),le("")}return}Be.source!=="blur"&&(r==="combobox"&&me(Ie),Y==null||Y(Ie))},$e=Ie=>{let Be=Ie;r!=="tags"&&(Be=Ie.map(ye=>{const Ae=ge.get(ye);return Ae==null?void 0:Ae.value}).filter(ye=>ye!==void 0)),r==="tags"&&(Be=Be.filter(ye=>{var Ae;return!((Ae=se.get(ye))!=null&&Ae.disabled)}));const te=Array.from(new Set([...ke,...Be]));me(te),te.forEach(ye=>{je(ye,!0)})},_e=a.useMemo(()=>({...X,flattenOptions:qe,onActiveValue:ve,defaultActiveFirstOption:Fe,onSelect:ce,menuItemSelectedIcon:S,rawValues:ke,fieldNames:ne,virtual:E!==!1&&v!==!1,direction:w,listHeight:R,listItemHeight:P,childrenAsData:Q,maxCount:L,optionRender:$,classNames:j,styles:O}),[L,X,qe,ve,Fe,ce,S,ke,ne,E,v,w,R,P,Q,$,j,O]);return a.createElement(wx.Provider,{value:_e},a.createElement($D,tv({},A,{id:ae,prefixCls:o,ref:t,omitDomProps:qD,mode:r,classNames:j,styles:O,displayValues:he,onDisplayValuesChange:Pe,maxCount:L,direction:w,showSearch:_,searchValue:re,onSearch:pe,autoClearSearchValue:ee,onSearchSplit:$e,popupMatchSelectWidth:v,OptionList:FD,emptyOptions:!qe.length,activeValue:Re,activeDescendantId:`${ae}_list_${Ue}`})))}),Px=XD;Px.Option=Ix;Px.OptGroup=Ex;function wC(e,t,n){return e===!1?null:e===!0?n:e&&e[t]!==void 0?e[t]:n}const qa=(e,t,n)=>H({[`${e}-status-success`]:t==="success",[`${e}-status-warning`]:t==="warning",[`${e}-status-error`]:t==="error",[`${e}-status-validating`]:t==="validating",[`${e}-has-feedback`]:n}),nu=(e,t)=>t||e,pi=(e,t)=>e!=null&&e.startsWith("var(")||t!=null&&t.startsWith("var(")?e:new Gt(e).onBackground(t).toHexString(),YD=()=>{const[,e]=Yn(),[t]=Ar("Empty"),{colorBgContainer:n,colorFill:r,colorFillSecondary:o,colorFillTertiary:s,colorTextQuaternary:i}=e,{panelBgColor:l,borderColor:c,detailColor:u,shadowColor:d,iconColor:m}=a.useMemo(()=>({panelBgColor:pi(s,n),borderColor:pi(i,n),detailColor:pi(r,n),shadowColor:pi(o,n),iconColor:n}),[n,r,o,s,i]);return a.createElement("svg",{width:"184",height:"152",viewBox:"0 0 184 152",xmlns:"http://www.w3.org/2000/svg"},a.createElement("title",null,(t==null?void 0:t.description)||"Empty"),a.createElement("g",{fill:"none",fillRule:"evenodd"},a.createElement("g",{transform:"translate(24 31.7)"},a.createElement("ellipse",{fillOpacity:".8",fill:d,cx:"67.8",cy:"106.9",rx:"67.8",ry:"12.7"}),a.createElement("path",{fill:c,d:"M122 69.7 98.1 40.2a6 6 0 0 0-4.6-2.2H42.1a6 6 0 0 0-4.6 2.2l-24 29.5V85H122z"}),a.createElement("path",{fill:l,d:"M33.8 0h68a4 4 0 0 1 4 4v93.3a4 4 0 0 1-4 4h-68a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4"}),a.createElement("path",{fill:u,d:"M42.7 10h50.2a2 2 0 0 1 2 2v25a2 2 0 0 1-2 2H42.7a2 2 0 0 1-2-2V12a2 2 0 0 1 2-2m.2 39.8h49.8a2.3 2.3 0 1 1 0 4.5H42.9a2.3 2.3 0 0 1 0-4.5m0 11.7h49.8a2.3 2.3 0 1 1 0 4.6H42.9a2.3 2.3 0 0 1 0-4.6m79 43.5a7 7 0 0 1-6.8 5.4H20.5a7 7 0 0 1-6.7-5.4l-.2-1.8V69.7h26.3c2.9 0 5.2 2.4 5.2 5.4s2.4 5.4 5.3 5.4h34.8c2.9 0 5.3-2.4 5.3-5.4s2.3-5.4 5.2-5.4H122v33.5q0 1-.2 1.8"})),a.createElement("path",{fill:u,d:"m149.1 33.3-6.8 2.6a1 1 0 0 1-1.3-1.2l2-6.2q-4.1-4.5-4.2-10.4c0-10 10.1-18.1 22.6-18.1S184 8.1 184 18.1s-10.1 18-22.6 18q-6.8 0-12.3-2.8"}),a.createElement("g",{fill:m,transform:"translate(149.7 15.4)"},a.createElement("circle",{cx:"20.7",cy:"3.2",r:"2.8"}),a.createElement("path",{d:"M5.7 5.6H0L2.9.7zM9.3.7h5v5h-5z"}))))},QD=()=>{const[,e]=Yn(),[t]=Ar("Empty"),{colorFill:n,colorFillTertiary:r,colorFillQuaternary:o,colorBgContainer:s}=e,{borderColor:i,shadowColor:l,contentColor:c}=a.useMemo(()=>({borderColor:pi(n,s),shadowColor:pi(r,s),contentColor:pi(o,s)}),[n,r,o,s]);return a.createElement("svg",{width:"64",height:"41",viewBox:"0 0 64 41",xmlns:"http://www.w3.org/2000/svg"},a.createElement("title",null,(t==null?void 0:t.description)||"Empty"),a.createElement("g",{transform:"translate(0 1)",fill:"none",fillRule:"evenodd"},a.createElement("ellipse",{fill:l,cx:"32",cy:"33",rx:"32",ry:"7"}),a.createElement("g",{fillRule:"nonzero",stroke:i},a.createElement("path",{d:"M55 12.8 44.9 1.3Q44 0 42.9 0H21.1q-1.2 0-2 1.3L9 12.8V22h46z"}),a.createElement("path",{d:"M41.6 16c0-1.7 1-3 2.2-3H55v18.1c0 2.2-1.3 3.9-3 3.9H12c-1.7 0-3-1.7-3-3.9V13h11.2c1.2 0 2.2 1.3 2.2 3s1 2.9 2.2 2.9h14.8c1.2 0 2.2-1.4 2.2-3",fill:c}))))},JD=e=>{const{componentCls:t,margin:n,marginXS:r,marginXL:o,fontSize:s,lineHeight:i}=e;return{[t]:{marginInline:r,fontSize:s,lineHeight:i,textAlign:"center",[`${t}-image`]:{height:e.emptyImgHeight,marginBottom:r,opacity:e.opacityImage,img:{height:"100%"},svg:{maxWidth:"100%",height:"100%",margin:"auto"}},[`${t}-description`]:{color:e.colorTextDescription},[`${t}-footer`]:{marginTop:n},"&-normal":{marginBlock:o,color:e.colorTextDescription,[`${t}-description`]:{color:e.colorTextDescription},[`${t}-image`]:{height:e.emptyImgHeightMD}},"&-small":{marginBlock:r,color:e.colorTextDescription,[`${t}-image`]:{height:e.emptyImgHeightSM}}}}},ZD=Tt("Empty",e=>{const{componentCls:t,controlHeightLG:n,calc:r}=e,o=Rt(e,{emptyImgCls:`${t}-img`,emptyImgHeight:r(n).mul(2.5).equal(),emptyImgHeightMD:n,emptyImgHeightSM:r(n).mul(.875).equal()});return JD(o)}),GR=a.createElement(YD,null),XR=a.createElement(QD,null),Go=e=>{const{className:t,rootClassName:n,prefixCls:r,image:o,description:s,children:i,imageStyle:l,style:c,classNames:u,styles:d,...m}=e,{getPrefixCls:f,direction:p,className:y,style:b,classNames:x,styles:v,image:g}=Pt("empty"),h=f("empty",r),[$,C]=ZD(h),N=Mt(b),S=Mt(c),[E,w]=Ot([x,u],[v,N,d,S],{props:e}),[R]=Ar("Empty"),P=typeof s<"u"?s:R==null?void 0:R.description,T=typeof P=="string"?P:"empty",M=o??g??GR;let z=null;return typeof M=="string"?z=a.createElement("img",{draggable:!1,alt:T,src:M}):z=M,a.createElement("div",{className:H($,C,h,y,{[`${h}-normal`]:M===XR,[`${h}-rtl`]:p==="rtl"},t,n,E.root),style:w.root,...m},a.createElement("div",{className:H(`${h}-image`,E.image),style:{...l,...w.image}},z),P&&a.createElement("div",{className:H(`${h}-description`,E.description),style:w.description},P),i&&a.createElement("div",{className:H(`${h}-footer`,E.footer),style:w.footer},i))};Go.PRESENTED_IMAGE_DEFAULT=GR;Go.PRESENTED_IMAGE_SIMPLE=XR;const YR=e=>{const{componentName:t}=e,{getPrefixCls:n}=a.useContext(ct),r=n("empty");switch(t){case"Table":case"List":return J.createElement(Go,{image:Go.PRESENTED_IMAGE_SIMPLE});case"Select":case"TreeSelect":case"Cascader":case"Transfer":case"Mentions":return J.createElement(Go,{image:Go.PRESENTED_IMAGE_SIMPLE,className:`${r}-small`});case"Table.filter":return null;default:return J.createElement(Go,null)}},tl=(e,t,n)=>{const{variant:r,[e]:o}=a.useContext(ct),s=a.useContext(uR),i=o==null?void 0:o.variant;let l;typeof t<"u"?l=t:n===!1?l="borderless":l=s??i??r??"outlined";const c=c_.includes(l);return[l,c]},eF=e=>{const n={overflow:{adjustX:!0,adjustY:!0,shiftY:!0},htmlRegion:e==="scroll"?"scroll":"visible",dynamicInset:!0};return{bottomLeft:{...n,points:["tl","bl"],offset:[0,4]},bottomRight:{...n,points:["tr","br"],offset:[0,4]},topLeft:{...n,points:["bl","tl"],offset:[0,-4]},topRight:{...n,points:["br","tr"],offset:[0,-4]}}};function tF(e,t){return e||eF(t)}const EC=e=>{const{optionHeight:t,optionFontSize:n,optionLineHeight:r,optionPadding:o}=e;return{position:"relative",display:"block",minHeight:t,padding:o,color:e.colorText,fontWeight:"normal",fontSize:n,lineHeight:r,boxSizing:"border-box"}},nF=e=>{const{antCls:t,componentCls:n}=e,r=`${n}-item`,o=`&${t}-slide-up-enter${t}-slide-up-enter-active`,s=`&${t}-slide-up-appear${t}-slide-up-appear-active`,i=`&${t}-slide-up-leave${t}-slide-up-leave-active`,l=`${n}-dropdown-placement-`,c=`${r}-option-selected`;return[{[`${n}-dropdown`]:{...Ft(e),position:"absolute",top:-9999,zIndex:e.zIndexPopup,boxSizing:"border-box",padding:e.paddingXXS,overflow:"hidden",fontSize:e.fontSize,fontVariant:"initial",backgroundColor:e.colorBgElevated,borderRadius:e.borderRadiusLG,outline:"none",boxShadow:e.boxShadowSecondary,[` + ${o}${l}bottomLeft, + ${s}${l}bottomLeft + `]:{animationName:Em},[` + ${o}${l}topLeft, + ${s}${l}topLeft, + ${o}${l}topRight, + ${s}${l}topRight + `]:{animationName:Pm},[`${i}${l}bottomLeft`]:{animationName:Im},[` + ${i}${l}topLeft, + ${i}${l}topRight + `]:{animationName:Nm},"&-hidden":{display:"none"},[r]:{...EC(e),cursor:"pointer",transition:`background-color ${e.motionDurationSlow} ease`,borderRadius:e.borderRadiusSM,"&-group":{color:e.colorTextDescription,fontSize:e.fontSizeSM,cursor:"default"},"&-option":{display:"flex","&-content":{flex:"auto",...ar},"&-state":{flex:"none",display:"flex",alignItems:"center"},[`&-selected:not(${r}-option-disabled)`]:{color:e.optionSelectedColor,fontWeight:e.optionSelectedFontWeight,backgroundColor:e.optionSelectedBg,[`${r}-option-state`]:{color:e.colorPrimary}},[`&-active:not(${r}-option-disabled)`]:{backgroundColor:e.optionActiveBg},[`&-selected${r}-option-active:not(${r}-option-disabled)`]:{backgroundColor:e.controlItemBgActiveHover},"&-disabled":{[`&${r}-option-selected`]:{backgroundColor:e.colorBgContainerDisabled},color:e.colorTextDisabled,cursor:"not-allowed"},"&-grouped":{paddingInlineStart:e.calc(e.controlPaddingHorizontal).mul(2).equal()}},"&-empty":{...EC(e),color:e.colorTextDisabled}},[`${c}:has(+ ${c})`]:{borderEndStartRadius:0,borderEndEndRadius:0,[`& + ${c}`]:{borderStartStartRadius:0,borderStartEndRadius:0}},"&-rtl":{direction:"rtl"}}},No(e,"slide-up"),No(e,"slide-down"),uf(e,"move-up"),uf(e,"move-down")]},rF=e=>{const{antCls:t,componentCls:n}=e,r={background:"transparent"},o=["> input[disabled]","> textarea[disabled]",`> ${n}-input`,`> ${t}-input-affix-wrapper-disabled`,`> ${t}-input-search`].join(", ");return{[`&${n}-customize`]:{border:0,padding:0,fontSize:"inherit",lineHeight:"inherit",[`${n}-placeholder`]:{display:"none"},[`${n}-content`]:{margin:0,padding:0,"&-value":{display:"none"}},[`&${n}-filled ${n}-content`]:{[`${t}-input-filled`]:r},[`&${n}-disabled ${n}-content`]:{[o]:r,"input[disabled], textarea[disabled]":r}}}},IC=4,oF=e=>{const{componentCls:t,calc:n,iconCls:r,paddingXS:o,paddingXXS:s,INTERNAL_FIXED_ITEM_MARGIN:i,lineWidth:l,colorIcon:c,colorIconHover:u,inputPaddingHorizontalBase:d,antCls:m}=e,[f,p]=rn(m,"select");return{"&-multiple":{[f("multi-item-background")]:e.multipleItemBg,[f("multi-item-border-color")]:"transparent",[f("multi-item-border-radius")]:e.borderRadiusSM,[f("multi-item-height")]:e.multipleItemHeight,[f("multi-padding-base")]:`calc((${p("height")} - ${p("multi-item-height")}) / 2)`,[f("multi-padding-vertical")]:`calc(${p("multi-padding-base")} - ${i} - ${l})`,[f("multi-item-padding-horizontal")]:`calc(${d} - ${p("multi-padding-vertical")} - ${l} * 2)`,paddingBlock:p("multi-padding-vertical"),paddingInlineStart:`calc(${p("multi-padding-base")} - ${l})`,[`${t}-prefix`]:{marginInlineStart:p("multi-item-padding-horizontal")},[`${t}-prefix + ${t}-content`]:{[`${t}-placeholder`]:{insetInlineStart:0},[`${t}-content-item${t}-content-item-suffix`]:{marginInlineStart:0}},[`${t}-placeholder`]:{position:"absolute",lineHeight:p("line-height"),insetInlineStart:p("multi-item-padding-horizontal"),width:`calc(100% - ${p("multi-item-padding-horizontal")})`,top:"50%",transform:"translateY(-50%)"},[`${t}-content`]:{flexWrap:"wrap",alignItems:"center",lineHeight:1,"&-item-prefix":{height:p("font-size")},"&-item":{lineHeight:1,maxWidth:`calc(100% - ${IC}px)`},[`${t}-content-item-prefix + ${t}-content-item-suffix, + ${t}-content-item-suffix:first-child`]:{marginInlineStart:p("multi-item-padding-horizontal")},[`${t}-selection-item`]:{lineHeight:`calc(${p("multi-item-height")} - ${l} * 2)`,border:`${l} solid ${p("multi-item-border-color")}`,display:"flex",marginBlock:i,marginInlineEnd:n(i).mul(2).equal(),background:p("multi-item-background"),borderRadius:p("multi-item-border-radius"),paddingInlineStart:o,paddingInlineEnd:s,transition:["height","line-height","padding"].map(y=>`${y} ${e.motionDurationSlow}`).join(","),"&-content":{...ar,marginInlineEnd:s},"&-remove":{...Kc(),display:"inline-flex",alignItems:"center",color:c,fontWeight:"bold",fontSize:10,lineHeight:"inherit",cursor:"pointer",[`> ${r}`]:{verticalAlign:"-0.2em"},"&:hover":{color:u}}},[`${t}-input`]:{lineHeight:n(i).mul(2).add(p("multi-item-height")).equal(),width:"calc(var(--select-input-width, 0) * 1px)",minWidth:IC,maxWidth:"100%",transition:`line-height ${e.motionDurationSlow}`}},[`&${t}-sm`]:{[f("multi-item-height")]:e.multipleItemHeightSM,[f("multi-item-border-radius")]:e.borderRadiusXS},[`&${t}-lg`]:{[f("multi-item-height")]:e.multipleItemHeightLG,[f("multi-item-border-radius")]:e.borderRadius},[`&${t}-filled`]:{[f("multi-item-border-color")]:e.colorSplit,[f("multi-item-background")]:e.colorBgContainer,[`&${t}-disabled`]:{[f("multi-item-border-color")]:"transparent"}}}}},lg=(e,t)=>{const{componentCls:n,antCls:r}=e,[o]=rn(r,"select"),{border:s,borderHover:i,borderActive:l,borderOutline:c}=t,u=t.background||e.selectorBg||e.colorBgContainer;return{[o("border-color")]:s,[o("background-color")]:u,[o("affix-color")]:t.affixColor,[`&:not(${n}-disabled)`]:{"&:hover":{[o("border-color")]:i,[o("background-color")]:t.backgroundHover||u},[`&${n}-focused`]:{[o("border-color")]:l,[o("background-color")]:t.backgroundActive||u,boxShadow:`0 0 0 ${G(e.controlOutlineWidth)} ${c}`}},[`&${n}-disabled`]:{[o("border-color")]:t.borderDisabled||t.border,[o("background-color")]:t.backgroundDisabled||t.background}}},Vu=(e,t,n,r,o,s)=>{const{componentCls:i}=e;return{[`&${i}-${t}`]:[lg(e,n),{[`&${i}-status-error`]:lg(e,{...n,...r}),[`&${i}-status-warning`]:lg(e,{...n,...o})},s]}},cg=(e,t)=>({outline:`${G(e.lineWidth)} ${e.lineType} ${t}`,outlineOffset:G(e.calc(e.lineWidth).mul(-1).equal()),transition:["outline-offset","outline"].map(n=>`${n} 0s`).join(", ")}),sF=e=>{const{componentCls:t,fontHeight:n,controlHeight:r,fontSizeIcon:o,showArrowPaddingInlineEnd:s,iconCls:i,antCls:l,max:c,calc:u}=e,[d,m]=rn(l,"select"),f=c(u(s).sub(o).equal(),0);return{[t]:[{[d("border-radius")]:e.borderRadius,[d("border-color")]:"#000",[d("border-size")]:e.lineWidth,[d("background-color")]:e.colorBgContainer,[d("font-size")]:e.fontSize,[d("line-height")]:e.lineHeight,[d("font-height")]:n,[d("color")]:e.colorText,[d("affix-color")]:e.colorText,[d("height")]:r,[d("padding-horizontal")]:u(e.paddingSM).sub(e.lineWidth).equal(),[d("padding-vertical")]:`calc((${m("height")} - ${m("font-height")}) / 2 - ${m("border-size")})`,...Ft(e),display:"inline-flex",flexWrap:"nowrap",position:"relative",transition:`all ${e.motionDurationSlow}`,alignItems:"flex-start",outline:0,cursor:"pointer",borderRadius:m("border-radius"),borderWidth:m("border-size"),borderStyle:e.lineType,borderColor:m("border-color"),background:m("background-color"),fontSize:m("font-size"),lineHeight:m("line-height"),color:m("color"),paddingInline:m("padding-horizontal"),paddingBlock:m("padding-vertical"),[`${t}-prefix`]:{color:m("affix-color"),flex:"none",lineHeight:1},[`${t}-placeholder`]:{...ar,color:e.colorTextPlaceholder,pointerEvents:"none",zIndex:1},[`${t}-content`]:{flex:"auto",minWidth:0,position:"relative",display:"flex",marginInlineEnd:f,"&:before":{content:'"\\a0"',width:0,overflow:"hidden"},"&-value":{visibility:"inherit"},"input[readonly]":{cursor:"inherit",caretColor:"transparent"}},[`${t}-suffix`]:{flex:"none",color:e.colorTextQuaternary,fontSize:e.fontSizeIcon,lineHeight:1,"> :not(:last-child)":{marginInlineEnd:e.marginXS}},[`${t}-prefix, ${t}-suffix`]:{alignSelf:"center",[i]:{verticalAlign:"top"}},"&-disabled":{background:e.colorBgContainerDisabled,color:e.colorTextDisabled,cursor:"not-allowed",input:{cursor:"not-allowed"}},"&-sm":{[d("height")]:e.controlHeightSM,[d("padding-horizontal")]:u(e.paddingXS).sub(e.lineWidth).equal(),[d("border-radius")]:e.borderRadiusSM,[`${t}-clear`]:{insetInlineEnd:m("padding-horizontal")}},"&-lg":{[d("height")]:e.controlHeightLG,[d("font-size")]:e.fontSizeLG,[d("line-height")]:e.lineHeightLG,[d("font-height")]:e.fontHeightLG,[d("border-radius")]:e.borderRadiusLG}},{[`&:not(${t}-customize)`]:{[`${t}-input`]:{outline:"none",background:"transparent",appearance:"none",border:0,margin:0,padding:0,color:m("color"),fontFamily:"inherit",fontSize:"inherit","&::-webkit-search-cancel-button":{display:"none",appearance:"none"}}}},{[`&-single:not(${t}-customize)`]:{[`${t}-input`]:{position:"absolute",inset:0,lineHeight:"inherit"},[`${t}-content`]:{...ar,alignSelf:"center","&-has-value":{display:"block","&:before":{display:"none"}},"&-has-search-value":{color:"transparent",[`> *:not(${t}-input)`]:{opacity:0}},"&-value":{transition:`all ${e.motionDurationMid} ${e.motionEaseInOut}`,zIndex:1,opacity:1}},[`&${t}-open ${t}-content`]:{"&-has-value":{opacity:.25},"&-has-search-value":{opacity:1,transition:`opacity ${e.motionDurationMid} ${e.motionEaseInOut}`,color:"transparent",[`> *:not(${t}-input)`]:{opacity:0}}}}},{[`&-show-search:not(${t}-customize-input):not(${t}-disabled)`]:{cursor:"text"}},oF(e),Vu(e,"outlined",{border:e.colorBorder,borderHover:e.hoverBorderColor,borderActive:e.activeBorderColor,borderOutline:e.activeOutlineColor,borderDisabled:e.colorBorderDisabled},{border:e.colorError,borderHover:e.colorErrorBorderHover,borderActive:e.colorError,borderOutline:e.colorErrorOutline,affixColor:e.colorErrorAffix},{border:e.colorWarning,borderHover:e.colorWarningHover,borderActive:e.colorWarning,borderOutline:e.colorWarningOutline,affixColor:e.colorWarningAffix}),Vu(e,"filled",{border:"transparent",borderHover:"transparent",borderActive:e.activeBorderColor,borderOutline:"transparent",borderDisabled:e.colorBorderDisabled,background:e.colorFillTertiary,backgroundHover:e.colorFillSecondary,backgroundActive:e.colorBgContainer},{color:e.colorErrorText,background:e.colorErrorBg,backgroundHover:e.colorErrorBgHover,borderActive:e.colorError},{background:e.colorWarningBg,backgroundHover:e.colorWarningBgHover,borderActive:e.colorWarning}),Vu(e,"borderless",{border:"transparent",borderHover:"transparent",borderActive:"transparent",borderOutline:"transparent",background:"transparent"},{},{},{[`&:not(${t}-disabled):has(input:focus-visible), &:not(${t}-disabled):has(textarea:focus-visible)`]:cg(e,e.activeBorderColor),[`&${t}-status-error:not(${t}-disabled):has(input:focus-visible), &${t}-status-error:not(${t}-disabled):has(textarea:focus-visible)`]:cg(e,e.colorError),[`&${t}-status-warning:not(${t}-disabled):has(input:focus-visible), &${t}-status-warning:not(${t}-disabled):has(textarea:focus-visible)`]:cg(e,e.colorWarning)}),Vu(e,"underlined",{border:e.colorBorder,borderHover:e.hoverBorderColor,borderActive:e.activeBorderColor,borderOutline:"transparent"},{border:e.colorError,borderHover:e.colorErrorBorderHover,borderActive:e.colorError},{border:e.colorWarning,borderHover:e.colorWarningHover,borderActive:e.colorWarning},{borderRadius:0,borderTopColor:"transparent",borderInlineColor:"transparent"}),rF(e)]}},iF=e=>{const{fontSize:t,lineHeight:n,lineWidth:r,controlHeight:o,controlHeightSM:s,controlHeightLG:i,paddingXXS:l,controlPaddingHorizontal:c,zIndexPopupBase:u,colorText:d,fontWeightStrong:m,controlItemBgActive:f,controlItemBgHover:p,colorBgContainer:y,colorFillSecondary:b,colorBgContainerDisabled:x,colorTextDisabled:v,colorPrimaryHover:g,colorPrimary:h,controlOutline:$}=e,C=l*2,N=r*2,S=Math.min(o-C,o-N),E=Math.min(s-C,s-N),w=Math.min(i-C,i-N);return{INTERNAL_FIXED_ITEM_MARGIN:Math.floor(l/2),zIndexPopup:u+50,optionSelectedColor:d,optionSelectedFontWeight:m,optionSelectedBg:f,optionActiveBg:p,optionPadding:`${(o-t*n)/2}px ${c}px`,optionFontSize:t,optionLineHeight:n,optionHeight:o,selectorBg:y,clearBg:y,singleItemHeightLG:i,multipleItemBg:b,multipleItemBorderColor:"transparent",multipleItemHeight:S,multipleItemHeightSM:E,multipleItemHeightLG:w,multipleSelectorBgDisabled:x,multipleItemColorDisabled:v,multipleItemBorderColorDisabled:"transparent",showArrowPaddingInlineEnd:Math.ceil(e.fontSize*1.25),hoverBorderColor:g,activeBorderColor:h,activeOutlineColor:$,selectAffixPadding:l}},aF=e=>{const{antCls:t,componentCls:n,motionDurationMid:r,inputPaddingHorizontalBase:o}=e,s={[`${n}-clear`]:{opacity:1,background:e.colorBgBase,borderRadius:"50%"}};return{[n]:{...Ft(e),[`${n}-selection-item`]:{flex:1,fontWeight:"normal",position:"relative",userSelect:"none",...ar,[`> ${t}-typography`]:{display:"inline"}},[`${n}-prefix`]:{flex:"none",marginInlineEnd:e.selectAffixPadding},[`${n}-clear`]:{position:"absolute",top:"50%",insetInlineStart:"auto",insetInlineEnd:o,zIndex:1,display:"inline-block",width:e.fontSizeIcon,height:e.fontSizeIcon,marginTop:e.calc(e.fontSizeIcon).mul(-1).div(2).equal(),color:e.colorTextQuaternary,fontSize:e.fontSizeIcon,fontStyle:"normal",lineHeight:1,textAlign:"center",textTransform:"none",cursor:"pointer",opacity:0,transition:["color","opacity"].map(i=>`${i} ${r} ease`).join(", "),textRendering:"auto",transform:"translateZ(0)","&:before":{display:"block"},"&:hover":{color:e.colorIcon}},"@media(hover:none)":s,"&:hover":s},[`${n}-status`]:{"&-error, &-warning, &-success, &-validating":{[`&${n}-has-feedback`]:{[`${n}-clear`]:{insetInlineEnd:e.calc(o).add(e.fontSize).add(e.paddingXS).equal()}}}}}},lF=e=>{const{componentCls:t}=e;return[{[t]:{[`&${t}-in-form-item`]:{width:"100%"}}},aF(e),nF(e),{[`${t}-rtl`]:{direction:"rtl"}},Qc(e,{focusElCls:`${t}-focused`})]},cF=Tt("Select",(e,{rootPrefixCls:t})=>{const n=Rt(e,{rootPrefixCls:t,inputPaddingHorizontalBase:e.calc(e.paddingSM).sub(e.lineWidth).equal(),multipleSelectItemHeight:e.multipleItemHeight,selectHeight:e.controlHeight});return[lF(n),sF(n)]},iF,{unitless:{optionLineHeight:!0,optionSelectedFontWeight:!0}});var QR={};Object.defineProperty(QR,"__esModule",{value:!0});var uF={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"}}]},name:"check",theme:"outlined"},dF=QR.default=uF;function nv(){return nv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,nv({},e,{ref:t,icon:dF})),Nx=a.forwardRef(fF);var JR={};Object.defineProperty(JR,"__esModule",{value:!0});var mF={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"}}]},name:"down",theme:"outlined"},pF=JR.default=mF;function rv(){return rv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,rv({},e,{ref:t,icon:pF})),Rx=a.forwardRef(gF);var ZR={};Object.defineProperty(ZR,"__esModule",{value:!0});var hF={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"}}]},name:"search",theme:"outlined"},yF=ZR.default=hF;function ov(){return ov=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,ov({},e,{ref:t,icon:yF})),Tx=a.forwardRef(vF);function bF({suffixIcon:e,contextSuffixIcon:t,clearIcon:n,contextClearIcon:r,menuItemSelectedIcon:o,contextMenuItemSelectedIcon:s,removeIcon:i,contextRemoveIcon:l,loading:c,loadingIcon:u,contextLoadingIcon:d,searchIcon:m,contextSearchIcon:f,multiple:p,hasFeedback:y,showSuffixIcon:b,feedbackIcon:x,showArrow:v,componentName:g}){return a.useMemo(()=>{const h=Ur(n,r,a.createElement(Bi,null)),$=E=>e===null&&!y&&!v?null:a.createElement(a.Fragment,null,b!==!1&&E,y&&x);let C=null;e!==void 0?C=$(e):c?C=$(Ur(u,d,a.createElement(ki,{spin:!0}))):C=({open:E,showSearch:w})=>$(E&&w?Ur(m,f,a.createElement(Tx,null)):Ur(t,a.createElement(Rx,null)));const N=Ur(o,s,p?a.createElement(Nx,null):null),S=Ur(i,l,a.createElement(Us,null));return{clearIcon:h,suffixIcon:C,itemIcon:N,removeIcon:S}},[e,t,n,r,o,s,i,l,c,u,d,m,f,p,y,b,x,v])}function xF(e){return J.useMemo(()=>{if(e)return(...t)=>J.createElement(Ri,{space:!0},e.apply(void 0,t))},[e])}function $F(e,t){return t!==void 0?t:e!==null}const e4="SECRET_COMBOBOX_MODE_DO_NOT_USE",SF=(e,t)=>{var Le,Ke;const{prefixCls:n,bordered:r,className:o,rootClassName:s,getPopupContainer:i,popupClassName:l,dropdownClassName:c,listHeight:u=256,placement:d,listItemHeight:m,size:f,disabled:p,notFoundContent:y,status:b,builtinPlacements:x,dropdownMatchSelectWidth:v,popupMatchSelectWidth:g,direction:h,style:$,allowClear:C,variant:N,popupStyle:S,dropdownStyle:E,transitionName:w,tagRender:R,maxCount:P,prefix:T,dropdownRender:M,popupRender:z,onDropdownVisibleChange:B,onOpenChange:F,styles:L,classNames:j,clearIcon:O,showSearch:A,...k}=e,{getPopupContainer:_,getPrefixCls:D,renderEmpty:V,direction:W,virtual:K,popupMatchSelectWidth:q,popupOverflow:Y}=a.useContext(ct),{showSearch:ee,allowClear:ie,style:ae,styles:U,className:Q,classNames:Z,clearIcon:ne,loadingIcon:oe,menuItemSelectedIcon:le,removeIcon:re,suffixIcon:X}=Pt("select"),[,se]=Yn(),ge=m??(se==null?void 0:se.controlHeight),de=D("select",n),Se=D(),ue=h??W,{compactSize:be,compactItemClassnames:Ne}=qs(de,ue),[we,ze]=tl("select",N,r),he=on(de),[ke,Oe]=cF(de,he),Ce=a.useMemo(()=>{const{mode:lt}=e;if(lt!=="combobox")return lt===e4?"combobox":lt},[e.mode]),Me=Ce==="multiple"||Ce==="tags",xe=$F(e.suffixIcon,e.showArrow),Ee=g??v??q,Ve=xF(z||M),qe=F||B,{status:me,hasFeedback:Re,isFormItemInput:Te,feedbackIcon:Ue}=a.useContext(Hn),Ge=nu(me,b);let Fe;y!==void 0?Fe=y:Ce==="combobox"?Fe=null:Fe=(V==null?void 0:V("Select"))||a.createElement(YR,{componentName:"Select"});const{suffixIcon:et,itemIcon:ve,removeIcon:je,clearIcon:ce}=bF({...k,multiple:Me,hasFeedback:Re,feedbackIcon:Ue,showSuffixIcon:xe,componentName:"Select",clearIcon:O,searchIcon:wC(A,"searchIcon"),contextClearIcon:ne,contextLoadingIcon:oe,contextMenuItemSelectedIcon:le,contextRemoveIcon:re,contextSearchIcon:wC(ee,"searchIcon"),contextSuffixIcon:X}),Pe=C??ie,pe=Pe===!0?{clearIcon:ce}:Pe,$e=A??ee,_e=Dt(k,["suffixIcon","itemIcon"]),Ie=Cn(lt=>f??be??lt),Be=a.useContext(cr),te=p??Be,ye={...e,variant:we,status:Ge,disabled:te,size:Ie},Ae=Mt(ae),Je=Mt($),[St,ht]=Ot([Z,j],[U,Ae,L,Je],{props:ye},{popup:{_default:"root"}}),Nt=H(St.popup.root,l,c,{[`${de}-dropdown-${ue}`]:ue==="rtl"},s,Oe,he,ke),yt={...(Le=ht.popup)==null?void 0:Le.root,...S??E},at=H({[`${de}-lg`]:Ie==="large",[`${de}-sm`]:Ie==="small",[`${de}-rtl`]:ue==="rtl",[`${de}-${we}`]:ze,[`${de}-in-form-item`]:Te},qa(de,Ge,Re),Ne,Q,o,St.root,s,Oe,he,ke),Ze=a.useMemo(()=>d!==void 0?d:ue==="rtl"?"bottomRight":"bottomLeft",[d,ue]),[De]=Xc("SelectLike",((Ke=ht.popup.root)==null?void 0:Ke.zIndex)??yt.zIndex);return a.createElement(Px,{ref:t,virtual:K,classNames:St,styles:ht,showSearch:$e,..._e,style:ht.root,popupMatchSelectWidth:Ee,transitionName:ks(Se,"slide-up",w),builtinPlacements:tF(x,Y),listHeight:u,listItemHeight:ge,mode:Ce,prefixCls:de,placement:Ze,direction:ue,prefix:T,suffixIcon:et,menuItemSelectedIcon:ve,removeIcon:je,allowClear:pe,notFoundContent:Fe,className:at,getPopupContainer:i||_,popupClassName:Nt,disabled:te,popupStyle:{...ht.popup.root,...yt,zIndex:De},maxCount:Me?P:void 0,tagRender:Me?R:void 0,popupRender:Ve,onPopupVisibleChange:qe})},dn=a.forwardRef(SF),CF=_R(dn,"popupAlign");dn.SECRET_COMBOBOX_MODE_DO_NOT_USE=e4;dn.Option=Ix;dn.OptGroup=Ex;dn._InternalPanelDoNotUseOrYouWillBeFired=CF;const As=["xxxl","xxl","xl","lg","md","sm","xs"],t4=[].concat(As).reverse(),wF=e=>({xs:`(max-width: ${e.screenXSMax}px)`,sm:`(min-width: ${e.screenSM}px)`,md:`(min-width: ${e.screenMD}px)`,lg:`(min-width: ${e.screenLG}px)`,xl:`(min-width: ${e.screenXL}px)`,xxl:`(min-width: ${e.screenXXL}px)`,xxxl:`(min-width: ${e.screenXXXL}px)`}),EF=e=>{const t=e,n=[].concat(As).reverse();return n.forEach((r,o)=>{const s=r.toUpperCase(),i=`screen${s}Min`,l=`screen${s}`;if(!(t[i]<=t[l]))throw new Error(`${i}<=${l} fails : !(${t[i]}<=${t[l]})`);if(o{const[,e]=Yn(),t=wF(EF(e));return J.useMemo(()=>{const n=new Map;let r=-1,o={};return{responsiveMap:t,matchHandlers:{},dispatch(s){return o=s,n.forEach(i=>{i(o)}),n.size>=1},subscribe(s){return n.size||this.register(),r+=1,n.set(r,s),s(o),r},unsubscribe(s){n.delete(s),n.size||this.unregister()},register(){Object.entries(t).forEach(([s,i])=>{const l=({matches:u})=>{this.dispatch({...o,[s]:u})},c=window.matchMedia(i);bt(c.addEventListener)&&c.addEventListener("change",l),this.matchHandlers[i]={mql:c,listener:l},l(c)})},unregister(){Object.values(t).forEach(s=>{const i=this.matchHandlers[s];bt(i==null?void 0:i.mql.removeEventListener)&&i.mql.removeEventListener("change",i==null?void 0:i.listener)}),n.clear()}}},[t])};function Mm(e=!0,t={}){const n=a.useRef(t),[,r]=RN(),o=IF();return It(()=>{const s=o.subscribe(i=>{n.current=i,e&&r()});return()=>o.unsubscribe(s)},[e]),n.current}const sv=a.createContext({}),PF=e=>{const{antCls:t,componentCls:n,iconCls:r,avatarBg:o,avatarColor:s,containerSize:i,containerSizeLG:l,containerSizeSM:c,textFontSize:u,textFontSizeLG:d,textFontSizeSM:m,iconFontSize:f,iconFontSizeLG:p,iconFontSizeSM:y,borderRadius:b,borderRadiusLG:x,borderRadiusSM:v,lineWidth:g,lineType:h}=e,$=(C,N,S,E)=>({width:C,height:C,borderRadius:"50%",fontSize:N,[`&${n}-square`]:{borderRadius:E},[`&${n}-icon`]:{fontSize:S,[`> ${r}`]:{margin:0}}});return{[n]:{...Ft(e),position:"relative",display:"inline-flex",justifyContent:"center",alignItems:"center",overflow:"hidden",color:s,whiteSpace:"nowrap",textAlign:"center",verticalAlign:"middle",background:o,border:`${G(g)} ${h} transparent`,"&-image":{background:"transparent"},[`${t}-image-img`]:{display:"block"},...$(i,u,f,b),"&-lg":{...$(l,d,p,x)},"&-sm":{...$(c,m,y,v)},"> img":{display:"block",width:"100%",height:"100%",objectFit:"cover"}}}},NF=e=>{const{componentCls:t,groupBorderColor:n,groupOverlapping:r,groupSpace:o}=e;return{[`${t}-group`]:{display:"inline-flex",[t]:{borderColor:n},"> *:not(:first-child)":{marginInlineStart:r}},[`${t}-group-popover`]:{[`${t} + ${t}`]:{marginInlineStart:o}}}},RF=e=>{const{controlHeight:t,controlHeightLG:n,controlHeightSM:r,fontSize:o,fontSizeLG:s,fontSizeXL:i,fontSizeHeading3:l,marginXS:c,marginXXS:u,colorBorderBg:d}=e;return{containerSize:t,containerSizeLG:n,containerSizeSM:r,textFontSize:o,textFontSizeLG:o,textFontSizeSM:o,iconFontSize:Math.round((s+i)/2),iconFontSizeLG:l,iconFontSizeSM:o,groupSpace:u,groupOverlapping:-c,groupBorderColor:d}},n4=Tt("Avatar",e=>{const{colorTextLightSolid:t,colorTextPlaceholder:n}=e,r=Rt(e,{avatarBg:n,avatarColor:t});return[PF(r),NF(r)]},RF),r4=a.forwardRef((e,t)=>{const{prefixCls:n,shape:r,size:o,src:s,srcSet:i,icon:l,className:c,rootClassName:u,style:d,alt:m,draggable:f,children:p,crossOrigin:y,gap:b=4,onError:x,...v}=e,[g,h]=a.useState(1),[$,C]=a.useState(!1),[N,S]=a.useState(!0),E=a.useRef(null),w=a.useRef(null),R=Tn(t,E),{getPrefixCls:P,className:T,style:M}=Pt("avatar"),z=a.useContext(sv),B=()=>{if(!w.current||!E.current)return;const ae=w.current.offsetWidth,U=E.current.offsetWidth;ae!==0&&U!==0&&b*2{C(!0)},[]),a.useEffect(()=>{S(!0),h(1)},[s]),a.useEffect(B,[b]);const F=()=>{(x==null?void 0:x())!==!1&&S(!1)},L=Cn(ae=>o??(z==null?void 0:z.size)??ae??"medium"),j=Object.keys(dt(L)?L||{}:{}).some(ae=>As.includes(ae)),O=Mm(j),A=a.useMemo(()=>{if(!dt(L))return{};const ae=As.find(Q=>O[Q]),U=L[ae];return U?{width:U,height:U,fontSize:U&&(l||p)?U/2:18}:{}},[O,L,l,p]),k=P("avatar",n),_=on(k),[D,V]=n4(k,_),W=H({[`${k}-lg`]:L==="large",[`${k}-sm`]:L==="small"}),K=a.isValidElement(s),q=r||(z==null?void 0:z.shape)||"circle",Y=H(k,W,T,`${k}-${q}`,{[`${k}-image`]:K||s&&N,[`${k}-icon`]:!!l},V,_,c,u,D),ee=Rn(L)?{width:L,height:L,fontSize:l?L/2:18}:{};let ie;if(typeof s=="string"&&N)ie=a.createElement("img",{src:s,draggable:f,srcSet:i,onError:F,alt:m,crossOrigin:y});else if(K)ie=s;else if(l)ie=l;else if($||g!==1){const ae=`scale(${g})`,U={msTransform:ae,WebkitTransform:ae,transform:ae};ie=a.createElement(ir,{onResize:B},a.createElement("span",{className:`${k}-string`,ref:w,style:U},p))}else ie=a.createElement("span",{className:`${k}-string`,style:{opacity:0},ref:w},p);return a.createElement("span",{...v,style:{...ee,...A,...M,...d},className:Y,ref:R},ie)}),Ga=e=>$n(e)?bt(e)?e():e:null,Mx=e=>{const{children:t,prefixCls:n,id:r,classNames:o,styles:s,className:i,style:l}=e;return a.createElement("div",{id:r,className:H(`${n}-container`,o==null?void 0:o.container,i),style:{...s==null?void 0:s.container,...l},role:"tooltip"},typeof t=="function"?t():t)},ea={shiftX:64,adjustY:1},ta={adjustX:1,shiftY:!0},Hr=[0,0],TF={left:{points:["cr","cl"],overflow:ta,offset:[-4,0],targetOffset:Hr},right:{points:["cl","cr"],overflow:ta,offset:[4,0],targetOffset:Hr},top:{points:["bc","tc"],overflow:ea,offset:[0,-4],targetOffset:Hr},bottom:{points:["tc","bc"],overflow:ea,offset:[0,4],targetOffset:Hr},topLeft:{points:["bl","tl"],overflow:ea,offset:[0,-4],targetOffset:Hr},leftTop:{points:["tr","tl"],overflow:ta,offset:[-4,0],targetOffset:Hr},topRight:{points:["br","tr"],overflow:ea,offset:[0,-4],targetOffset:Hr},rightTop:{points:["tl","tr"],overflow:ta,offset:[4,0],targetOffset:Hr},bottomRight:{points:["tr","br"],overflow:ea,offset:[0,4],targetOffset:Hr},rightBottom:{points:["bl","br"],overflow:ta,offset:[4,0],targetOffset:Hr},bottomLeft:{points:["tl","bl"],overflow:ea,offset:[0,4],targetOffset:Hr},leftBottom:{points:["br","bl"],overflow:ta,offset:[-4,0],targetOffset:Hr}};function iv(){return iv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{trigger:n=["hover"],mouseEnterDelay:r=0,mouseLeaveDelay:o=.1,prefixCls:s="rc-tooltip",children:i,onVisibleChange:l,afterVisibleChange:c,motion:u,placement:d="right",align:m={},destroyOnHidden:f=!1,defaultVisible:p,getTooltipContainer:y,arrowContent:b,overlay:x,id:v,showArrow:g=!0,classNames:h,styles:$,...C}=e,N=jo(v),S=a.useRef(null);a.useImperativeHandle(t,()=>S.current);const E={...C};"visible"in e&&(E.popupVisible=e.visible);const w=a.useMemo(()=>{if(!g)return!1;const P=g===!0?{}:g;return{...P,className:H(P.className,h==null?void 0:h.arrow),style:{...P.style,...$==null?void 0:$.arrow},content:P.content??b}},[g,h==null?void 0:h.arrow,$==null?void 0:$.arrow,b]),R=({open:P})=>{const T=a.Children.only(i),M={"aria-describedby":x&&P?N:void 0};return a.cloneElement(T,M)};return a.createElement(bm,iv({popupClassName:h==null?void 0:h.root,prefixCls:s,popup:a.createElement(Mx,{key:"content",prefixCls:s,id:N,classNames:h,styles:$},x),action:n,builtinPlacements:TF,popupPlacement:d,ref:S,popupAlign:m,getPopupContainer:y,onOpenChange:l,afterOpenChange:c,popupMotion:u,defaultPopupVisible:p,autoDestroy:f,mouseLeaveDelay:o,popupStyle:$==null?void 0:$.root,mouseEnterDelay:r,arrow:w,uniqueContainerClassName:h==null?void 0:h.uniqueContainer,uniqueContainerStyle:$==null?void 0:$.uniqueContainer},E),R)});function Ox(e){const{sizePopupArrow:t,borderRadiusXS:n,borderRadiusOuter:r}=e,o=t/2,s=0,i=o,l=r*1/Math.sqrt(2),c=o-r*(1-1/Math.sqrt(2)),u=o-n*(1/Math.sqrt(2)),d=r*(Math.sqrt(2)-1)+n*(1/Math.sqrt(2)),m=2*o-u,f=d,p=2*o-l,y=c,b=2*o-s,x=i,v=o*Math.sqrt(2)+r*(Math.sqrt(2)-2),g=r*(Math.sqrt(2)-1),h=`polygon(${g}px 100%, 50% ${g}px, ${2*o-g}px 100%, ${g}px 100%)`,$=`path('M ${s} ${i} A ${r} ${r} 0 0 0 ${l} ${c} L ${u} ${d} A ${n} ${n} 0 0 1 ${m} ${f} L ${p} ${y} A ${r} ${r} 0 0 0 ${b} ${x} Z')`;return{arrowShadowWidth:v,arrowPath:$,arrowPolygon:h}}const OF=(e,t,n)=>{const{sizePopupArrow:r,arrowPolygon:o,arrowPath:s,arrowShadowWidth:i,borderRadiusXS:l,calc:c}=e,u={content:'""',position:"absolute",width:i,height:i,bottom:0,insetInline:0,margin:"auto",borderRadius:{_skip_check_:!0,value:`0 0 ${G(l)} 0`},transform:"translateY(50%) rotate(-135deg)",zIndex:0,background:"transparent"};return n&&(u.boxShadow=n),{pointerEvents:"none",width:r,height:r,overflow:"hidden","&::before":{position:"absolute",bottom:0,insetInlineStart:0,width:r,height:c(r).div(2).equal(),background:t,clipPath:{_multi_value_:!0,value:[o,s]},content:'""'},"&::after":u}},o4=8;function Om(e){const{contentRadius:t,limitVerticalRadius:n}=e,r=t>12?t+2:12;return{arrowOffsetHorizontal:r,arrowOffsetVertical:n?o4:r}}const _x=(e,t,n)=>{const{componentCls:r,boxShadowPopoverArrow:o,arrowOffsetVertical:s,arrowOffsetHorizontal:i,antCls:l}=e,[c]=rn(l,"tooltip"),{arrowDistance:u=0,arrowShadow:d=!0}=n||{};return{[r]:{[`${r}-arrow`]:[{position:"absolute",zIndex:1,display:"block",...OF(e,t,d?o:!1),"&:before":{background:t}}],[[`&-placement-top > ${r}-arrow`,`&-placement-topLeft > ${r}-arrow`,`&-placement-topRight > ${r}-arrow`].join(",")]:{bottom:u,transform:"translateY(100%) rotate(180deg)"},[`&-placement-top > ${r}-arrow`]:{left:{_skip_check_:!0,value:"50%"},transform:"translateX(-50%) translateY(100%) rotate(180deg)"},"&-placement-topLeft":{[c("arrow-offset-x")]:i,[`> ${r}-arrow`]:{left:{_skip_check_:!0,value:i}}},"&-placement-topRight":{[c("arrow-offset-x")]:`calc(100% - ${G(i)})`,[`> ${r}-arrow`]:{right:{_skip_check_:!0,value:i}}},[[`&-placement-bottom > ${r}-arrow`,`&-placement-bottomLeft > ${r}-arrow`,`&-placement-bottomRight > ${r}-arrow`].join(",")]:{top:u,transform:"translateY(-100%)"},[`&-placement-bottom > ${r}-arrow`]:{left:{_skip_check_:!0,value:"50%"},transform:"translateX(-50%) translateY(-100%)"},"&-placement-bottomLeft":{[c("arrow-offset-x")]:i,[`> ${r}-arrow`]:{left:{_skip_check_:!0,value:i}}},"&-placement-bottomRight":{[c("arrow-offset-x")]:`calc(100% - ${G(i)})`,[`> ${r}-arrow`]:{right:{_skip_check_:!0,value:i}}},[[`&-placement-left > ${r}-arrow`,`&-placement-leftTop > ${r}-arrow`,`&-placement-leftBottom > ${r}-arrow`].join(",")]:{right:{_skip_check_:!0,value:u},transform:"translateX(100%) rotate(90deg)"},[`&-placement-left > ${r}-arrow`]:{top:{_skip_check_:!0,value:"50%"},transform:"translateY(-50%) translateX(100%) rotate(90deg)"},[`&-placement-leftTop > ${r}-arrow`]:{top:s},[`&-placement-leftBottom > ${r}-arrow`]:{bottom:s},[[`&-placement-right > ${r}-arrow`,`&-placement-rightTop > ${r}-arrow`,`&-placement-rightBottom > ${r}-arrow`].join(",")]:{left:{_skip_check_:!0,value:u},transform:"translateX(-100%) rotate(-90deg)"},[`&-placement-right > ${r}-arrow`]:{top:{_skip_check_:!0,value:"50%"},transform:"translateY(-50%) translateX(-100%) rotate(-90deg)"},[`&-placement-rightTop > ${r}-arrow`]:{top:s},[`&-placement-rightBottom > ${r}-arrow`]:{bottom:s}}}};function _F(e,t,n,r){if(r===!1)return{adjustX:!1,adjustY:!1};const o=dt(r)?r:{},s={};switch(e){case"top":case"bottom":s.shiftX=t.arrowOffsetHorizontal*2+n,s.shiftY=!0,s.adjustY=!0;break;case"left":case"right":s.shiftY=t.arrowOffsetVertical*2+n,s.shiftX=!0,s.adjustX=!0;break}const i={...s,...o};return i.shiftX||(i.adjustX=!0),i.shiftY||(i.adjustY=!0),i}const PC={left:{points:["cr","cl"]},right:{points:["cl","cr"]},top:{points:["bc","tc"]},bottom:{points:["tc","bc"]},topLeft:{points:["bl","tl"]},leftTop:{points:["tr","tl"]},topRight:{points:["br","tr"]},rightTop:{points:["tl","tr"]},bottomRight:{points:["tr","br"]},rightBottom:{points:["bl","br"]},bottomLeft:{points:["tl","bl"]},leftBottom:{points:["br","bl"]}},zF={topLeft:{points:["bl","tc"]},leftTop:{points:["tr","cl"]},topRight:{points:["br","tc"]},rightTop:{points:["tl","cr"]},bottomRight:{points:["tr","bc"]},rightBottom:{points:["bl","cr"]},bottomLeft:{points:["tl","bc"]},leftBottom:{points:["br","cl"]}},jF=new Set(["topLeft","topRight","bottomLeft","bottomRight","leftTop","leftBottom","rightTop","rightBottom"]);function s4(e){const{arrowWidth:t,autoAdjustOverflow:n,arrowPointAtCenter:r,offset:o,borderRadius:s,visibleFirst:i}=e,l=t/2,c={},u=Om({contentRadius:s,limitVerticalRadius:!0});return Object.keys(PC).forEach(d=>{const f={...r&&zF[d]||PC[d],offset:[0,0],dynamicInset:!0};switch(c[d]=f,jF.has(d)&&(f.autoArrow=!1),d){case"top":case"topLeft":case"topRight":f.offset[1]=-l-o;break;case"bottom":case"bottomLeft":case"bottomRight":f.offset[1]=l+o;break;case"left":case"leftTop":case"leftBottom":f.offset[0]=-l-o;break;case"right":case"rightTop":case"rightBottom":f.offset[0]=l+o;break}if(r)switch(d){case"topLeft":case"bottomLeft":f.offset[0]=-u.arrowOffsetHorizontal-l;break;case"topRight":case"bottomRight":f.offset[0]=u.arrowOffsetHorizontal+l;break;case"leftTop":case"rightTop":f.offset[1]=-u.arrowOffsetHorizontal*2+l;break;case"leftBottom":case"rightBottom":f.offset[1]=u.arrowOffsetHorizontal*2-l;break}f.overflow=_F(d,u,t,n),i&&(f.htmlRegion="visibleFirst")}),c}const zx=J.createContext(!1),jx=(e,t)=>{const n=r=>typeof r=="boolean"?{show:r}:r||{};return J.useMemo(()=>{const r=n(e),o=n(t);return{...o,...r,show:r.show??o.show??!0}},[e,t])},NC="50%",BF=e=>{const{calc:t,componentCls:n,tooltipMaxWidth:r,tooltipColor:o,tooltipBg:s,tooltipBorderRadius:i,zIndexPopup:l,controlHeight:c,dropShadowPopover:u,paddingSM:d,paddingXS:m,arrowOffsetHorizontal:f,sizePopupArrow:p,antCls:y}=e,[b,x]=rn(y,"tooltip"),v=t(i).add(p).add(f).equal(),h={minWidth:t(i).mul(2).add(p).equal(),minHeight:c,padding:`${G(e.calc(d).div(2).equal())} ${G(m)}`,color:x("overlay-color",o),textAlign:"start",textDecoration:"none",wordWrap:"break-word",backgroundColor:s,borderRadius:i,boxSizing:"border-box"},$={[b("valid-offset-x")]:x("arrow-offset-x","var(--arrow-x)"),transformOrigin:[x("valid-offset-x",NC),`var(--arrow-y, ${NC})`].join(" ")};return[{[n]:{...Ft(e),position:"absolute",zIndex:l,display:"block",width:"max-content",maxWidth:r,visibility:"visible",filter:u,...$,"&-hidden":{display:"none"},[b("arrow-background-color")]:s,[`${n}-container`]:[h,HN(e,!0)],[`&:has(~ ${n}-unique-container)`]:{[`${n}-container`]:{border:"none",background:"transparent"}},[["&-placement-topLeft","&-placement-topRight","&-placement-bottomLeft","&-placement-bottomRight"].join(",")]:{minWidth:v},[["&-placement-left","&-placement-leftTop","&-placement-leftBottom","&-placement-right","&-placement-rightTop","&-placement-rightBottom"].join(",")]:{[`${n}-inner`]:{borderRadius:e.min(i,o4)}},[`${n}-content`]:{position:"relative"},...EP(e,(C,{darkColor:N})=>({[`&${n}-${C}`]:{[`${n}-container`]:{backgroundColor:N},[`${n}-arrow`]:{[b("arrow-background-color")]:N}}})),"&-rtl":{direction:"rtl"}}},_x(e,x("arrow-background-color"),{arrowShadow:!1}),{[`${n}-pure`]:{position:"relative",maxWidth:"none",margin:e.sizePopupArrow}},{[`${n}-unique-container`]:{...h,...$,position:"absolute",zIndex:t(l).sub(1).equal(),filter:u,"&-hidden":{display:"none"},"&-visible":{transition:`all ${e.motionDurationSlow}`}}}]},LF=e=>({zIndexPopup:e.zIndexPopupBase+70,maxWidth:250,...Om({contentRadius:e.borderRadius,limitVerticalRadius:!0}),...Ox(Rt(e,{borderRadiusOuter:Math.min(e.borderRadiusOuter,4)}))}),i4=(e,t,n=!0)=>Tt("Tooltip",o=>{const{borderRadius:s,colorTextLightSolid:i,colorBgSpotlight:l,maxWidth:c}=o,u=Rt(o,{tooltipMaxWidth:c,tooltipColor:i,tooltipBorderRadius:s,tooltipBg:l});return[BF(u),Yc(o,"zoom-big-fast")]},LF,{resetStyle:!1,injectStyle:n})(e,t),kF=Oo.map(e=>`${e}-inverse`),AF=["success","processing","error","default","warning"];function a4(e,t=!0){return t?[].concat($t(kF),$t(Oo)).includes(e):Oo.includes(e)}function DF(e){return AF.includes(e)}const l4=(e,t,n)=>{const r=a4(n),[o]=rn(e,"tooltip"),s=H({[`${t}-${n}`]:n&&r}),i={},l={},c=TL(n).toRgb(),d=(.299*c.r+.587*c.g+.114*c.b)/255<.5?"#FFF":"#000";return n&&!r&&(i.background=n,i[o("overlay-color")]=d,l[o("arrow-background-color")]=n),{className:s,overlayStyle:i,arrowStyle:l}},FF=e=>{const{prefixCls:t,className:n,placement:r="top",title:o,color:s,overlayInnerStyle:i,classNames:l,styles:c}=e,{getPrefixCls:u}=a.useContext(ct),d=u("tooltip",t),m=u(),f=on(d),[p,y]=i4(d,f),b=l4(m,d,s),x=b.arrowStyle,v=a.useMemo(()=>({container:{...i,...b.overlayStyle}}),[i,b.overlayStyle]),g={...e,placement:r},[h,$]=Ot([l],[v,c],{props:g}),C=H(f,p,y,d,`${d}-pure`,`${d}-placement-${r}`,n,b.className);return a.createElement("div",{className:C,style:x},a.createElement("div",{className:`${d}-arrow`}),a.createElement(Mx,{...e,className:p,prefixCls:d,classNames:h,styles:$},o))},HF=a.forwardRef((e,t)=>{const{prefixCls:n,openClassName:r,getTooltipContainer:o,color:s,children:i,afterOpenChange:l,arrow:c,destroyTooltipOnHide:u,destroyOnHidden:d,title:m,overlay:f,trigger:p,builtinPlacements:y,autoAdjustOverflow:b=!0,motion:x,getPopupContainer:v,placement:g="top",mouseEnterDelay:h=.1,mouseLeaveDelay:$=.1,rootClassName:C,styles:N,classNames:S,onOpenChange:E,overlayInnerStyle:w,overlayStyle:R,overlayClassName:P,...T}=e,[,M]=Yn(),z=e["data-popover-inject"],{getPopupContainer:B,getPrefixCls:F,direction:L,...j}=Pt("tooltip"),{className:O,style:A,classNames:k,styles:_,arrow:D,trigger:V}=z?{}:j,W=jx(c,D),K=W.show,q=p||V||"hover",Y=v||B,ee=d??!!u,ie=a.useContext(zx);yo();const ae=a.useRef(null),U=()=>{var Te;(Te=ae.current)==null||Te.forceAlign()};a.useImperativeHandle(t,()=>{var Te,Ue;return{forceAlign:U,nativeElement:(Te=ae.current)==null?void 0:Te.nativeElement,popupElement:(Ue=ae.current)==null?void 0:Ue.popupElement}});const[Q,Z]=nn(e.defaultOpen??!1,e.open),ne=!m&&!f&&m!==0,oe=Te=>{Z(ne?!1:Te),!ne&&E&&E(Te)},le=a.useMemo(()=>y||s4({arrowPointAtCenter:(W==null?void 0:W.pointAtCenter)??!1,autoAdjustOverflow:b,arrowWidth:K?M.sizePopupArrow:0,borderRadius:M.borderRadius,offset:M.marginXXS,visibleFirst:!0}),[W,y,M,K,b]),re=a.useMemo(()=>m===0?m:f||m||"",[f,m]),X=a.createElement(Ri,{space:!0,form:!0},bt(re)?re():re),se={...e,trigger:q,builtinPlacements:le,getPopupContainer:Y,destroyOnHidden:ee},[ge,de]=Ot([k,S],[_,N],{props:se}),Se=F("tooltip",n),ue=F();let be=Q;(!("open"in e)&&ne||ie)&&(be=!1);const Ne=a.isValidElement(i)&&!cN(i)?i:a.createElement("span",null,i),we=Ne.props,ze=!we.className||typeof we.className=="string"?H(we.className,r||`${Se}-open`):we.className,he=on(Se),[ke,Oe]=i4(Se,he,!z),Ce=l4(ue,Se,s),Me=Ce.arrowStyle,xe=H(he,ke,Oe),Ee=H(P,{[`${Se}-rtl`]:L==="rtl"},Ce.className,C,xe,O,ge.root),[Ve,qe]=Xc("Tooltip",T.zIndex),me={...de.container,...w,...Ce.overlayStyle},Re=a.createElement(MF,{unique:!0,...T,zIndex:Ve,showArrow:K,placement:g,mouseEnterDelay:h,mouseLeaveDelay:$,prefixCls:Se,classNames:{root:Ee,container:ge.container,arrow:ge.arrow,uniqueContainer:H(xe,ge.container)},styles:{root:{...Me,...de.root,...A,...R},container:me,uniqueContainer:me,arrow:de.arrow},ref:ae,overlay:X,visible:be,onVisibleChange:oe,afterVisibleChange:l,arrowContent:a.createElement("span",{className:`${Se}-arrow-content`}),motion:{motionName:ks(ue,"zoom-big-fast",typeof(x==null?void 0:x.motionName)=="string"?x==null?void 0:x.motionName:void 0),motionDeadline:1e3},trigger:q,builtinPlacements:le,getTooltipContainer:Y,destroyOnHidden:ee},be?Fn(Ne,{className:ze}):Ne);return a.createElement(xm.Provider,{value:qe},Re)}),bo=HF;bo._InternalPanelDoNotUseOrYouWillBeFired=FF;bo.UniqueProvider=dN;const RC="50%",VF=e=>{const{componentCls:t,popoverColor:n,titleMinWidth:r,fontWeightStrong:o,innerPadding:s,dropShadowPopover:i,colorTextHeading:l,borderRadiusLG:c,zIndexPopup:u,titleMarginBottom:d,colorBgElevated:m,popoverBg:f,titleBorderBottom:p,innerContentPadding:y,titlePadding:b,antCls:x}=e,[v,g]=rn(x,"tooltip");return[{[t]:{...Ft(e),position:"absolute",top:0,left:{_skip_check_:!0,value:0},zIndex:u,fontWeight:"normal",whiteSpace:"normal",textAlign:"start",cursor:"auto",userSelect:"text",filter:i,[v("valid-offset-x")]:g("arrow-offset-x","var(--arrow-x)"),transformOrigin:[g("valid-offset-x",RC),`var(--arrow-y, ${RC})`].join(" "),[v("arrow-background-color")]:m,width:"max-content",maxWidth:"100vw","&-rtl":{direction:"rtl"},"&-hidden":{display:"none"},[`${t}-content`]:{position:"relative"},[`${t}-container`]:{backgroundColor:f,backgroundClip:"padding-box",borderRadius:c,padding:s},[`${t}-title`]:{minWidth:r,marginBottom:d,color:l,fontWeight:o,borderBottom:p,padding:b},[`${t}-content`]:{color:n,padding:y}}},_x(e,g("arrow-background-color"),{arrowShadow:!1}),{[`${t}-pure`]:{position:"relative",maxWidth:"none",margin:e.sizePopupArrow,display:"inline-block"}}]},WF=e=>{const{componentCls:t,antCls:n}=e,[r]=rn(n,"tooltip");return{[t]:Oo.map(o=>{const s=e[`${o}6`];return{[`&${t}-${o}`]:{[r("arrow-background-color")]:s,[`${t}-inner`]:{backgroundColor:s},[`${t}-arrow`]:{background:"transparent"}}}})}},KF=e=>{const{lineWidth:t,controlHeight:n,fontHeight:r,padding:o,wireframe:s,zIndexPopupBase:i,borderRadiusLG:l,marginXS:c,lineType:u,colorSplit:d,paddingSM:m}=e,f=n-r,p=f/2,y=f/2-t,b=o;return{titleMinWidth:177,zIndexPopup:i+30,...Ox(e),...Om({contentRadius:l,limitVerticalRadius:!0}),innerPadding:s?0:12,titleMarginBottom:s?0:c,titlePadding:s?`${p}px ${b}px ${y}px`:0,titleBorderBottom:s?`${t}px ${u} ${d}`:"none",innerContentPadding:s?`${m}px ${b}px`:0}},c4=Tt("Popover",e=>{const{colorBgElevated:t,colorText:n}=e,r=Rt(e,{popoverBg:t,popoverColor:n});return[VF(r),WF(r),Yc(r,"zoom-big")]},KF,{resetStyle:!1,deprecatedTokens:[["width","titleMinWidth"],["minWidth","titleMinWidth"]]}),u4=e=>{const{title:t,content:n,prefixCls:r,classNames:o,styles:s}=e;return!$n(t)&&!$n(n)?null:a.createElement(a.Fragment,null,$n(t)&&a.createElement("div",{className:H(`${r}-title`,o==null?void 0:o.title),style:s==null?void 0:s.title},t),$n(n)&&a.createElement("div",{className:H(`${r}-content`,o==null?void 0:o.content),style:s==null?void 0:s.content},n))},UF=e=>{const{hashId:t,prefixCls:n,className:r,style:o,placement:s="top",title:i,content:l,children:c,classNames:u,styles:d}=e,m=Ga(i),f=Ga(l),p={...e,placement:s},[y,b]=Ot([u],[d],{props:p}),x=H(t,n,`${n}-pure`,`${n}-placement-${s}`,r);return a.createElement("div",{className:x,style:o},a.createElement("div",{className:`${n}-arrow`}),a.createElement(Mx,{...e,className:t,prefixCls:n,classNames:y,styles:b},c||a.createElement(u4,{prefixCls:n,title:m,content:f,classNames:y,styles:b})))},d4=e=>{const{prefixCls:t,className:n,...r}=e,{getPrefixCls:o}=a.useContext(ct),s=o("popover",t),[i,l]=c4(s);return a.createElement(UF,{...r,prefixCls:s,hashId:i,className:H(n,l)})},qF=a.forwardRef((e,t)=>{const{prefixCls:n,title:r,content:o,overlayClassName:s,placement:i="top",trigger:l,children:c,mouseEnterDelay:u=.1,mouseLeaveDelay:d=.1,onOpenChange:m,overlayStyle:f={},styles:p,classNames:y,motion:b,arrow:x,...v}=e,{getPrefixCls:g,className:h,style:$,classNames:C,styles:N,arrow:S,trigger:E}=Pt("popover"),w=g("popover",n),[R,P]=c4(w),T=g(),M=jx(x,S),z=l||E||"hover",B={...e,placement:i,trigger:z,mouseEnterDelay:u,mouseLeaveDelay:d,overlayStyle:f,styles:p,classNames:y},[F,L]=Ot([C,y],[N,p],{props:B}),j=H(s,R,P,h,F.root),[O,A]=nn(e.defaultOpen??!1,e.open),k=V=>{A(V),m==null||m(V)},_=Ga(r),D=Ga(o);return a.createElement(bo,{unique:!1,arrow:M,placement:i,trigger:z,mouseEnterDelay:u,mouseLeaveDelay:d,...v,prefixCls:w,classNames:{root:j,container:F.container,arrow:F.arrow},styles:{root:{...L.root,...$,...f},container:L.container,arrow:L.arrow},ref:t,open:O,onOpenChange:k,overlay:$n(_)||$n(D)?a.createElement(u4,{prefixCls:w,title:_,content:D,classNames:F,styles:L}):null,motion:{motionName:ks(T,"zoom-big",typeof(b==null?void 0:b.motionName)=="string"?b==null?void 0:b.motionName:void 0)},"data-popover-inject":!0},c)}),Bx=qF;Bx._InternalPanelDoNotUseOrYouWillBeFired=d4;const TC=e=>{const{size:t,shape:n}=a.useContext(sv),r=a.useMemo(()=>({size:e.size||t,shape:e.shape||n}),[e.size,e.shape,t,n]);return a.createElement(sv.Provider,{value:r},e.children)},GF=e=>{var E,w,R;const{getPrefixCls:t,direction:n}=a.useContext(ct),{prefixCls:r,className:o,rootClassName:s,style:i,maxCount:l,maxStyle:c,size:u,shape:d,maxPopoverPlacement:m,maxPopoverTrigger:f,children:p,max:y}=e,b=t("avatar",r),x=`${b}-group`,v=on(b),[g,h]=n4(b,v),$=H(x,{[`${x}-rtl`]:n==="rtl"},h,v,o,s,g),C=zn(p).map((P,T)=>Fn(P,{key:`avatar-key-${T}`})),N=(y==null?void 0:y.count)||l,S=C.length;if(N&&Na.createElement(gt,av({},e,{ref:t,icon:YF})),Tc=a.forwardRef(QF),{ESC:JF,TAB:ZF}=nt;function e7({visible:e,triggerRef:t,onVisibleChange:n,autoFocus:r,overlayRef:o}){const s=a.useRef(!1),i=()=>{var u,d;e&&((d=(u=t.current)==null?void 0:u.focus)==null||d.call(u),n==null||n(!1))},l=()=>{var u;return(u=o.current)!=null&&u.focus?(o.current.focus(),s.current=!0,!0):!1},c=u=>{switch(u.keyCode){case JF:i();break;case ZF:{let d=!1;s.current||(d=l()),d?u.preventDefault():i();break}}};a.useEffect(()=>e?(window.addEventListener("keydown",c),r&&Ct(l,3),()=>{window.removeEventListener("keydown",c),s.current=!1}):()=>{s.current=!1},[e])}const t7=a.forwardRef((e,t)=>{const{overlay:n,arrow:r,prefixCls:o}=e,s=a.useMemo(()=>{let l;return typeof n=="function"?l=n():l=n,l},[n]),i=Tn(t,Bo(s));return J.createElement(J.Fragment,null,r&&J.createElement("div",{className:`${o}-arrow`}),J.cloneElement(s,{ref:is(s)?i:void 0}))}),na={adjustX:1,adjustY:1},ra=[0,0],n7={topLeft:{points:["bl","tl"],overflow:na,offset:[0,-4],targetOffset:ra},top:{points:["bc","tc"],overflow:na,offset:[0,-4],targetOffset:ra},topRight:{points:["br","tr"],overflow:na,offset:[0,-4],targetOffset:ra},bottomLeft:{points:["tl","bl"],overflow:na,offset:[0,4],targetOffset:ra},bottom:{points:["tc","bc"],overflow:na,offset:[0,4],targetOffset:ra},bottomRight:{points:["tr","br"],overflow:na,offset:[0,4],targetOffset:ra}};function lv(){return lv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var V;const{arrow:n=!1,prefixCls:r="rc-dropdown",transitionName:o,animation:s,align:i,placement:l="bottomLeft",placements:c=n7,getPopupContainer:u,showAction:d,hideAction:m,overlayClassName:f,overlayStyle:p,visible:y,trigger:b=["hover"],autoFocus:x,overlay:v,children:g,onVisibleChange:h,disabled:$,...C}=e,[N,S]=J.useState(),E="visible"in e?y:N,w=s?`${r}-${s}`:o,R=J.useRef(null),P=J.useRef(null),T=J.useRef(null);J.useImperativeHandle(t,()=>R.current);const M=W=>{S(W),h==null||h(W)};e7({visible:E,triggerRef:T,onVisibleChange:M,autoFocus:x,overlayRef:P});const z=W=>{const{onOverlayClick:K}=e;S(!1),K&&K(W)},B=()=>J.createElement(t7,{ref:P,overlay:v,prefixCls:r,arrow:n}),F=()=>typeof v=="function"?B:B(),L=()=>{const{minOverlayWidthMatchTrigger:W,alignPoint:K}=e;return"minOverlayWidthMatchTrigger"in e?W:!K},j=()=>{const{openClassName:W}=e;return W!==void 0?W:`${r}-open`},O=g,A=H((V=O.props)==null?void 0:V.className,E&&j()),k={className:A,ref:Tn(T,Bo(O))},_=is(O)?J.cloneElement(O,k):J.createElement("span",{className:A,ref:T},J.cloneElement(O,{className:A}));let D=m;return!D&&b.indexOf("contextMenu")!==-1&&(D=["click"]),J.createElement(bm,lv({builtinPlacements:c},C,{prefixCls:r,ref:R,popupClassName:H(f,{[`${r}-show-arrow`]:n}),popupStyle:p,action:b,showAction:d,hideAction:D,popupPlacement:l,popupAlign:i,popupMotion:{motionName:w},popupVisible:E,stretch:L()?"minWidth":"",popup:F(),onOpenChange:M,onPopupClick:z,getPopupContainer:u}),_)}),g4=a.createContext(null);function h4(e,t){return`${e}-${t}`}function y4(e){const t=a.useContext(g4);return h4(t,e)}const xo=a.createContext(null);function r7(e,t){const n={...e};return Object.keys(t).forEach(r=>{const o=t[r];o!==void 0&&(n[r]=o)}),n}function Mc({children:e,locked:t,...n}){const r=a.useContext(xo),o=_i(()=>r7(r,n),[r,n],(s,i)=>!t&&(s[0]!==i[0]||!ho(s[1],i[1],!0)));return a.createElement(xo.Provider,{value:o},e)}const o7=[],v4=a.createContext(null);function _m(){return a.useContext(v4)}const b4=a.createContext(o7);function nl(e){const t=a.useContext(b4);return a.useMemo(()=>e!==void 0?[...t,e]:t,[t,e])}const x4=a.createContext(null),Lx=a.createContext({}),{LEFT:cv,RIGHT:uv,UP:dv,DOWN:wd,ENTER:Ed,ESC:$4,HOME:$l,END:Sl}=nt,MC=[dv,wd,cv,uv];function s7(e,t,n,r){var p;const o="prev",s="next",i="children",l="parent";if(e==="inline"&&r===Ed)return{inlineTrigger:!0};const c={[dv]:o,[wd]:s},u={[cv]:n?s:o,[uv]:n?o:s,[wd]:i,[Ed]:i},d={[dv]:o,[wd]:s,[Ed]:i,[$4]:l,[cv]:n?i:l,[uv]:n?l:i};switch((p={inline:c,horizontal:u,vertical:d,inlineSub:c,horizontalSub:d,verticalSub:d}[`${e}${t?"":"Sub"}`])==null?void 0:p[r]){case o:return{offset:-1,sibling:!0};case s:return{offset:1,sibling:!0};case l:return{offset:-1,sibling:!1};case i:return{offset:1,sibling:!1};default:return null}}function i7(e){let t=e;for(;t;){if(t.getAttribute("data-menu-list"))return t;t=t.parentElement}return null}function a7(e,t){let n=e||document.activeElement;for(;n;){if(t.has(n))return n;n=n.parentElement}return null}function kx(e,t){return Ab(e,!0).filter(r=>t.has(r))}function OC(e,t,n,r=1){if(!e)return null;const o=kx(e,t),s=o.length;let i=o.findIndex(l=>n===l);return r<0?i===-1?i=s-1:i-=1:r>0&&(i+=1),i=(i+s)%s,o[i]}const hf=(e,t)=>{const n=new Set,r=new Map,o=new Map;return e.forEach(s=>{const i=document.querySelector(`[data-menu-id='${h4(t,s)}']`);i&&(n.add(i),o.set(i,s),r.set(s,i))}),{elements:n,key2element:r,element2key:o}};function l7(e,t,n,r,o,s,i,l,c,u){const d=a.useRef(),m=a.useRef();m.current=t;const f=()=>{Ct.cancel(d.current)};return a.useEffect(()=>()=>{f()},[]),p=>{const{which:y}=p;if([...MC,Ed,$4,$l,Sl].includes(y)){const b=s();let x=hf(b,r);const{elements:v,key2element:g,element2key:h}=x,$=g.get(t),C=a7($,v),N=h.get(C),S=s7(e,i(N,!0).length===1,n,y);if(!S&&y!==$l&&y!==Sl)return;(MC.includes(y)||[$l,Sl].includes(y))&&p.preventDefault();const E=w=>{if(w){let R=w;const P=w.querySelector("a");P!=null&&P.getAttribute("href")&&(R=P);const T=h.get(w);l(T),f(),d.current=Ct(()=>{m.current===T&&R.focus()})}};if([$l,Sl].includes(y)||S.sibling||!C){let w;!C||e==="inline"?w=o.current:w=i7(C);let R;const P=kx(w,v);y===$l?R=P[0]:y===Sl?R=P[P.length-1]:R=OC(w,v,C,S.offset),E(R)}else if(S.inlineTrigger)c(N);else if(S.offset>0)c(N,!0),f(),d.current=Ct(()=>{x=hf(b,r);const w=C.getAttribute("aria-controls"),R=document.getElementById(w),P=OC(R,x.elements);E(P)},5);else if(S.offset<0){const w=i(N,!0),R=w[w.length-2],P=g.get(R);c(R,!1),E(P)}}u==null||u(p)}}function c7(e){Promise.resolve().then(e)}const Ax="__RC_UTIL_PATH_SPLIT__",_C=e=>e.join(Ax),u7=e=>e.split(Ax),fv="rc-menu-more";function d7(){const[,e]=a.useState({}),t=a.useRef(new Map),n=a.useRef(new Map),[r,o]=a.useState([]),s=a.useRef(0),i=a.useRef(!1),l=()=>{i.current||e({})},c=a.useCallback((b,x)=>{const v=_C(x);n.current.set(v,b),t.current.set(b,v),s.current+=1;const g=s.current;c7(()=>{g===s.current&&l()})},[]),u=a.useCallback((b,x)=>{const v=_C(x);n.current.delete(v),t.current.delete(b)},[]),d=a.useCallback(b=>{o(b)},[]),m=a.useCallback((b,x)=>{const v=t.current.get(b)||"",g=u7(v);return x&&r.includes(g[0])&&g.unshift(fv),g},[r]),f=a.useCallback((b,x)=>b.filter(v=>v!==void 0).some(v=>m(v,!0).includes(x)),[m]),p=()=>{const b=[...t.current.keys()];return r.length&&b.push(fv),b},y=a.useCallback(b=>{const x=`${t.current.get(b)}${Ax}`,v=new Set;return[...n.current.keys()].forEach(g=>{g.startsWith(x)&&v.add(n.current.get(g))}),v},[]);return a.useEffect(()=>()=>{i.current=!0},[]),{registerPath:c,unregisterPath:u,refreshOverflowKeys:d,isSubPathKey:f,getKeyPath:m,getKeys:p,getSubPathKeys:y}}function _l(e){const t=a.useRef(e);t.current=e;const n=a.useCallback((...r)=>{var o;return(o=t.current)==null?void 0:o.call(t,...r)},[]);return e?n:void 0}function S4(e,t,n,r){const{activeKey:o,onActive:s,onInactive:i}=a.useContext(xo),l={active:o===e};return t||(l.onMouseEnter=c=>{n==null||n({key:e,domEvent:c}),s(e)},l.onMouseLeave=c=>{r==null||r({key:e,domEvent:c}),i(e)}),l}function C4(e){const{mode:t,rtl:n,inlineIndent:r}=a.useContext(xo);if(t!=="inline")return null;const o=e;return n?{paddingRight:o*r}:{paddingLeft:o*r}}function w4({icon:e,props:t,children:n}){let r;return e===null||e===!1?null:(typeof e=="function"?r=a.createElement(e,{...t}):typeof e!="boolean"&&(r=e),r||n||null)}function yf({item:e,...t}){return Object.defineProperty(t,"item",{get:()=>(fn(!1,"`info.item` is deprecated since we will move to function component that not provides React Node instance in future."),e)}),t}function Oc(){return Oc=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{style:n,className:r,eventKey:o,warnKey:s,disabled:i,itemIcon:l,children:c,itemData:u,role:d,onMouseEnter:m,onMouseLeave:f,onClick:p,onKeyDown:y,onFocus:b,...x}=e,v=y4(o),{prefixCls:g,onItemClick:h,disabled:$,overflowDisabled:C,itemIcon:N,selectedKeys:S,onActive:E}=a.useContext(xo),{_internalRenderMenuItem:w}=a.useContext(Lx),R=`${g}-item`,P=a.useRef(),T=a.useRef(),M=$||i,z=$o(t,T),B=nl(o),F=q=>{const Y=u||{key:o||"",label:c,itemIcon:l,extra:e.extra,title:e.title};return{key:o,keyPath:[...B].reverse(),item:P.current,domEvent:q,itemData:u||Y}},L=l||N,{active:j,...O}=S4(o,M,m,f),A=S.includes(o),k=C4(B.length),_=q=>{if(M)return;const Y=F(q);p==null||p(yf(Y)),h(Y)},D=q=>{if(y==null||y(q),q.which===nt.ENTER){const Y=F(q);p==null||p(yf(Y)),h(Y)}},V=q=>{E(o),b==null||b(q)},W={};e.role==="option"&&(W["aria-selected"]=A);let K=a.createElement(f7,Oc({ref:P,elementRef:z,role:d===null?"none":d||"menuitem",tabIndex:i?null:-1,"data-menu-id":C&&v?null:v},Dt(x,["extra"]),O,W,{component:"li","aria-disabled":i,style:{...k,...n},className:H(R,{[`${R}-active`]:j,[`${R}-selected`]:A,[`${R}-disabled`]:M},r),onClick:_,onKeyDown:D,onFocus:V}),c,a.createElement(w4,{props:{...e,isSelected:A},icon:L}));return w&&(K=w(K,e,{selected:A})),K});function p7(e,t){const{eventKey:n}=e,r=_m(),o=nl(n);return a.useEffect(()=>{if(r)return r.registerPath(n,o),()=>{r.unregisterPath(n,o)}},[o]),r?null:a.createElement(m7,Oc({},e,{ref:t}))}const ru=a.forwardRef(p7);function mv(){return mv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:o,mode:s,rtl:i}=a.useContext(xo);return a.createElement("ul",mv({className:H(o,i&&`${o}-rtl`,`${o}-sub`,`${o}-${s==="inline"?"inline":"vertical"}`,e),role:"menu"},n,{"data-menu-list":!0,ref:r}),t)},E4=a.forwardRef(g7);function Dx(e,t){return zn(e).map((n,r)=>{var o;if(a.isValidElement(n)){const{key:s}=n;let i=((o=n.props)==null?void 0:o.eventKey)??s;i==null&&(i=`tmp_key-${[...t,r].join("-")}`);const c={key:i,eventKey:i};return a.cloneElement(n,c)}return n})}const Zn={adjustX:1,adjustY:1},h7={topLeft:{points:["bl","tl"],overflow:Zn},topRight:{points:["br","tr"],overflow:Zn},bottomLeft:{points:["tl","bl"],overflow:Zn},bottomRight:{points:["tr","br"],overflow:Zn},leftTop:{points:["tr","tl"],overflow:Zn},leftBottom:{points:["br","bl"],overflow:Zn},rightTop:{points:["tl","tr"],overflow:Zn},rightBottom:{points:["bl","br"],overflow:Zn}},y7={topLeft:{points:["bl","tl"],overflow:Zn},topRight:{points:["br","tr"],overflow:Zn},bottomLeft:{points:["tl","bl"],overflow:Zn},bottomRight:{points:["tr","br"],overflow:Zn},rightTop:{points:["tr","tl"],overflow:Zn},rightBottom:{points:["br","bl"],overflow:Zn},leftTop:{points:["tl","tr"],overflow:Zn},leftBottom:{points:["bl","br"],overflow:Zn}};function I4(e,t,n){if(t)return t;if(n)return n[e]||n.other}const v7={horizontal:"bottomLeft",vertical:"rightTop","vertical-left":"rightTop","vertical-right":"leftTop"};function b7({prefixCls:e,visible:t,children:n,popup:r,popupStyle:o,popupClassName:s,popupOffset:i,disabled:l,mode:c,onVisibleChange:u}){const{getPopupContainer:d,rtl:m,subMenuOpenDelay:f,subMenuCloseDelay:p,builtinPlacements:y,triggerSubMenuAction:b,forceSubMenuRender:x,rootClassName:v,motion:g,defaultMotions:h}=a.useContext(xo),[$,C]=a.useState(!1),N=m?{...y7,...y}:{...h7,...y},S=v7[c],E=I4(c,g,h),w=a.useRef(E);c!=="inline"&&(w.current=E);const R={...w.current,leavedClassName:`${e}-hidden`,removeOnLeave:!1,motionAppear:!0},P=a.useRef();return a.useEffect(()=>(P.current=Ct(()=>{C(t)}),()=>{Ct.cancel(P.current)}),[t]),a.createElement(bm,{prefixCls:e,popupClassName:H(`${e}-popup`,{[`${e}-rtl`]:m},s,v),stretch:c==="horizontal"?"minWidth":null,getPopupContainer:d,builtinPlacements:N,popupPlacement:S,popupVisible:$,popup:r,popupStyle:o,popupAlign:i&&{offset:i},action:l?[]:[b],mouseEnterDelay:f,mouseLeaveDelay:p,onPopupVisibleChange:u,forceRender:x,popupMotion:R,fresh:!0},n)}function pv(){return pv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{d.current&&f(!1)},[u]);const y={...I4(o,l,c)};n.length>1&&(y.motionAppear=!1);const b=y.onVisibleChanged;return y.onVisibleChanged=x=>(!d.current&&!x&&f(!0),b==null?void 0:b(x)),m?null:a.createElement(Mc,{mode:o,locked:!d.current},a.createElement(fr,pv({visible:p},y,{forceRender:i,removeOnLeave:!1,leavedClassName:`${s}-hidden`}),({className:x,style:v})=>a.createElement(E4,{id:e,className:x,style:v},r)))}function _c(){return _c=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{style:n,className:r,styles:o,classNames:s,title:i,eventKey:l,warnKey:c,disabled:u,internalPopupClose:d,children:m,itemIcon:f,expandIcon:p,popupClassName:y,popupOffset:b,popupStyle:x,onClick:v,onMouseEnter:g,onMouseLeave:h,onTitleClick:$,onTitleMouseEnter:C,onTitleMouseLeave:N,popupRender:S,...E}=e,w=y4(l),{prefixCls:R,mode:P,openKeys:T,disabled:M,overflowDisabled:z,activeKey:B,selectedKeys:F,itemIcon:L,expandIcon:j,onItemClick:O,onOpenChange:A,onActive:k,popupRender:_}=a.useContext(xo),{_internalRenderSubMenuItem:D}=a.useContext(Lx),{isSubPathKey:V}=a.useContext(x4),W=nl(),K=`${R}-submenu`,q=M||u,Y=a.useRef(),ee=a.useRef(),ie=f??L,ae=p??j,U=T.includes(l),Q=!z&&U,Z=V(F,l),{active:ne,...oe}=S4(l,q,C,N),[le,re]=a.useState(!1),X=Ee=>{q||re(Ee)},se=Ee=>{X(!0),g==null||g({key:l,domEvent:Ee})},ge=Ee=>{X(!1),h==null||h({key:l,domEvent:Ee})},de=a.useMemo(()=>ne||(P!=="inline"?le||V([B],l):!1),[P,ne,B,le,l,V]),Se=C4(W.length),ue=Ee=>{q||($==null||$({key:l,domEvent:Ee}),P==="inline"&&A(l,!U))},be=_l(Ee=>{v==null||v(yf(Ee)),O(Ee)}),Ne=Ee=>{P!=="inline"&&A(l,Ee)},we=()=>{k(l)},ze=w&&`${w}-popup`,he=a.useMemo(()=>a.createElement(w4,{icon:P!=="horizontal"?ae:void 0,props:{...e,isOpen:Q,isSubMenu:!0}},a.createElement("i",{className:`${K}-arrow`})),[P,ae,e,Q,K]);let ke=a.createElement("div",_c({role:"menuitem",style:Se,className:`${K}-title`,tabIndex:q?null:-1,ref:Y,title:typeof i=="string"?i:null,"data-menu-id":z&&w?null:w,"aria-expanded":Q,"aria-haspopup":!0,"aria-controls":ze,"aria-disabled":q,onClick:ue,onFocus:we},oe),i,he);const Oe=a.useRef(P);P!=="inline"&&W.length>1?Oe.current="vertical":Oe.current=P;const Ce=Oe.current,Me=a.useMemo(()=>{const Ee=a.createElement(Mc,{classNames:s,styles:o,mode:Ce==="horizontal"?"vertical":Ce},a.createElement(E4,{id:ze,ref:ee},m)),Ve=S||_;return Ve?Ve(Ee,{item:e,keys:W}):Ee},[S,_,W,ze,m,e,Ce]);if(!z){const Ee=Oe.current;ke=a.createElement(b7,{mode:Ee,prefixCls:K,visible:!d&&Q&&P!=="inline",popupClassName:y,popupOffset:b,popupStyle:x,popup:Me,disabled:q,onVisibleChange:Ne},ke)}let xe=a.createElement(es.Item,_c({ref:t,role:"none"},E,{component:"li",style:n,className:H(K,`${K}-${P}`,r,{[`${K}-open`]:Q,[`${K}-active`]:de,[`${K}-selected`]:Z,[`${K}-disabled`]:q}),onMouseEnter:se,onMouseLeave:ge}),ke,!z&&a.createElement(x7,{id:ze,open:Q,keyPath:W},m));return D&&(xe=D(xe,e,{selected:Z,active:de,open:Q,disabled:q})),a.createElement(Mc,{classNames:s,styles:o,onItemClick:be,mode:P==="horizontal"?"vertical":P,itemIcon:ie,expandIcon:ae},xe)}),zm=a.forwardRef((e,t)=>{const{eventKey:n,children:r}=e,o=nl(n),s=Dx(r,o),i=_m();a.useEffect(()=>{if(i)return i.registerPath(n,o),()=>{i.unregisterPath(n,o)}},[o]);let l;return i?l=s:l=a.createElement($7,_c({ref:t},e),s),a.createElement(b4.Provider,{value:o},l)});function Fx({className:e,style:t}){const{prefixCls:n}=a.useContext(xo);return _m()?null:a.createElement("li",{role:"separator",className:H(`${n}-item-divider`,e),style:t})}function vf(){return vf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{className:n,title:r,eventKey:o,children:s,...i}=e,{prefixCls:l,classNames:c,styles:u}=a.useContext(xo),d=`${l}-item-group`;return a.createElement("li",vf({ref:t,role:"presentation"},i,{onClick:m=>m.stopPropagation(),className:H(d,n)}),a.createElement("div",{role:"presentation",className:H(`${d}-title`,c==null?void 0:c.listTitle),style:u==null?void 0:u.listTitle,title:typeof r=="string"?r:void 0},r),a.createElement("ul",{role:"group",className:H(`${d}-list`,c==null?void 0:c.list),style:u==null?void 0:u.list},s))}),Hx=a.forwardRef((e,t)=>{const{eventKey:n,children:r}=e,o=nl(n),s=Dx(r,o);return _m()?s:a.createElement(S7,vf({ref:t},Dt(e,["warnKey"])),s)});function xa(){return xa=Object.assign?Object.assign.bind():function(e){for(var t=1;t{if(l&&typeof l=="object"){const{label:u,children:d,key:m,type:f,extra:p,...y}=l,b=m??`tmp-${c}`;if(d||f==="group")return f==="group"?a.createElement(o,xa({key:b},y,{title:u}),gv(d,t,n)):a.createElement(s,xa({key:b},y,{title:u}),gv(d,t,n));if(f==="divider")return a.createElement(i,xa({key:b},y));const x=!!p||p===0;return a.createElement(r,xa({key:b},y,{extra:p,itemData:{...l,key:b}}),x?a.createElement(a.Fragment,null,a.createElement("span",{className:`${n}-item-label`},u),a.createElement("span",{className:`${n}-item-extra`},p)):u)}return null}).filter(l=>l)}function zC(e,t,n,r,o){let s=e;const i={divider:Fx,item:ru,group:Hx,submenu:zm,...r};return t&&(s=gv(t,i,o)),Dx(s,n)}function hv(){return hv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var at;const{prefixCls:n="rc-menu",rootClassName:r,style:o,className:s,styles:i,classNames:l,tabIndex:c=0,items:u,children:d,direction:m,id:f,mode:p="vertical",inlineCollapsed:y,disabled:b,disabledOverflow:x,subMenuOpenDelay:v=.1,subMenuCloseDelay:g=.1,forceSubMenuRender:h,defaultOpenKeys:$,openKeys:C,activeKey:N,defaultActiveFirst:S,selectable:E=!0,multiple:w=!1,defaultSelectedKeys:R,selectedKeys:P,onSelect:T,onDeselect:M,inlineIndent:z=24,motion:B,defaultMotions:F,triggerSubMenuAction:L="hover",builtinPlacements:j,itemIcon:O,expandIcon:A,overflowedIndicator:k="...",overflowedIndicatorPopupClassName:_,getPopupContainer:D,onClick:V,onOpenChange:W,onKeyDown:K,openAnimation:q,openTransitionName:Y,_internalRenderMenuItem:ee,_internalRenderSubMenuItem:ie,_internalComponents:ae,popupRender:U,...Q}=e,[Z,ne]=a.useMemo(()=>[zC(d,u,Qs,ae,n),zC(d,u,Qs,{},n)],[d,u,ae]),[oe,le]=a.useState(!1),re=a.useRef(),X=jo(f?`rc-menu-uuid-${f}`:"rc-menu-uuid"),se=m==="rtl",[ge,de]=nn($,C),Se=ge||Qs,ue=(Ze,De=!1)=>{function Le(){de(Ze),W==null||W(Ze)}De?ss.flushSync(Le):Le()},[be,Ne]=a.useState(Se),we=a.useRef(!1),[ze,he]=a.useMemo(()=>(p==="inline"||p==="vertical")&&y?["vertical",y]:[p,!1],[p,y]),ke=ze==="inline",[Oe,Ce]=a.useState(ze),[Me,xe]=a.useState(he);a.useEffect(()=>{Ce(ze),xe(he),we.current&&(ke?de(be):ue(Qs))},[ze,he]);const[Ee,Ve]=a.useState(0),qe=Ee>=Z.length-1||Oe!=="horizontal"||x;a.useEffect(()=>{ke&&Ne(Se)},[Se]),a.useEffect(()=>(we.current=!0,()=>{we.current=!1}),[]);const{registerPath:me,unregisterPath:Re,refreshOverflowKeys:Te,isSubPathKey:Ue,getKeyPath:Ge,getKeys:Fe,getSubPathKeys:et}=d7(),ve=a.useMemo(()=>({registerPath:me,unregisterPath:Re}),[me,Re]),je=a.useMemo(()=>({isSubPathKey:Ue}),[Ue]);a.useEffect(()=>{Te(qe?Qs:Z.slice(Ee+1).map(Ze=>Ze.key))},[Ee,qe]);const[ce,Pe]=nn(N||S&&((at=Z[0])==null?void 0:at.key),N),pe=_l(Ze=>{Pe(Ze)}),$e=_l(()=>{Pe(void 0)});a.useImperativeHandle(t,()=>({list:re.current,focus:Ze=>{var jt,pt;const De=Fe(),{elements:Le,key2element:Ke,element2key:lt}=hf(De,X),_t=kx(re.current,Le);let ft;ce&&De.includes(ce)?ft=ce:ft=_t[0]?lt.get(_t[0]):(jt=Z.find(qt=>!qt.props.disabled))==null?void 0:jt.key;const xt=Ke.get(ft);ft&&xt&&((pt=xt==null?void 0:xt.focus)==null||pt.call(xt,Ze))},findItem:({key:Ze})=>{const De=Fe(),{key2element:Le}=hf(De,X);return Le.get(Ze)||null}}));const[_e,Ie]=nn(R||[],P),Be=a.useMemo(()=>Array.isArray(_e)?_e:_e==null?Qs:[_e],[_e]),te=Ze=>{if(E){const{key:De}=Ze,Le=Be.includes(De);let Ke;w?Le?Ke=Be.filter(_t=>_t!==De):Ke=[...Be,De]:Ke=[De],Ie(Ke);const lt={...Ze,selectedKeys:Ke};Le?M==null||M(lt):T==null||T(lt)}!w&&Se.length&&Oe!=="inline"&&ue(Qs)},ye=_l(Ze=>{V==null||V(yf(Ze)),te(Ze)}),Ae=_l((Ze,De)=>{let Le=Se.filter(Ke=>Ke!==Ze);if(De)Le.push(Ze);else if(Oe!=="inline"){const Ke=et(Ze);Le=Le.filter(lt=>!Ke.has(lt))}ho(Se,Le,!0)||ue(Le,!0)}),St=l7(Oe,ce,se,X,re,Fe,Ge,Pe,(Ze,De)=>{const Le=De??!Se.includes(Ze);Ae(Ze,Le)},K);a.useEffect(()=>{le(!0)},[]);const ht=a.useMemo(()=>({_internalRenderMenuItem:ee,_internalRenderSubMenuItem:ie}),[ee,ie]),Nt=Oe!=="horizontal"||x?Z:Z.map((Ze,De)=>a.createElement(Mc,{key:Ze.key,overflowDisabled:De>Ee,classNames:l,styles:i},Ze)),yt=a.createElement(es,hv({id:f,ref:re,prefixCls:`${n}-overflow`,component:"ul",itemComponent:ru,className:H(n,`${n}-root`,`${n}-${Oe}`,s,{[`${n}-inline-collapsed`]:Me,[`${n}-rtl`]:se},r),dir:m,style:o,role:"menu",tabIndex:c,data:Nt,renderRawItem:Ze=>Ze,renderRawRest:Ze=>{const De=Ze.length,Le=De?Z.slice(-De):null;return a.createElement(zm,{eventKey:fv,title:k,disabled:qe,internalPopupClose:De===0,popupClassName:_},Le)},maxCount:Oe!=="horizontal"||x?es.INVALIDATE:es.RESPONSIVE,ssr:"full","data-menu-list":!0,onVisibleChange:Ze=>{Ve(Ze)},onKeyDown:St},Q));return a.createElement(Lx.Provider,{value:ht},a.createElement(g4.Provider,{value:X},a.createElement(Mc,{prefixCls:n,rootClassName:r,classNames:l,styles:i,mode:Oe,openKeys:Se,rtl:se,disabled:b,motion:oe?B:null,defaultMotions:oe?F:null,activeKey:ce,onActive:pe,onInactive:$e,selectedKeys:Be,inlineIndent:z,subMenuOpenDelay:v,subMenuCloseDelay:g,forceSubMenuRender:h,builtinPlacements:j,triggerSubMenuAction:L,getPopupContainer:D,itemIcon:O,expandIcon:A,onItemClick:ye,onOpenChange:Ae,popupRender:U},a.createElement(x4.Provider,{value:je},yt),a.createElement("div",{style:{display:"none"},"aria-hidden":!0},a.createElement(v4.Provider,{value:ve},ne)))))}),rl=C7;rl.Item=ru;rl.SubMenu=zm;rl.ItemGroup=Hx;rl.Divider=Fx;var P4={};Object.defineProperty(P4,"__esModule",{value:!0});var w7={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M912 192H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM104 228a56 56 0 10112 0 56 56 0 10-112 0zm0 284a56 56 0 10112 0 56 56 0 10-112 0zm0 284a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"bars",theme:"outlined"},E7=P4.default=w7;function yv(){return yv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,yv({},e,{ref:t,icon:E7})),P7=a.forwardRef(I7),N4=a.createContext({siderHook:{addSider:()=>null,removeSider:()=>null}}),N7=e=>{const{antCls:t,componentCls:n,colorText:r,footerBg:o,headerHeight:s,headerPadding:i,headerColor:l,footerPadding:c,fontSize:u,bodyBg:d,headerBg:m}=e;return{[n]:{display:"flex",flex:"auto",flexDirection:"column",minHeight:0,background:d,"&, *":{boxSizing:"border-box"},[`&${n}-has-sider`]:{flexDirection:"row",[`> ${n}, > ${n}-content`]:{width:0}},[`${n}-header, &${n}-footer`]:{flex:"0 0 auto"},"&-rtl":{direction:"rtl"}},[`${n}-header`]:{height:s,padding:i,color:l,lineHeight:G(s),background:m,[`${t}-menu`]:{lineHeight:"inherit"}},[`${n}-footer`]:{padding:c,color:r,fontSize:u,background:o},[`${n}-content`]:{flex:"auto",color:r,minHeight:0}}},R4=e=>{const{colorBgLayout:t,controlHeight:n,controlHeightLG:r,colorText:o,controlHeightSM:s,marginXXS:i,colorTextLightSolid:l,colorBgContainer:c}=e,u=r*1.25;return{colorBgHeader:"#001529",colorBgBody:t,colorBgTrigger:"#002140",bodyBg:t,headerBg:"#001529",headerHeight:n*2,headerPadding:`0 ${u}px`,headerColor:o,footerPadding:`${s}px ${u}px`,footerBg:t,siderBg:"#001529",triggerHeight:r+i*2,triggerBg:"#002140",triggerColor:l,zeroTriggerWidth:r,zeroTriggerHeight:r,lightSiderBg:c,lightTriggerBg:c,lightTriggerColor:o}},T4=[["colorBgBody","bodyBg"],["colorBgHeader","headerBg"],["colorBgTrigger","triggerBg"]],M4=Tt("Layout",N7,R4,{deprecatedTokens:T4}),R7=e=>{const{componentCls:t,siderBg:n,motionDurationMid:r,motionDurationSlow:o,antCls:s,triggerHeight:i,triggerColor:l,triggerBg:c,headerHeight:u,zeroTriggerWidth:d,zeroTriggerHeight:m,borderRadiusLG:f,lightSiderBg:p,lightTriggerColor:y,lightTriggerBg:b,bodyBg:x}=e;return{[t]:{position:"relative",minWidth:0,background:n,transition:`all ${r}, background 0s`,"&-has-trigger":{paddingBottom:i},"&-right":{order:1},[`${t}-children`]:{height:"100%",marginTop:-.1,paddingTop:.1,[`${s}-menu${s}-menu-inline-collapsed`]:{width:"auto"}},[`&-zero-width ${t}-children`]:{overflow:"hidden"},[`${t}-trigger`]:{position:"fixed",bottom:0,zIndex:1,height:i,color:l,lineHeight:G(i),textAlign:"center",background:c,cursor:"pointer",transition:`all ${r}`},[`${t}-zero-width-trigger`]:{position:"absolute",top:u,insetInlineEnd:e.calc(d).mul(-1).equal(),zIndex:1,width:d,height:m,color:l,fontSize:e.fontSizeXL,display:"flex",alignItems:"center",justifyContent:"center",background:n,borderRadius:`0 ${G(f)} ${G(f)} 0`,cursor:"pointer",transition:`background-color ${o} ease`,"&::after":{position:"absolute",inset:0,background:"transparent",transition:`all ${o}`,content:'""'},"&:hover::after":{background:"rgba(255, 255, 255, 0.2)"},"&-right":{insetInlineStart:e.calc(d).mul(-1).equal(),borderRadius:`${G(f)} 0 0 ${G(f)}`}},"&-light":{background:p,[`${t}-trigger`]:{color:y,background:b},[`${t}-zero-width-trigger`]:{color:y,background:b,border:`1px solid ${x}`,borderInlineStart:0}}}}},T7=Tt(["Layout","Sider"],R7,R4,{deprecatedTokens:T4}),jC={xs:"479.98px",sm:"575.98px",md:"767.98px",lg:"991.98px",xl:"1199.98px",xxl:"1599.98px",xxxl:"1839.98px"},M7=e=>!Number.isNaN(Number.parseFloat(e))&&Number.isFinite(Number(e)),jm=a.createContext({}),O7=(()=>{let e=0;return(t="")=>(e+=1,`${t}${e}`)})(),O4=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,trigger:o,children:s,defaultCollapsed:i=!1,theme:l="dark",style:c={},collapsible:u=!1,reverseArrow:d=!1,width:m=200,collapsedWidth:f=80,zeroWidthTriggerStyle:p,breakpoint:y,onCollapse:b,onBreakpoint:x,classNames:v,styles:g,...h}=e,{siderHook:$}=a.useContext(N4),[C,N]=a.useState("collapsed"in e?e.collapsed:i),[S,E]=a.useState(!1);a.useEffect(()=>{"collapsed"in e&&N(e.collapsed)},[e.collapsed]);const w=(U,Q)=>{"collapsed"in e||N(U),b==null||b(U,Q)},R={...e,collapsed:C,defaultCollapsed:i,theme:l,style:c,collapsible:u,reverseArrow:d,width:m,collapsedWidth:f,zeroWidthTriggerStyle:p,breakpoint:y,onCollapse:b,onBreakpoint:x},[P,T]=Ot([v],[g],{props:R}),{getPrefixCls:M,direction:z}=a.useContext(ct),B=M("layout-sider",n),[F,L]=T7(B),j=a.useRef(null);j.current=U=>{E(U.matches),x==null||x(U.matches),C!==U.matches&&w(U.matches,"responsive")},a.useEffect(()=>{function U(Z){var ne;return(ne=j.current)==null?void 0:ne.call(j,Z)}let Q;return typeof(window==null?void 0:window.matchMedia)<"u"&&y&&y in jC&&(Q=window.matchMedia(`screen and (max-width: ${jC[y]})`),bt(Q==null?void 0:Q.addEventListener)&&Q.addEventListener("change",U),U(Q)),()=>{bt(Q==null?void 0:Q.removeEventListener)&&Q.removeEventListener("change",U)}},[y]),a.useEffect(()=>{const U=O7("ant-sider-");return $.addSider(U),()=>$.removeSider(U)},[]);const O=()=>{w(!C,"clickTrigger")},A=Dt(h,["collapsed"]),k=C?f:m,_=M7(k)?`${k}px`:String(k),D=Number.parseFloat(String(f||0))===0?a.createElement("span",{onClick:O,className:H(`${B}-zero-width-trigger`,`${B}-zero-width-trigger-${d?"right":"left"}`),style:p},o||a.createElement(P7,null)):null,V=z==="rtl"==!d,q={expanded:V?a.createElement(Nc,null):a.createElement(Tc,null),collapsed:V?a.createElement(Tc,null):a.createElement(Nc,null)}[C?"collapsed":"expanded"],Y=o!==null?D||a.createElement("div",{className:`${B}-trigger`,onClick:O,style:{width:_}},o||q):null,ee={...c,flex:`0 0 ${_}`,maxWidth:_,minWidth:_,width:_},ie=H(B,`${B}-${l}`,{[`${B}-collapsed`]:!!C,[`${B}-has-trigger`]:u&&o!==null&&!D,[`${B}-below`]:!!S,[`${B}-zero-width`]:Number.parseFloat(_)===0},r,P.root,F,L),ae=a.useMemo(()=>({siderCollapsed:C}),[C]);return a.createElement(jm.Provider,{value:ae},a.createElement("aside",{className:ie,...A,style:{...T.root,...ee},ref:t},a.createElement("div",{className:H(`${B}-children`,P.body),style:T.body},s),u||S&&D?Y:null))});var _4={};Object.defineProperty(_4,"__esModule",{value:!0});var _7={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"ellipsis",theme:"outlined"},z7=_4.default=_7;function vv(){return vv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,vv({},e,{ref:t,icon:z7})),Bm=a.forwardRef(j7),bf=a.createContext({prefixCls:"",firstLevel:!0,inlineCollapsed:!1,styles:null,classNames:null}),z4=e=>{const{prefixCls:t,className:n,dashed:r,...o}=e,{getPrefixCls:s}=a.useContext(ct),i=s("menu",t),l=H({[`${i}-item-divider-dashed`]:!!r},n);return a.createElement(Fx,{className:l,...o})},j4=e=>{var S,E;const{className:t,children:n,icon:r,title:o,danger:s,extra:i}=e,{prefixCls:l,firstLevel:c,direction:u,disableMenuItemTitleTooltip:d,tooltip:m,inlineCollapsed:f,styles:p,classNames:y}=a.useContext(bf),b=w=>{var T,M;const R=n==null?void 0:n[0],P=a.createElement("span",{className:H(`${l}-title-content`,c?y==null?void 0:y.itemContent:(T=y==null?void 0:y.subMenu)==null?void 0:T.itemContent,{[`${l}-title-content-with-extra`]:!!i||i===0}),style:c?p==null?void 0:p.itemContent:(M=p==null?void 0:p.subMenu)==null?void 0:M.itemContent},n);return(!r||a.isValidElement(n)&&n.type==="span")&&n&&w&&c&&typeof R=="string"?a.createElement("div",{className:`${l}-inline-collapsed-noicon`},R.charAt(0)):P},{siderCollapsed:x}=a.useContext(jm);let v=o;typeof o>"u"?v=c?n:"":o===!1&&(v="");const g=m===!1?void 0:m,h=g&&g.title!==void 0?g.title:v,$={...g??null,title:h};!x&&!f&&($.title=null,$.open=!1);const C=zn(n).length;let N=a.createElement(ru,{...Dt(e,["title","icon","danger"]),className:H(c?y==null?void 0:y.item:(S=y==null?void 0:y.subMenu)==null?void 0:S.item,{[`${l}-item-danger`]:s,[`${l}-item-only-child`]:(r?C+1:C)===1},t),style:{...c?p==null?void 0:p.item:(E=p==null?void 0:p.subMenu)==null?void 0:E.item,...e.style},title:typeof o=="string"?o:void 0,itemData:(e==null?void 0:e.itemData)??{...e,key:e.eventKey}},Fn(r,w=>{var R,P;return{className:H(`${l}-item-icon`,c?y==null?void 0:y.itemIcon:(R=y==null?void 0:y.subMenu)==null?void 0:R.itemIcon,w.className),style:{...c?p==null?void 0:p.itemIcon:(P=p==null?void 0:p.subMenu)==null?void 0:P.itemIcon,...w.style}}}),b(f));if(!d&&m!==!1){const w=g&&g.placement?g.placement:u==="rtl"?"left":"right",R=`${l}-inline-collapsed-tooltip`,P=M=>({...M,root:H(R,M==null?void 0:M.root)}),T=bt(g==null?void 0:g.classNames)?M=>{const z=g.classNames(M);return P(z)}:P(g==null?void 0:g.classNames);N=a.createElement(bo,{...$,placement:w,classNames:T},N)}return N},xf=a.createContext(null),B4=a.forwardRef((e,t)=>{const{children:n,...r}=e,o=a.useContext(xf),s=a.useMemo(()=>({...o,...r}),[o,r.prefixCls,r.mode,r.selectable,r.rootClassName]),i=kI(n),l=$o(t,i?Bo(n):null);return a.createElement(xf.Provider,{value:s},a.createElement(Ri,{space:!0},i?a.cloneElement(n,{ref:l}):n))}),B7=e=>{const{componentCls:t,motionDurationSlow:n,horizontalLineHeight:r,colorSplit:o,lineWidth:s,lineType:i,itemPaddingInline:l}=e;return{[`${t}-horizontal`]:{lineHeight:r,border:0,borderBottom:`${G(s)} ${i} ${o}`,boxShadow:"none","&::after":{display:"block",clear:"both",height:0,content:'"\\20"'},[`${t}-item, ${t}-submenu`]:{position:"relative",display:"inline-block",verticalAlign:"bottom",paddingInline:l},[`> ${t}-item:hover, + > ${t}-item-active, + > ${t}-submenu ${t}-submenu-title:hover`]:{backgroundColor:"transparent"},[`${t}-item, ${t}-submenu-title`]:{transition:["border-color","background-color"].map(c=>`${c} ${n}`).join(",")},[`${t}-submenu-arrow`]:{display:"none"}}}},L7=({componentCls:e,menuArrowOffset:t,calc:n})=>({[`${e}-rtl`]:{direction:"rtl"},[`${e}-submenu-rtl`]:{transformOrigin:"100% 0"},[`${e}-rtl${e}-vertical, + ${e}-submenu-rtl ${e}-vertical`]:{[`${e}-submenu-arrow`]:{"&::before":{transform:`rotate(-45deg) translateY(${G(n(t).mul(-1).equal())})`},"&::after":{transform:`rotate(45deg) translateY(${G(t)})`}}}}),BC=e=>jr(e),LC=(e,t)=>{const{componentCls:n,itemColor:r,itemSelectedColor:o,subMenuItemSelectedColor:s,groupTitleColor:i,itemBg:l,subMenuItemBg:c,itemSelectedBg:u,activeBarHeight:d,activeBarWidth:m,activeBarBorderWidth:f,motionDurationSlow:p,motionEaseInOut:y,motionEaseOut:b,itemPaddingInline:x,motionDurationMid:v,itemHoverColor:g,lineType:h,colorSplit:$,itemDisabledColor:C,dangerItemColor:N,dangerItemHoverColor:S,dangerItemSelectedColor:E,dangerItemActiveBg:w,dangerItemSelectedBg:R,popupBg:P,itemHoverBg:T,itemActiveBg:M,menuSubMenuBg:z,horizontalItemSelectedColor:B,horizontalItemSelectedBg:F,horizontalItemBorderRadius:L,horizontalItemHoverBg:j}=e;return{[`${n}-${t}, ${n}-${t} > ${n}`]:{color:r,background:l,[`&${n}-root:focus-visible`]:{...BC(e)},[`${n}-item`]:{"&-group-title, &-extra":{color:i}},[`${n}-submenu-selected > ${n}-submenu-title`]:{color:s},[`${n}-item, ${n}-submenu-title`]:{color:r,[`&:not(${n}-item-disabled):focus-visible`]:{...BC(e)}},[`${n}-item-disabled, ${n}-submenu-disabled`]:{color:`${C} !important`},[`${n}-item:not(${n}-item-selected):not(${n}-submenu-selected)`]:{[`&:hover, > ${n}-submenu-title:hover`]:{color:g}},[`${n}-submenu:not(${n}-submenu-selected)`]:{[`> ${n}-submenu-title:hover`]:{color:g}},[`&:not(${n}-horizontal)`]:{[`${n}-item:not(${n}-item-selected)`]:{"&:hover":{backgroundColor:T},"&:active":{backgroundColor:M}},[`${n}-submenu-title`]:{"&:hover":{backgroundColor:T},"&:active":{backgroundColor:M}}},[`${n}-item-danger`]:{color:N,[`&${n}-item:hover`]:{[`&:not(${n}-item-selected):not(${n}-submenu-selected)`]:{color:S}},[`&${n}-item:active`]:{background:w}},[`${n}-item a`]:{"&, &:hover":{color:"inherit"}},[`${n}-item-selected`]:{color:o,[`&${n}-item-danger`]:{color:E},"a, a:hover":{color:"inherit"}},[`& ${n}-item-selected`]:{backgroundColor:u,[`&${n}-item-danger`]:{backgroundColor:R}},[`&${n}-submenu > ${n}`]:{backgroundColor:z},[`&${n}-popup > ${n}`]:{backgroundColor:P},[`&${n}-submenu-popup > ${n}`]:{backgroundColor:P},[`&${n}-horizontal`]:{...t==="dark"?{borderBottom:0}:{},[`> ${n}-item, > ${n}-submenu`]:{top:f,marginTop:e.calc(f).mul(-1).equal(),marginBottom:0,borderRadius:L,"&::after":{position:"absolute",insetInline:x,bottom:0,borderBottom:`${G(d)} solid transparent`,transition:`border-color ${p} ${y}`,content:'""'},"&:hover, &-active, &-open":{background:j,"&::after":{borderBottomWidth:d,borderBottomColor:B}},"&-selected":{color:B,backgroundColor:F,"&:hover":{backgroundColor:F},"&::after":{borderBottomWidth:d,borderBottomColor:B}}}},[`&${n}-root`]:{[`&${n}-inline, &${n}-vertical`]:{borderInlineEnd:`${G(f)} ${h} ${$}`}},[`&${n}-inline`]:{[`${n}-sub${n}-inline`]:{background:c},[`${n}-item`]:{position:"relative","&::after":{position:"absolute",insetBlock:0,insetInlineEnd:0,borderInlineEnd:`${G(m)} solid ${o}`,transform:"scaleY(0.0001)",opacity:0,transition:["transform","opacity"].map(O=>`${O} ${v} ${b}`).join(","),content:'""'},[`&${n}-item-danger`]:{"&::after":{borderInlineEndColor:E}}},[`${n}-selected, ${n}-item-selected`]:{"&::after":{transform:"scaleY(1)",opacity:1,transition:["transform","opacity"].map(O=>`${O} ${v} ${y}`).join(",")}}}}}},kC=e=>{const{componentCls:t,itemHeight:n,itemMarginInline:r,padding:o,menuArrowSize:s,marginXS:i,itemMarginBlock:l,itemWidth:c,itemPaddingInline:u}=e,d=e.calc(s).add(o).add(i).equal();return{[`${t}-item`]:{position:"relative",overflow:"hidden"},[`${t}-item, ${t}-submenu-title`]:{height:n,lineHeight:G(n),paddingInline:u,overflow:"hidden",textOverflow:"ellipsis",marginInline:r,marginBlock:l,width:c},[`> ${t}-item, + > ${t}-submenu > ${t}-submenu-title`]:{height:n,lineHeight:G(n)},[`${t}-item-group-list ${t}-submenu-title, + ${t}-submenu-title`]:{paddingInlineEnd:d}}},k7=e=>{const{componentCls:t,iconCls:n,itemHeight:r,colorTextLightSolid:o,dropdownWidth:s,controlHeightLG:i,motionEaseOut:l,padding:c,paddingXL:u,itemMarginInline:d,fontSizeLG:m,motionDurationFast:f,motionDurationSlow:p,paddingXS:y,boxShadowSecondary:b,collapsedWidth:x,collapsedIconSize:v}=e,g={height:r,lineHeight:G(r),listStylePosition:"inside",listStyleType:"disc"};return[{[t]:{"&-inline, &-vertical":{[`&${t}-root`]:{boxShadow:"none"},...kC(e)}},[`${t}-submenu-popup`]:{[`${t}-vertical`]:{...kC(e),boxShadow:b}}},{[`${t}-submenu-popup ${t}-vertical${t}-sub`]:{minWidth:s,maxHeight:`calc(100vh - ${G(e.calc(i).mul(2.5).equal())})`,padding:"0",overflow:"hidden",borderInlineEnd:0,"&:not([class*='-active'])":{overflowX:"hidden",overflowY:"auto"}}},{[`${t}-inline`]:{width:"100%",[`&${t}-root`]:{[`${t}-item, ${t}-submenu-title`]:{display:"flex",alignItems:"center",transition:[`border-color ${p}`,`background-color ${p}`,`padding ${f} ${l}`].join(","),[`> ${t}-title-content`]:{flex:"auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},"> *":{flex:"none"}}},[`${t}-sub${t}-inline`]:{padding:0,border:0,borderRadius:0,boxShadow:"none",[`& > ${t}-submenu > ${t}-submenu-title`]:g,[`& ${t}-item-group-title`]:{paddingInlineStart:u}},[`${t}-item`]:g}},{[`${t}-inline-collapsed`]:{width:x,[`&${t}-root`]:{[`${t}-item, ${t}-submenu ${t}-submenu-title`]:{[`> ${t}-inline-collapsed-noicon`]:{fontSize:m,textAlign:"center",width:"100%"}}},[`> ${t}-item, + > ${t}-item-group > ${t}-item-group-list > ${t}-item, + > ${t}-item-group > ${t}-item-group-list > ${t}-submenu > ${t}-submenu-title, + > ${t}-submenu > ${t}-submenu-title`]:{display:"flex",alignItems:"center",justifyContent:"flex-start",insetInlineStart:0,paddingInline:`calc(50% - ${G(e.calc(v).div(2).equal())} - ${G(d)})`,textOverflow:"clip",[` + ${t}-submenu-arrow, + ${t}-submenu-expand-icon + `]:{opacity:0},[`> ${t}-title-content`]:{width:0,opacity:0,overflow:"hidden"},[`${t}-item-icon, ${n}`]:{margin:0,fontSize:v,lineHeight:G(r),"+ span":{display:"inline-block",width:0,opacity:0,overflow:"hidden",marginInlineStart:0}}},[`${t}-item-icon, ${n}`]:{display:"inline-block"},"&-tooltip":{pointerEvents:"none",[`${t}-item-icon, ${n}`]:{display:"none"},[`${t}-item-extra`]:{paddingInlineStart:c},"a, a:hover":{color:o}},[`${t}-item-group-title`]:{...ar,paddingInline:y}}}]},AC=e=>{const{componentCls:t,motionDurationSlow:n,motionDurationMid:r,motionEaseInOut:o,motionEaseOut:s,iconCls:i,iconSize:l,iconMarginInlineEnd:c}=e;return{[`${t}-item, ${t}-submenu-title`]:{position:"relative",display:"block",margin:0,whiteSpace:"nowrap",cursor:"pointer",transition:[`border-color ${n}`,`background-color ${n}`,`padding calc(${n} + 0.1s) ${o}`].join(","),[`${t}-item-icon, ${i}`]:{minWidth:l,fontSize:l,transition:[`font-size ${r} ${s}`,`margin ${n} ${o}`,`color ${n}`].join(","),"+ span":{marginInlineStart:c,opacity:1,transition:[`opacity ${n} ${o}`,`margin ${n}`,`color ${n}`].join(",")}},[`${t}-item-icon`]:{...Kc()},[`&${t}-item-only-child`]:{[`> ${i}, > ${t}-item-icon`]:{marginInlineEnd:0}}},[`${t}-item-disabled, ${t}-submenu-disabled`]:{background:"none !important",cursor:"not-allowed","&::after":{borderColor:"transparent !important"},a:{color:"inherit !important",cursor:"not-allowed",pointerEvents:"none"},[`> ${t}-submenu-title`]:{color:"inherit !important",cursor:"not-allowed"}}}},DC=e=>{const{componentCls:t,motionDurationSlow:n,motionEaseInOut:r,borderRadius:o,menuArrowSize:s,menuArrowOffset:i}=e;return{[`${t}-submenu`]:{"&-expand-icon, &-arrow":{position:"absolute",top:"50%",insetInlineEnd:e.margin,width:s,color:"currentcolor",transform:"translateY(-50%)",transition:["transform","opacity"].map(l=>`${l} ${n}`).join(",")},"&-arrow":{"&::before, &::after":{position:"absolute",width:e.calc(s).mul(.6).equal(),height:e.calc(s).mul(.15).equal(),backgroundColor:"currentcolor",borderRadius:o,transition:["background-color","transform","top","color"].map(l=>`${l} ${n} ${r}`).join(","),content:'""'},"&::before":{transform:`rotate(45deg) translateY(${G(e.calc(i).mul(-1).equal())})`},"&::after":{transform:`rotate(-45deg) translateY(${G(i)})`}}}}},A7=e=>{const{antCls:t,componentCls:n,fontSize:r,motionDurationSlow:o,motionDurationMid:s,motionEaseInOut:i,paddingXS:l,padding:c,colorSplit:u,lineWidth:d,zIndexPopup:m,borderRadiusLG:f,subMenuItemBorderRadius:p,menuArrowSize:y,menuArrowOffset:b,lineType:x,groupTitleLineHeight:v,groupTitleFontSize:g,iconSize:h,iconMarginInlineEnd:$}=e,C=[`> ${t}-typography-ellipsis-single-line`,`> ${n}-item-label > ${t}-typography-ellipsis-single-line`].join(",");return[{"":{[n]:{...Ls(),"&-hidden":{display:"none"}}},[`${n}-submenu-hidden`]:{display:"none"}},{[n]:{...Ft(e),...Ls(),marginBottom:0,paddingInlineStart:0,fontSize:r,lineHeight:0,listStyle:"none",outline:"none",transition:`width ${o} cubic-bezier(0.2, 0, 0, 1) 0s`,"ul, ol":{margin:0,padding:0,listStyle:"none"},"&-overflow":{display:"flex",[`${n}-item`]:{flex:"none"}},[`${n}-item, ${n}-submenu, ${n}-submenu-title`]:{borderRadius:e.itemBorderRadius},[`${n}-item-group-title`]:{padding:`${G(l)} ${G(c)}`,fontSize:g,lineHeight:v,transition:`all ${o}`},[`&-horizontal ${n}-submenu`]:{transition:["border-color","background-color"].map(N=>`${N} ${o} ${i}`).join(",")},[`${n}-submenu, ${n}-submenu-inline`]:{transition:[`border-color ${o}`,`background-color ${o}`,`padding ${s}`].map(N=>`${N} ${i}`).join(",")},[`${n}-submenu ${n}-sub`]:{cursor:"initial",transition:["background-color","padding"].map(N=>`${N} ${o} ${i}`).join(",")},[`${n}-title-content`]:{transition:`color ${o}`,"&-with-extra":{display:"inline-flex",alignItems:"center",width:"100%",minWidth:0},[`${n}-item-label`]:{flex:"auto",minWidth:0,...ar},[C]:{display:"inline",verticalAlign:"unset"},[`${n}-item-extra`]:{flex:"none",marginInlineStart:"auto",paddingInlineStart:e.padding}},[`${n}-item-icon + ${n}-title-content-with-extra`]:{width:`calc(100% - ${G(e.calc(h).add($??0).equal())})`},[`${n}-item a`]:{"&::before":{position:"absolute",inset:0,backgroundColor:"transparent",content:'""'}},[`${n}-item-divider`]:{overflow:"hidden",lineHeight:0,borderColor:u,borderStyle:x,borderWidth:0,borderTopWidth:d,marginBlock:d,padding:0,"&-dashed":{borderStyle:"dashed"}},...AC(e),[`${n}-item-group`]:{[`${n}-item-group-list`]:{margin:0,padding:0,[`${n}-item, ${n}-submenu-title`]:{paddingInline:`${G(e.calc(r).mul(2).equal())} ${G(c)}`}}},"&-submenu":{"&-popup":{position:"absolute",zIndex:m,borderRadius:f,boxShadow:"none",transformOrigin:"0 0",[`&${n}-submenu`]:{background:"transparent"},"&::before":{position:"absolute",inset:0,zIndex:-1,width:"100%",height:"100%",opacity:0,content:'""'},[`> ${n}`]:{borderRadius:f,...AC(e),...DC(e),[`${n}-item, ${n}-submenu > ${n}-submenu-title`]:{borderRadius:p},[`${n}-submenu-title::after`]:{transition:`transform ${o} ${i}`}}},"&-placement-leftTop, &-placement-bottomRight":{transformOrigin:"100% 0"},"&-placement-leftBottom, &-placement-topRight":{transformOrigin:"100% 100%"},"&-placement-rightBottom, &-placement-topLeft":{transformOrigin:"0 100%"},"&-placement-bottomLeft, &-placement-rightTop":{transformOrigin:"0 0"},"&-placement-leftTop, &-placement-leftBottom":{paddingInlineEnd:e.paddingXS},"&-placement-rightTop, &-placement-rightBottom":{paddingInlineStart:e.paddingXS},"&-placement-topRight, &-placement-topLeft":{paddingBottom:e.paddingXS},"&-placement-bottomRight, &-placement-bottomLeft":{paddingTop:e.paddingXS}},...DC(e),[`&-inline-collapsed ${n}-submenu-arrow, + &-inline ${n}-submenu-arrow`]:{"&::before":{transform:`rotate(-45deg) translateX(${G(b)})`},"&::after":{transform:`rotate(45deg) translateX(${G(e.calc(b).mul(-1).equal())})`}},[`${n}-submenu-open${n}-submenu-inline > ${n}-submenu-title > ${n}-submenu-arrow`]:{transform:`translateY(${G(e.calc(y).mul(.2).mul(-1).equal())})`,"&::after":{transform:`rotate(-45deg) translateX(${G(e.calc(b).mul(-1).equal())})`},"&::before":{transform:`rotate(45deg) translateX(${G(b)})`}}}},{[`${t}-layout-header`]:{[n]:{lineHeight:"inherit"}}}]},D7=e=>{const{colorPrimary:t,colorError:n,colorTextDisabled:r,colorErrorBg:o,colorText:s,colorTextDescription:i,colorBgContainer:l,colorFillAlter:c,colorFillContent:u,lineWidth:d,lineWidthBold:m,controlItemBgActive:f,colorBgTextHover:p,controlHeightLG:y,lineHeight:b,colorBgElevated:x,marginXXS:v,padding:g,fontSize:h,controlHeightSM:$,fontSizeLG:C,colorTextLightSolid:N,colorErrorHover:S}=e,E=e.activeBarWidth??0,w=e.activeBarBorderWidth??d,R=e.itemMarginInline??e.marginXXS,P=new Gt(N).setA(.65).toRgbString();return{dropdownWidth:160,zIndexPopup:e.zIndexPopupBase+50,radiusItem:e.borderRadiusLG,itemBorderRadius:e.borderRadiusLG,radiusSubMenuItem:e.borderRadiusSM,subMenuItemBorderRadius:e.borderRadiusSM,colorItemText:s,itemColor:s,colorItemTextHover:s,itemHoverColor:s,colorItemTextHoverHorizontal:t,horizontalItemHoverColor:t,colorGroupTitle:i,groupTitleColor:i,colorItemTextSelected:t,itemSelectedColor:t,subMenuItemSelectedColor:t,colorItemTextSelectedHorizontal:t,horizontalItemSelectedColor:t,colorItemBg:l,itemBg:l,colorItemBgHover:p,itemHoverBg:p,colorItemBgActive:u,itemActiveBg:f,colorSubItemBg:c,subMenuItemBg:c,colorItemBgSelected:f,itemSelectedBg:f,colorItemBgSelectedHorizontal:"transparent",horizontalItemSelectedBg:"transparent",colorActiveBarWidth:0,activeBarWidth:E,colorActiveBarHeight:m,activeBarHeight:m,colorActiveBarBorderSize:d,activeBarBorderWidth:w,colorItemTextDisabled:r,itemDisabledColor:r,colorDangerItemText:n,dangerItemColor:n,colorDangerItemTextHover:n,dangerItemHoverColor:n,colorDangerItemTextSelected:n,dangerItemSelectedColor:n,colorDangerItemBgActive:o,dangerItemActiveBg:o,colorDangerItemBgSelected:o,dangerItemSelectedBg:o,itemMarginInline:R,horizontalItemBorderRadius:0,horizontalItemHoverBg:"transparent",itemHeight:y,groupTitleLineHeight:b,collapsedWidth:y*2,popupBg:x,itemMarginBlock:v,itemPaddingInline:g,horizontalLineHeight:`${y*1.15}px`,iconSize:h,iconMarginInlineEnd:$-h,collapsedIconSize:C,groupTitleFontSize:h,darkItemDisabledColor:new Gt(N).setA(.25).toRgbString(),darkItemColor:P,darkDangerItemColor:n,darkItemBg:"#001529",darkPopupBg:"#001529",darkSubMenuItemBg:"#000c17",darkItemSelectedColor:N,darkItemSelectedBg:t,darkDangerItemSelectedBg:n,darkItemHoverBg:"transparent",darkGroupTitleColor:P,darkItemHoverColor:N,darkDangerItemHoverColor:S,darkDangerItemSelectedColor:N,darkDangerItemActiveBg:n,itemWidth:E?`calc(100% + ${w}px)`:`calc(100% - ${R*2}px)`}},F7=(e,t=e,n=!0)=>Tt("Menu",o=>{const{colorBgElevated:s,controlHeightLG:i,fontSize:l,darkItemColor:c,darkDangerItemColor:u,darkItemBg:d,darkSubMenuItemBg:m,darkItemSelectedColor:f,darkItemSelectedBg:p,darkDangerItemSelectedBg:y,darkItemHoverBg:b,darkGroupTitleColor:x,darkItemHoverColor:v,darkItemDisabledColor:g,darkDangerItemHoverColor:h,darkDangerItemSelectedColor:$,darkDangerItemActiveBg:C,popupBg:N,darkPopupBg:S}=o,E=o.calc(l).div(7).mul(5).equal(),w=Rt(o,{menuArrowSize:E,menuHorizontalHeight:o.calc(i).mul(1.15).equal(),menuArrowOffset:o.calc(E).mul(.25).equal(),menuSubMenuBg:s,calc:o.calc,popupBg:N}),R=Rt(w,{itemColor:c,itemHoverColor:v,groupTitleColor:x,itemSelectedColor:f,subMenuItemSelectedColor:f,itemBg:d,popupBg:S,subMenuItemBg:m,itemActiveBg:"transparent",itemSelectedBg:p,activeBarHeight:0,activeBarBorderWidth:0,itemHoverBg:b,itemDisabledColor:g,dangerItemColor:u,dangerItemHoverColor:h,dangerItemSelectedColor:$,dangerItemActiveBg:C,dangerItemSelectedBg:y,menuSubMenuBg:m,horizontalItemSelectedColor:f,horizontalItemSelectedBg:p});return[A7(w),B7(w),k7(w),LC(w,"light"),LC(R,"dark"),L7(w),gx(w),No(w,"slide-up"),No(w,"slide-down"),Yc(w,"zoom-big")]},D7,{deprecatedTokens:[["colorGroupTitle","groupTitleColor"],["radiusItem","itemBorderRadius"],["radiusSubMenuItem","subMenuItemBorderRadius"],["colorItemText","itemColor"],["colorItemTextHover","itemHoverColor"],["colorItemTextHoverHorizontal","horizontalItemHoverColor"],["colorItemTextSelected","itemSelectedColor"],["colorItemTextSelectedHorizontal","horizontalItemSelectedColor"],["colorItemTextDisabled","itemDisabledColor"],["colorDangerItemText","dangerItemColor"],["colorDangerItemTextHover","dangerItemHoverColor"],["colorDangerItemTextSelected","dangerItemSelectedColor"],["colorDangerItemBgActive","dangerItemActiveBg"],["colorDangerItemBgSelected","dangerItemSelectedBg"],["colorItemBg","itemBg"],["colorItemBgHover","itemHoverBg"],["colorSubItemBg","subMenuItemBg"],["colorItemBgActive","itemActiveBg"],["colorItemBgSelectedHorizontal","horizontalItemSelectedBg"],["colorActiveBarWidth","activeBarWidth"],["colorActiveBarHeight","activeBarHeight"],["colorActiveBarBorderSize","activeBarBorderWidth"],["colorItemBgSelected","itemSelectedBg"]],injectStyle:n,unitless:{groupTitleLineHeight:!0}})(e,t),L4=e=>{var b,x,v,g,h,$;const{popupClassName:t,icon:n,title:r,theme:o}=e,s=a.useContext(bf),{prefixCls:i,inlineCollapsed:l,theme:c,classNames:u,styles:d}=s,m=nl();let f;if(!n)f=l&&!m.length&&r&&typeof r=="string"?a.createElement("div",{className:`${i}-inline-collapsed-noicon`},r.charAt(0)):a.createElement("span",{className:`${i}-title-content`},r);else{const C=a.isValidElement(r)&&r.type==="span";f=a.createElement(a.Fragment,null,Fn(n,N=>({className:H(N.className,`${i}-item-icon`,u==null?void 0:u.itemIcon),style:{...N.style,...d==null?void 0:d.itemIcon}})),C?r:a.createElement("span",{className:`${i}-title-content`},r))}const p=a.useMemo(()=>({...s,firstLevel:!1}),[s]),[y]=Xc("Menu");return a.createElement(bf.Provider,{value:p},a.createElement(zm,{...Dt(e,["icon"]),title:f,classNames:{list:(b=u==null?void 0:u.subMenu)==null?void 0:b.list,listTitle:(x=u==null?void 0:u.subMenu)==null?void 0:x.itemTitle},styles:{list:(v=d==null?void 0:d.subMenu)==null?void 0:v.list,listTitle:(g=d==null?void 0:d.subMenu)==null?void 0:g.itemTitle},popupClassName:H(i,t,(h=u==null?void 0:u.popup)==null?void 0:h.root,`${i}-${o||c}`),popupStyle:{zIndex:y,...e.popupStyle,...($=d==null?void 0:d.popup)==null?void 0:$.root}}))};function ug(e){return e===null||e===!1}const H7={item:j4,submenu:L4,divider:z4},V7=a.forwardRef((e,t)=>{var U;const n=a.useContext(xf),r=n||{},{prefixCls:o,className:s,style:i,theme:l="light",expandIcon:c,_internalDisableMenuItemTitleTooltip:u,tooltip:d,inlineCollapsed:m,siderCollapsed:f,rootClassName:p,mode:y,selectable:b,onClick:x,overflowedIndicatorPopupClassName:v,classNames:g,styles:h,...$}=e,{menu:C}=a.useContext(ct),{getPrefixCls:N,getPopupContainer:S,direction:E,className:w,style:R,classNames:P,styles:T}=Pt("menu"),M=N(),z=Dt($,["collapsedWidth"]);(U=r.validator)==null||U.call(r,{mode:y});const B=vt((...Q)=>{var Z;x==null||x(...Q),(Z=r.onClick)==null||Z.call(r)}),F=r.mode||y,L=b??r.selectable,j=m??f,O={...e,mode:F,inlineCollapsed:j,selectable:L,theme:l},A=Mt(R),k=Mt(i),[_,D]=Ot([P,g],[T,A,h,k],{props:O},{popup:{_default:"root"},subMenu:{_default:"item"}}),V={horizontal:{motionName:`${M}-slide-up`},inline:cf(M),other:{motionName:`${M}-zoom-big`}},W=N("menu",o||r.prefixCls),K=on(W),[q,Y]=F7(W,K,!n),ee=H(`${W}-${l}`,w,s),ie=a.useMemo(()=>{var Z;if(bt(c)||ug(c))return c||null;if(bt(r.expandIcon)||ug(r.expandIcon))return r.expandIcon||null;if(bt(C==null?void 0:C.expandIcon)||ug(C==null?void 0:C.expandIcon))return(C==null?void 0:C.expandIcon)||null;const Q=c??(r==null?void 0:r.expandIcon)??(C==null?void 0:C.expandIcon);return Fn(Q,{className:H(`${W}-submenu-expand-icon`,a.isValidElement(Q)?(Z=Q.props)==null?void 0:Z.className:void 0)})},[c,r==null?void 0:r.expandIcon,C==null?void 0:C.expandIcon,W]),ae=a.useMemo(()=>({prefixCls:W,inlineCollapsed:j||!1,direction:E,firstLevel:!0,theme:l,mode:F,disableMenuItemTitleTooltip:u,tooltip:d,classNames:_,styles:D}),[W,j,E,u,l,F,_,D,d]);return a.createElement(xf.Provider,{value:null},a.createElement(bf.Provider,{value:ae},a.createElement(rl,{getPopupContainer:S,overflowedIndicator:a.createElement(Bm,null),overflowedIndicatorPopupClassName:H(W,`${W}-${l}`,v),classNames:{list:_.list,listTitle:_.itemTitle},styles:{list:D.list,listTitle:D.itemTitle},mode:F,selectable:L,onClick:B,...z,inlineCollapsed:j,style:D.root,className:ee,prefixCls:W,direction:E,defaultMotions:V,expandIcon:ie,ref:t,rootClassName:H(p,q,r.rootClassName,Y,K,_.root),_internalComponents:H7})))}),Fi=a.forwardRef((e,t)=>{const n=a.useRef(null),r=a.useContext(jm);return a.useImperativeHandle(t,()=>({menu:n.current,focus:o=>{var s;(s=n.current)==null||s.focus(o)}})),a.createElement(V7,{ref:n,...e,...r})});Fi.Item=j4;Fi.SubMenu=L4;Fi.Divider=z4;Fi.ItemGroup=Hx;const W7=e=>{const{componentCls:t,menuCls:n,colorError:r,colorTextLightSolid:o}=e,s=`${n}-item`;return{[`${t}, ${t}-menu-submenu`]:{[`${n} ${s}`]:{[`&${s}-danger:not(${s}-disabled)`]:{color:r,"&:hover":{color:o,backgroundColor:r}}}}}},K7=e=>{const{componentCls:t,menuCls:n,zIndexPopup:r,dropdownArrowDistance:o,sizePopupArrow:s,antCls:i,iconCls:l,motionDurationMid:c,paddingBlock:u,fontSize:d,dropdownEdgeChildPadding:m,colorTextDisabled:f,fontSizeIcon:p,controlPaddingHorizontal:y,colorBgElevated:b,controlHeightLG:x}=e;return[{[t]:{position:"absolute",top:-9999,left:{_skip_check_:!0,value:-9999},zIndex:r,display:"block","&::before":{position:"absolute",insetBlock:e.calc(s).div(2).sub(o).equal(),zIndex:-9999,opacity:1e-4,content:'""'},"&-menu-vertical":{maxHeight:`calc(100vh - ${G(e.calc(x).mul(2.5).equal())})`,overflowY:"auto"},[`&-trigger${i}-btn`]:{[`& > ${l}-down, & > ${i}-btn-icon > ${l}-down`]:{fontSize:p}},[`${t}-wrap`]:{position:"relative",[`${i}-btn > ${l}-down`]:{fontSize:p},[`${l}-down::before`]:{transition:`transform ${c}`}},[`${t}-wrap-open`]:{[`${l}-down::before`]:{transform:"rotate(180deg)"}},"&-hidden, &-menu-hidden, &-menu-submenu-hidden":{display:"none"},[`&${i}-slide-down-enter${i}-slide-down-enter-active${t}-placement-bottomLeft, + &${i}-slide-down-appear${i}-slide-down-appear-active${t}-placement-bottomLeft, + &${i}-slide-down-enter${i}-slide-down-enter-active${t}-placement-bottom, + &${i}-slide-down-appear${i}-slide-down-appear-active${t}-placement-bottom, + &${i}-slide-down-enter${i}-slide-down-enter-active${t}-placement-bottomRight, + &${i}-slide-down-appear${i}-slide-down-appear-active${t}-placement-bottomRight`]:{animationName:Em},[`&${i}-slide-up-enter${i}-slide-up-enter-active${t}-placement-topLeft, + &${i}-slide-up-appear${i}-slide-up-appear-active${t}-placement-topLeft, + &${i}-slide-up-enter${i}-slide-up-enter-active${t}-placement-top, + &${i}-slide-up-appear${i}-slide-up-appear-active${t}-placement-top, + &${i}-slide-up-enter${i}-slide-up-enter-active${t}-placement-topRight, + &${i}-slide-up-appear${i}-slide-up-appear-active${t}-placement-topRight`]:{animationName:Pm},[`&${i}-slide-down-leave${i}-slide-down-leave-active${t}-placement-bottomLeft, + &${i}-slide-down-leave${i}-slide-down-leave-active${t}-placement-bottom, + &${i}-slide-down-leave${i}-slide-down-leave-active${t}-placement-bottomRight`]:{animationName:Im},[`&${i}-slide-up-leave${i}-slide-up-leave-active${t}-placement-topLeft, + &${i}-slide-up-leave${i}-slide-up-leave-active${t}-placement-top, + &${i}-slide-up-leave${i}-slide-up-leave-active${t}-placement-topRight`]:{animationName:Nm},[`&${i}-slide-right-enter${i}-slide-right-enter-active${t}-placement-right, + &${i}-slide-right-appear${i}-slide-right-appear-active${t}-placement-right, + &${i}-slide-right-enter${i}-slide-right-enter-active${t}-placement-rightTop, + &${i}-slide-right-appear${i}-slide-right-appear-active${t}-placement-rightTop, + &${i}-slide-right-enter${i}-slide-right-enter-active${t}-placement-rightBottom, + &${i}-slide-right-appear${i}-slide-right-appear-active${t}-placement-rightBottom`]:{animationName:VN},[`&${i}-slide-left-enter${i}-slide-left-enter-active${t}-placement-left, + &${i}-slide-left-appear${i}-slide-left-appear-active${t}-placement-left, + &${i}-slide-left-enter${i}-slide-left-enter-active${t}-placement-leftTop, + &${i}-slide-left-appear${i}-slide-left-appear-active${t}-placement-leftTop, + &${i}-slide-left-enter${i}-slide-left-enter-active${t}-placement-leftBottom, + &${i}-slide-left-appear${i}-slide-left-appear-active${t}-placement-leftBottom`]:{animationName:KN},[`&${i}-slide-right-leave${i}-slide-right-leave-active${t}-placement-right, + &${i}-slide-right-leave${i}-slide-right-leave-active${t}-placement-rightTop, + &${i}-slide-right-leave${i}-slide-right-leave-active${t}-placement-rightBottom`]:{animationName:WN},[`&${i}-slide-left-leave${i}-slide-left-leave-active${t}-placement-left, + &${i}-slide-left-leave${i}-slide-left-leave-active${t}-placement-leftTop, + &${i}-slide-left-leave${i}-slide-left-leave-active${t}-placement-leftBottom`]:{animationName:UN}}},_x(e,b),{[`${t} ${n}`]:{position:"relative",margin:0},[`${n}-submenu-popup`]:{position:"absolute",zIndex:r,background:"transparent",boxShadow:"none",transformOrigin:"0 0","ul, li":{listStyle:"none",margin:0}},[`${t}, ${t}-menu-submenu`]:{...Ft(e),[n]:{padding:m,listStyleType:"none",backgroundColor:b,backgroundClip:"padding-box",borderRadius:e.borderRadiusLG,outline:"none",boxShadow:e.boxShadowSecondary,...Br(e),"&:empty":{padding:0,boxShadow:"none"},[`${n}-item-group-title`]:{padding:`${G(u)} ${G(y)}`,color:e.colorTextDescription,transition:`all ${c}`},[`${n}-item`]:{position:"relative",display:"flex",alignItems:"center"},[`${n}-item-icon`]:{minWidth:d,marginInlineEnd:e.marginXS,fontSize:e.fontSizeSM},[`${n}-title-content`]:{flex:"auto","&-with-extra":{display:"inline-flex",alignItems:"center",width:"100%"},[`> a, > ${n}-item-label > a`]:{color:"inherit",transition:`all ${c}`,"&:hover":{color:"inherit"},"&::after":{position:"absolute",inset:0,content:'""'}},[`${n}-item-extra`]:{paddingInlineStart:e.padding,marginInlineStart:"auto",fontSize:e.fontSizeSM,color:e.colorTextDescription}},[`${n}-item, ${n}-submenu-title`]:{display:"flex",margin:0,padding:`${G(u)} ${G(y)}`,color:e.colorText,fontWeight:"normal",fontSize:d,lineHeight:e.lineHeight,cursor:"pointer",transition:`all ${c}`,borderRadius:e.borderRadiusSM,"&:hover, &-active":{backgroundColor:e.controlItemBgHover},...Br(e),"&-selected":{color:e.colorPrimary,backgroundColor:e.controlItemBgActive,"&:hover, &-active":{backgroundColor:e.controlItemBgActiveHover}},"&-disabled":{color:f,cursor:"not-allowed","&:hover":{color:f,backgroundColor:b,cursor:"not-allowed"},a:{pointerEvents:"none"}},"&-divider":{height:1,margin:`${G(e.marginXXS)} 0`,overflow:"hidden",lineHeight:0,backgroundColor:e.colorSplit},[`${t}-menu-submenu-expand-icon`]:{position:"absolute",insetInlineEnd:e.paddingXS,[`${t}-menu-submenu-arrow-icon`]:{marginInlineEnd:"0 !important",color:e.colorIcon,fontSize:p,fontStyle:"normal"}}},[`${n}-item-group-list`]:{margin:`0 ${G(e.marginXS)}`,padding:0,listStyle:"none"},[`${n}-submenu-title`]:{paddingInlineEnd:e.calc(y).add(e.fontSizeSM).equal()},[`${n}-submenu-vertical`]:{position:"relative"},[`${n}-submenu${n}-submenu-disabled ${t}-menu-submenu-title`]:{[`&, ${t}-menu-submenu-arrow-icon`]:{color:f,backgroundColor:b,cursor:"not-allowed"}},[`${n}-submenu-selected ${t}-menu-submenu-title`]:{color:e.colorPrimary}}}},[No(e,"slide-up"),No(e,"slide-down"),No(e,"slide-left"),No(e,"slide-right"),uf(e,"move-up"),uf(e,"move-down"),Yc(e,"zoom-big")]]},U7=e=>({zIndexPopup:e.zIndexPopupBase+50,paddingBlock:(e.controlHeight-e.fontSize*e.lineHeight)/2,...Om({contentRadius:e.borderRadiusLG,limitVerticalRadius:!0}),...Ox(e)}),q7=Tt("Dropdown",e=>{const{marginXXS:t,sizePopupArrow:n,paddingXXS:r,componentCls:o}=e,s=Rt(e,{menuCls:`${o}-menu`,dropdownArrowDistance:e.calc(n).div(2).add(t).equal(),dropdownEdgeChildPadding:r});return[K7(s),W7(s)]},U7,{resetStyle:!1}),Lm=a.forwardRef((e,t)=>{const{menu:n,arrow:r,prefixCls:o,children:s,trigger:i,disabled:l,dropdownRender:c,popupRender:u,getPopupContainer:d,overlayClassName:m,rootClassName:f,overlayStyle:p,open:y,onOpenChange:b,mouseEnterDelay:x=.15,mouseLeaveDelay:v=.1,autoAdjustOverflow:g=!0,placement:h="",transitionName:$,classNames:C,styles:N,destroyPopupOnHide:S,destroyOnHidden:E}=e,{getPrefixCls:w,direction:R,getPopupContainer:P,className:T,style:M,classNames:z,styles:B}=Pt("dropdown"),F={...e,mouseEnterDelay:x,mouseLeaveDelay:v,autoAdjustOverflow:g},[L,j]=Ot([z,C],[B,N],{props:F}),O={...M,...p,...j.root},A=u||c;yo();const k=a.useMemo(()=>{const Se=w();return $!==void 0?$:h.startsWith("top")?`${Se}-slide-down`:h.startsWith("left")?`${Se}-slide-right`:h.startsWith("right")?`${Se}-slide-left`:`${Se}-slide-up`},[w,h,$]),_=a.useMemo(()=>h?h.includes("Center")?h.slice(0,h.indexOf("Center")):h:R==="rtl"?"bottomRight":"bottomLeft",[h,R]),D=w("dropdown",o),V=on(D),[W,K]=q7(D,V),[,q]=Yn(),Y=a.Children.only(P5(s)?a.createElement("span",null,s):s),ee=$o(t,Bo(Y)),ie=Fn(Y,{className:H(`${D}-trigger`,{[`${D}-rtl`]:R==="rtl"},Y.props.className),disabled:Y.props.disabled??l,ref:ee}),ae=l?[]:i,U=!!(ae!=null&&ae.includes("contextMenu")),[Q,Z]=nn(!1,y),ne=vt(Se=>{b==null||b(Se,{source:"trigger"}),Z(Se)}),oe=H(m,f,W,K,V,T,L.root,{[`${D}-rtl`]:R==="rtl"}),le=s4({arrowPointAtCenter:dt(r)&&r.pointAtCenter,autoAdjustOverflow:g,offset:q.marginXXS,arrowWidth:r?q.sizePopupArrow:0,borderRadius:q.borderRadius}),re=vt(()=>{n!=null&&n.selectable&&(n!=null&&n.multiple)||(b==null||b(!1,{source:"menu"}),Z(!1))}),X=()=>{const Se=Dt(L,["root"]),ue=Dt(j,["root"]);let be;return n!=null&&n.items&&(be=a.createElement(Fi,{...n,classNames:{...Se,subMenu:{...Se}},styles:{...ue,subMenu:{...ue}}})),A&&(be=A(be)),be=a.Children.only(typeof be=="string"?a.createElement("span",null,be):be),a.createElement(B4,{prefixCls:`${D}-menu`,rootClassName:H(K,V),expandIcon:a.createElement("span",{className:`${D}-menu-submenu-arrow`},R==="rtl"?a.createElement(Tc,{className:`${D}-menu-submenu-arrow-icon`}):a.createElement(Nc,{className:`${D}-menu-submenu-arrow-icon`})),mode:"vertical",selectable:!1,onClick:re,validator:({mode:Ne})=>{}},be)},[se,ge]=Xc("Dropdown",O.zIndex);let de=a.createElement(p4,{alignPoint:U,...Dt(e,["rootClassName","onOpenChange"]),mouseEnterDelay:x,mouseLeaveDelay:v,visible:Q,builtinPlacements:le,arrow:!!r,overlayClassName:oe,prefixCls:D,getPopupContainer:d||P,transitionName:k,trigger:ae,overlay:X,placement:_,onVisibleChange:ne,overlayStyle:{...O,zIndex:se},autoDestroy:E??S},ie);return se&&(de=a.createElement(xm.Provider,{value:ge},de)),de}),G7=_R(Lm,"align",void 0,"dropdown",e=>e),X7=e=>a.createElement(G7,{...e},a.createElement("span",null));Lm._InternalPanelDoNotUseOrYouWillBeFired=X7;const Y7=["parentNode"],Q7="form_item";function Xl(e){return e===void 0||e===!1?[]:Array.isArray(e)?e:[e]}function k4(e,t){if(!e.length)return;const n=e.join("_");return t?`${t}_${n}`:Y7.includes(n)?`${Q7}_${n}`:n}function A4(e,t,n,r,o,s){let i=r;return s!==void 0?i=s:n.validating?i="validating":e.length?i="error":t.length?i="warning":(n.touched||o&&n.validated)&&(i="success"),i}function bv(e){return Xl(e).join("_")}function FC(e,t){const n=t.getFieldInstance(e),r=go(n);if(r)return r;const o=k4(Xl(e),t.__INTERNAL__.name);if(o)return document.getElementById(o)}function D4(e){const[t]=xx(),n=a.useRef({}),r=a.useMemo(()=>e??{...t,__INTERNAL__:{itemRef:o=>s=>{const i=bv(o);s?n.current[i]=s:delete n.current[i]}},scrollToField:(o,s={})=>{const{focus:i,...l}=s,c=FC(o,r);c&&(H5(c,{scrollMode:"if-needed",block:"nearest",...l}),i&&r.focusField(o))},focusField:o=>{var i,l;const s=r.getFieldInstance(o);bt(s==null?void 0:s.focus)?s.focus():(l=(i=FC(o,r))==null?void 0:i.focus)==null||l.call(i)},getFieldInstance:o=>{const s=bv(o);return n.current[s]}},[e,t]);return[r]}const F4=a.createContext(void 0),J7=F4.Provider,H4=a.createContext(void 0),Z7=H4.Provider;function xv(){return xv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n="rc-checkbox",className:r,style:o,checked:s,disabled:i,defaultChecked:l=!1,type:c="checkbox",title:u,onChange:d,...m}=e,f=a.useRef(null),p=a.useRef(null),[y,b]=nn(l,s);a.useImperativeHandle(t,()=>({focus:g=>{var h;(h=f.current)==null||h.focus(g)},blur:()=>{var g;(g=f.current)==null||g.blur()},input:f.current,nativeElement:p.current}));const x=H(n,r,{[`${n}-checked`]:y,[`${n}-disabled`]:i}),v=g=>{i||("checked"in e||b(g.target.checked),d==null||d({target:{...e,type:c,checked:g.target.checked},stopPropagation(){g.stopPropagation()},preventDefault(){g.preventDefault()},nativeEvent:g.nativeEvent}))};return a.createElement("span",{className:x,title:u,style:o,ref:p},a.createElement("input",xv({},m,{className:`${n}-input`,ref:f,onChange:v,disabled:i,checked:!!y,type:c})))});function W4(e){const t=J.useRef(null),n=()=>{Ct.cancel(t.current),t.current=null};return[()=>{n(),t.current=Ct(()=>{t.current=null})},s=>{t.current&&(s.stopPropagation(),n()),e==null||e(s)}]}const eH=e=>{const{componentCls:t,antCls:n,lineWidth:r,borderRadius:o,borderRadiusLG:s,borderRadiusSM:i,calc:l}=e,c=`${t}-group`,u=`${t}-button-wrapper`,d=`${n}-badge`,m=f=>({[`> ${d}`]:{width:"auto"},[`> ${d} > ${u}`]:{width:"100%"},[`> ${d}:not(:last-child)`]:{marginBlockEnd:l(r).mul(-1).equal()},[`> ${d} > ${u}:not(:last-child)`]:{marginBlockEnd:0},[`> ${d}:first-child > ${u}`]:{borderStartStartRadius:f,borderStartEndRadius:f,borderEndStartRadius:0,borderEndEndRadius:0},[`> ${d}:last-child > ${u}`]:{borderStartStartRadius:0,borderStartEndRadius:0,borderEndStartRadius:f,borderEndEndRadius:f},[`> ${d}:not(:first-child):not(:last-child) > ${u}`]:{borderRadius:0},[`> ${d}:first-child:last-child > ${u}`]:{borderRadius:f}});return{[c]:{...Ft(e),display:"inline-block",fontSize:0,[`&${c}-rtl`]:{direction:"rtl"},[`&${c}-block`]:{display:"flex"},[`${n}-badge ${n}-badge-count`]:{zIndex:1},[`> ${n}-badge:not(:first-child) > ${n}-button-wrapper`]:{borderInlineStart:"none"},"&-vertical":{display:"flex",flexDirection:"column",rowGap:e.marginXS,[`&:has(> ${u}, > ${d} > ${u})`]:{rowGap:0},[`${t}-wrapper`]:{marginInlineEnd:0},...m(o),[`&${c}-large`]:{...m(s)},[`&${c}-small`]:{...m(i)}}}}},tH=e=>{const{componentCls:t,wrapperMarginInlineEnd:n,colorPrimary:r,colorPrimaryHover:o,radioSize:s,motionDurationSlow:i,motionDurationMid:l,motionEaseInOutCirc:c,colorBgContainer:u,colorBorder:d,lineWidth:m,colorBgContainerDisabled:f,colorTextDisabled:p,paddingXS:y,dotColorDisabled:b,dotSize:x,lineType:v,radioColor:g,radioBgColor:h}=e;return{[`${t}-wrapper`]:{...Ft(e),display:"inline-flex",alignItems:"baseline",marginInlineStart:0,marginInlineEnd:n,cursor:"pointer","&:last-child":{marginInlineEnd:0},[`&${t}-wrapper-rtl`]:{direction:"rtl"},"&-disabled":{cursor:"not-allowed",color:e.colorTextDisabled},"&::after":{display:"inline-block",width:0,overflow:"hidden",content:'"\\a0"'},"&-block":{flex:1,justifyContent:"center"},[t]:{...Ft(e),position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",alignSelf:"center",boxSizing:"border-box",display:"block",width:`calc(${s} * 1px)`,height:`calc(${s} * 1px)`,backgroundColor:u,border:`${G(m)} ${v} ${d}`,borderRadius:"50%",transition:`all ${l}`,flex:"none","&:after":{content:'""',position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%) scale(0)",width:`calc(${x} * 1px)`,height:`calc(${x} * 1px)`,backgroundColor:g,borderRadius:"50%",transformOrigin:"50% 50%",opacity:0,transition:`all ${i} ${c}`},[`${t}-input`]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0},[`&:has(${t}-input:focus-visible)`]:jr(e)},[`&:hover:not(${t}-wrapper-disabled) ${t}`]:{borderColor:r},[`&:hover ${t}-checked:not(${t}-disabled)`]:{backgroundColor:o,borderColor:"transparent"},[`${t}-checked`]:{backgroundColor:h,borderColor:r,"&::after":{transform:"translate(-50%, -50%)",opacity:1}},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},background:f,borderColor:d,"&::after":{backgroundColor:b}},[`${t}-disabled + span`]:{color:p,cursor:"not-allowed"},[`span${t} + *`]:{paddingInlineStart:y,paddingInlineEnd:y}}}},nH=e=>{const{buttonColor:t,controlHeight:n,componentCls:r,lineWidth:o,lineType:s,colorBorder:i,motionDurationMid:l,buttonPaddingInline:c,fontSize:u,buttonBg:d,fontSizeLG:m,controlHeightLG:f,controlHeightSM:p,paddingXS:y,borderRadius:b,borderRadiusSM:x,borderRadiusLG:v,buttonCheckedBg:g,buttonSolidCheckedColor:h,colorTextDisabled:$,colorBgContainerDisabled:C,buttonCheckedBgDisabled:N,buttonCheckedColorDisabled:S,colorPrimary:E,colorPrimaryHover:w,colorPrimaryActive:R,buttonSolidCheckedBg:P,buttonSolidCheckedHoverBg:T,buttonSolidCheckedActiveBg:M,calc:z}=e;return{[`${r}-button-wrapper`]:{position:"relative",display:"inline-block",height:n,margin:0,paddingInline:c,paddingBlock:0,color:t,fontSize:u,lineHeight:G(z(n).sub(z(o).mul(2)).equal()),background:d,border:`${G(o)} ${s} ${i}`,borderBlockStartWidth:z(o).add(.02).equal(),borderInlineEndWidth:o,cursor:"pointer",transition:["color","background-color","box-shadow"].map(B=>`${B} ${l}`).join(","),a:{color:t},[`> ${r}-button`]:{position:"absolute",insetBlockStart:0,insetInlineStart:0,zIndex:-1,width:"100%",height:"100%"},"&:not(:last-child)":{marginInlineEnd:z(o).mul(-1).equal()},"&:first-child":{borderInlineStart:`${G(o)} ${s} ${i}`,borderStartStartRadius:b,borderEndStartRadius:b},"&:last-child":{borderStartEndRadius:b,borderEndEndRadius:b},"&:first-child:last-child":{borderRadius:b},[`${r}-group-large &`]:{height:f,fontSize:m,lineHeight:G(z(f).sub(z(o).mul(2)).equal()),"&:first-child":{borderStartStartRadius:v,borderEndStartRadius:v},"&:last-child":{borderStartEndRadius:v,borderEndEndRadius:v}},[`${r}-group-small &`]:{height:p,paddingInline:z(y).sub(o).equal(),paddingBlock:0,lineHeight:G(z(p).sub(z(o).mul(2)).equal()),"&:first-child":{borderStartStartRadius:x,borderEndStartRadius:x},"&:last-child":{borderStartEndRadius:x,borderEndEndRadius:x}},[`${r}-group-vertical > &`]:{marginInlineEnd:0,borderRadius:0,"&:not(:last-child)":{marginBlockEnd:z(o).mul(-1).equal()},"&:first-child":{borderStartStartRadius:b,borderStartEndRadius:b,borderEndStartRadius:0,borderEndEndRadius:0},"&:last-child":{borderStartStartRadius:0,borderStartEndRadius:0,borderEndStartRadius:b,borderEndEndRadius:b},"&:first-child:last-child":{borderRadius:b}},[`${r}-group-vertical${r}-group-large > &`]:{"&:first-child":{borderStartStartRadius:v,borderStartEndRadius:v},"&:last-child":{borderEndStartRadius:v,borderEndEndRadius:v},"&:first-child:last-child":{borderRadius:v}},[`${r}-group-vertical${r}-group-small > &`]:{"&:first-child":{borderStartStartRadius:x,borderStartEndRadius:x},"&:last-child":{borderEndStartRadius:x,borderEndEndRadius:x},"&:first-child:last-child":{borderRadius:x}},"&:hover":{position:"relative",color:E},"&:has(:focus-visible)":jr(e),[`${r}, input[type='checkbox'], input[type='radio']`]:{width:0,height:0,opacity:0,pointerEvents:"none"},[`&-checked:not(${r}-button-wrapper-disabled)`]:{zIndex:1,color:E,background:g,borderColor:E,"&::before":{backgroundColor:E},"&:first-child":{borderColor:E},"&:hover":{color:w,borderColor:w,"&::before":{backgroundColor:w}},"&:active":{color:R,borderColor:R,"&::before":{backgroundColor:R}}},[`${r}-group-solid &-checked:not(${r}-button-wrapper-disabled)`]:{color:h,background:P,borderColor:P,"&:hover":{color:h,background:T,borderColor:T},"&:active":{color:h,background:M,borderColor:M}},"&-disabled":{color:$,backgroundColor:C,borderColor:i,cursor:"not-allowed","&:first-child, &:hover":{color:$,backgroundColor:C,borderColor:i}},[`&-disabled${r}-button-wrapper-checked`]:{color:S,backgroundColor:N,borderColor:i,boxShadow:"none"},"&-block":{flex:1,textAlign:"center"}}}},rH=e=>{const{wireframe:t,padding:n,marginXS:r,lineWidth:o,fontSizeLG:s,colorText:i,colorBgContainer:l,colorTextDisabled:c,controlItemBgActiveDisabled:u,colorTextLightSolid:d,colorPrimary:m,colorPrimaryHover:f,colorPrimaryActive:p,colorWhite:y}=e,b=4,x=s,v=t?x-b*2:x-(b+o)*2;return{radioSize:x,dotSize:v,dotColorDisabled:c,buttonSolidCheckedColor:d,buttonSolidCheckedBg:m,buttonSolidCheckedHoverBg:f,buttonSolidCheckedActiveBg:p,buttonBg:l,buttonCheckedBg:l,buttonColor:i,buttonCheckedBgDisabled:u,buttonCheckedColorDisabled:c,buttonPaddingInline:n-o,wrapperMarginInlineEnd:r,radioColor:t?m:y,radioBgColor:t?l:m}},K4=Tt("Radio",e=>{const{controlOutline:t,controlOutlineWidth:n}=e,r=`0 0 0 ${G(n)} ${t}`,s=Rt(e,{radioFocusShadow:r,radioButtonFocusShadow:r});return[eH(s),tH(s),nH(s)]},rH,{unitless:{radioSize:!0,dotSize:!0}}),oH=(e,t)=>{const n=a.useContext(F4),r=a.useContext(H4),{getPrefixCls:o,direction:s,className:i,style:l,classNames:c,styles:u}=Pt("radio"),d=a.useRef(null),m=Tn(t,d),{isFormItemInput:f}=a.useContext(Hn),p=K=>{var q,Y;(q=e.onChange)==null||q.call(e,K),(Y=n==null?void 0:n.onChange)==null||Y.call(n,K)},{prefixCls:y,className:b,rootClassName:x,children:v,style:g,title:h,classNames:$,styles:C,checked:N,...S}=e,E=o("radio",y),w=((n==null?void 0:n.optionType)||r)==="button",R=w?`${E}-button`:E,P=on(E),[T,M]=K4(E,P),z={...S},B=a.useContext(cr),F="checked"in e;let L=N;n&&(z.name=n.name,z.onChange=p,L=e.value===n.value,z.disabled=z.disabled??n.disabled),(F||n)&&(z.checked=L),z.disabled=z.disabled??B;const j={...e,...z,checked:L},O=Mt(l),A=Mt(g),[k,_]=Ot([c,$],[u,O,C,A],{props:j}),D=H(`${R}-wrapper`,{[`${R}-wrapper-checked`]:L,[`${R}-wrapper-disabled`]:z.disabled,[`${R}-wrapper-rtl`]:s==="rtl",[`${R}-wrapper-in-form-item`]:f,[`${R}-wrapper-block`]:!!(n!=null&&n.block)},i,b,x,k.root,T,M,P),[V,W]=W4(z.onClick);return a.createElement(Sm,{component:"Radio",disabled:z.disabled},a.createElement("label",{className:D,style:_.root,onMouseEnter:e.onMouseEnter,onMouseLeave:e.onMouseLeave,title:h,onClick:V},a.createElement(V4,{...z,className:H(k.icon,{[$m]:!w}),style:_.icon,type:"radio",prefixCls:R,ref:m,onClick:W}),$n(v)?a.createElement("span",{className:H(`${R}-label`,k.label),style:_.label},v):null))},$f=a.forwardRef(oH),sH=a.forwardRef((e,t)=>{const{getPrefixCls:n,direction:r}=a.useContext(ct),{name:o}=a.useContext(Hn),s=jo(bv(o)),{prefixCls:i,className:l,rootClassName:c,options:u,buttonStyle:d="outline",disabled:m,children:f,size:p,style:y,id:b,optionType:x,name:v=s,defaultValue:g,value:h,block:$=!1,onChange:C,onMouseEnter:N,onMouseLeave:S,onFocus:E,onBlur:w,orientation:R,vertical:P,role:T="radiogroup"}=e,[M,z]=nn(g,h),B=a.useCallback(K=>{const q=M,Y=K.target.value;"value"in e||z(Y),Y!==q&&(C==null||C(K))},[M,z,C]),F=n("radio",i),L=`${F}-group`,j=on(F),[O,A]=K4(F,j);let k=f;u&&u.length>0&&(k=u.map(K=>typeof K=="string"||Rn(K)?a.createElement($f,{key:K.toString(),prefixCls:F,disabled:m,value:K,checked:M===K},K):a.createElement($f,{key:`radio-group-value-options-${K.value}`,prefixCls:F,disabled:K.disabled||m,value:K.value,checked:M===K.value,title:K.title,style:K.style,className:K.className,id:K.id,required:K.required},K.label)));const _=Cn(p),[,D]=Gc(R,P),V=H(L,`${L}-${d}`,{[`${L}-large`]:_==="large",[`${L}-small`]:_==="small",[`${L}-rtl`]:r==="rtl",[`${L}-block`]:$},l,c,O,A,j),W=a.useMemo(()=>({onChange:B,value:M,disabled:m,name:v,optionType:x,block:$}),[B,M,m,v,x,$]);return a.createElement("div",{...Nn(e,{aria:!0,data:!0}),role:T,className:H(V,{[`${F}-group-vertical`]:D}),style:y,onMouseEnter:N,onMouseLeave:S,onFocus:E,onBlur:w,id:b,ref:t},a.createElement(J7,{value:W},k))}),iH=a.memo(sH),aH=(e,t)=>{const{getPrefixCls:n}=a.useContext(ct),{prefixCls:r,...o}=e,s=n("radio",r);return a.createElement(Z7,{value:"button"},a.createElement($f,{prefixCls:s,...o,type:"radio",ref:t}))},lH=a.forwardRef(aH),ou=$f;ou.Button=lH;ou.Group=iH;ou.__ANT_RADIO=!0;function Hi(e){return Rt(e,{inputAffixPadding:e.paddingXXS})}const Vi=e=>{const{controlHeight:t,fontSize:n,lineHeight:r,lineWidth:o,controlHeightSM:s,controlHeightLG:i,fontSizeLG:l,lineHeightLG:c,paddingSM:u,controlPaddingHorizontalSM:d,controlPaddingHorizontal:m,colorFillAlter:f,colorPrimaryHover:p,colorPrimary:y,controlOutlineWidth:b,controlOutline:x,colorErrorOutline:v,colorWarningOutline:g,colorBgContainer:h,inputFontSize:$,inputFontSizeLG:C,inputFontSizeSM:N}=e,S=$||n,E=N||S,w=C||l,R=Math.round((t-S*r)/2*10)/10-o,P=Math.round((s-E*r)/2*10)/10-o,T=Math.ceil((i-w*c)/2*10)/10-o;return{paddingBlock:Math.max(R,0),paddingBlockSM:Math.max(P,0),paddingBlockLG:Math.max(T,0),paddingInline:u-o,paddingInlineSM:d-o,paddingInlineLG:m-o,addonBg:f,activeBorderColor:y,hoverBorderColor:p,activeShadow:`0 0 0 ${b}px ${x}`,errorActiveShadow:`0 0 0 ${b}px ${v}`,warningActiveShadow:`0 0 0 ${b}px ${g}`,hoverBg:h,activeBg:h,inputFontSize:S,inputFontSizeLG:w,inputFontSizeSM:E}},cH=e=>({borderColor:e.hoverBorderColor,backgroundColor:e.hoverBg}),su=e=>({color:e.colorTextDisabled,backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorderDisabled,boxShadow:"none",cursor:"not-allowed",opacity:1,"input[disabled], textarea[disabled]":{cursor:"not-allowed"},"&:hover:not([disabled])":{...cH(Rt(e,{hoverBorderColor:e.colorBorderDisabled,hoverBg:e.colorBgContainerDisabled}))}}),Vx=(e,t)=>({background:e.colorBgContainer,borderWidth:e.lineWidth,borderStyle:e.lineType,borderColor:t.borderColor,"&:hover":{borderColor:t.hoverBorderColor,backgroundColor:e.hoverBg},"&:focus, &:focus-within":{borderColor:t.activeBorderColor,boxShadow:t.activeShadow,outline:0,backgroundColor:e.activeBg}}),HC=(e,t)=>({[`&${e.componentCls}-status-${t.status}:not(${e.componentCls}-disabled)`]:{...Vx(e,t),[`${e.componentCls}-prefix, ${e.componentCls}-suffix`]:{color:t.affixColor}},[`&${e.componentCls}-status-${t.status}${e.componentCls}-disabled`]:{borderColor:t.borderColor}}),U4=(e,t)=>({"&-outlined":{...Vx(e,{borderColor:e.colorBorder,hoverBorderColor:e.hoverBorderColor,activeBorderColor:e.activeBorderColor,activeShadow:e.activeShadow}),[`&${e.componentCls}-disabled, &[disabled]`]:{...su(e)},...HC(e,{status:"error",borderColor:e.colorError,hoverBorderColor:e.colorErrorBorderHover,activeBorderColor:e.colorError,activeShadow:e.errorActiveShadow,affixColor:e.colorErrorAffix}),...HC(e,{status:"warning",borderColor:e.colorWarning,hoverBorderColor:e.colorWarningBorderHover,activeBorderColor:e.colorWarning,activeShadow:e.warningActiveShadow,affixColor:e.colorWarningAffix}),...t}}),VC=(e,t)=>({[`&${e.componentCls}-group-wrapper-status-${t.status}`]:{[`${e.componentCls}-group-addon`]:{borderColor:t.addonBorderColor,color:t.addonColor}}}),uH=e=>({"&-outlined":{[`${e.componentCls}-group`]:{"&-addon":{background:e.addonBg,border:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`},"&-addon:first-child":{borderInlineEnd:0},"&-addon:last-child":{borderInlineStart:0}},...VC(e,{status:"error",addonBorderColor:e.colorError,addonColor:e.colorErrorText}),...VC(e,{status:"warning",addonBorderColor:e.colorWarning,addonColor:e.colorWarningText}),[`&${e.componentCls}-group-wrapper-disabled`]:{[`${e.componentCls}-group-addon`]:{...su(e)}}}}),q4="&:focus-visible, &:has(input:focus-visible), &:has(textarea:focus-visible)",G4=(e,t)=>({outline:`${G(e.lineWidth)} ${e.lineType} ${t}`,outlineOffset:G(e.calc(e.lineWidth).mul(-1).equal()),transition:["outline-offset","outline"].map(n=>`${n} 0s`).join(", ")}),WC=(e,t)=>({"&, & input, & textarea":{color:t.color},[q4]:G4(e,t.color),[`${e.componentCls}-prefix, ${e.componentCls}-suffix`]:{color:t.affixColor}}),X4=(e,t)=>{const{componentCls:n}=e;return{"&-borderless":{background:"transparent",border:"none",paddingBlock:e.calc(e.paddingBlock).add(e.lineWidth).equal(),[`&${n}-sm, &${n}-affix-wrapper-sm`]:{paddingBlock:e.calc(e.paddingBlockSM).add(e.lineWidth).equal()},[`&${n}-lg, &${n}-affix-wrapper-lg`]:{paddingBlock:e.calc(e.paddingBlockLG).add(e.lineWidth).equal()},"&:focus, &:focus-within":{outline:"none"},[q4]:G4(e,e.activeBorderColor),[`&${n}-disabled, &[disabled]`]:{color:e.colorTextDisabled,cursor:"not-allowed"},[`&${n}-status-error`]:WC(e,{color:e.colorError,affixColor:e.colorErrorAffix}),[`&${n}-status-warning`]:WC(e,{color:e.colorWarning,affixColor:e.colorWarningAffix}),...t}}},Y4=(e,t)=>({background:t.bg,borderWidth:e.lineWidth,borderStyle:e.lineType,borderColor:"transparent","input&, & input, textarea&, & textarea":{color:(t==null?void 0:t.inputColor)??"unset"},"&:hover":{background:t.hoverBg},"&:focus, &:focus-within":{outline:0,borderColor:t.activeBorderColor,backgroundColor:e.activeBg}}),KC=(e,t)=>({[`&${e.componentCls}-status-${t.status}:not(${e.componentCls}-disabled)`]:{...Y4(e,t),[`${e.componentCls}-prefix, ${e.componentCls}-suffix`]:{color:t.affixColor}}}),Q4=(e,t)=>({"&-filled":{...Y4(e,{bg:e.colorFillTertiary,hoverBg:e.colorFillSecondary,activeBorderColor:e.activeBorderColor,inputColor:e.colorText}),[`&${e.componentCls}-disabled, &[disabled]`]:{...su(e)},...KC(e,{status:"error",bg:e.colorErrorBg,hoverBg:e.colorErrorBgHover,activeBorderColor:e.colorError,inputColor:e.colorErrorText,affixColor:e.colorErrorAffix}),...KC(e,{status:"warning",bg:e.colorWarningBg,hoverBg:e.colorWarningBgHover,activeBorderColor:e.colorWarning,inputColor:e.colorWarningText,affixColor:e.colorWarningAffix}),...t}}),UC=(e,t)=>({[`&${e.componentCls}-group-wrapper-status-${t.status}`]:{[`${e.componentCls}-group-addon`]:{background:t.addonBg,color:t.addonColor}}}),dH=e=>({"&-filled":{[`${e.componentCls}-group-addon`]:{background:e.colorFillTertiary,"&:last-child":{position:"static"}},...UC(e,{status:"error",addonBg:e.colorErrorBg,addonColor:e.colorErrorText}),...UC(e,{status:"warning",addonBg:e.colorWarningBg,addonColor:e.colorWarningText}),[`&${e.componentCls}-group-wrapper-disabled`]:{[`${e.componentCls}-group`]:{"&-addon":{background:e.colorFillTertiary,color:e.colorTextDisabled},"&-addon:first-child":{borderInlineStart:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderTop:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`},"&-addon:last-child":{borderInlineEnd:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderTop:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`}}}}}),J4=(e,t)=>({background:e.colorBgContainer,borderWidth:`${G(e.lineWidth)} 0`,borderStyle:`${e.lineType} none`,borderColor:`transparent transparent ${t.borderColor} transparent`,borderRadius:0,"&:hover":{borderColor:`transparent transparent ${t.hoverBorderColor} transparent`,backgroundColor:e.hoverBg},"&:focus, &:focus-within":{borderColor:`transparent transparent ${t.activeBorderColor} transparent`,outline:0,backgroundColor:e.activeBg}}),qC=(e,t)=>({[`&${e.componentCls}-status-${t.status}:not(${e.componentCls}-disabled)`]:{...J4(e,t),[`${e.componentCls}-prefix, ${e.componentCls}-suffix`]:{color:t.affixColor}},[`&${e.componentCls}-status-${t.status}${e.componentCls}-disabled`]:{borderColor:`transparent transparent ${t.borderColor} transparent`}}),Z4=(e,t)=>({"&-underlined":{...J4(e,{borderColor:e.colorBorder,hoverBorderColor:e.hoverBorderColor,activeBorderColor:e.activeBorderColor,activeShadow:e.activeShadow}),[`&${e.componentCls}-disabled, &[disabled]`]:{color:e.colorTextDisabled,boxShadow:"none",cursor:"not-allowed","&:hover":{borderColor:`transparent transparent ${e.colorBorder} transparent`}},"input[disabled], textarea[disabled]":{cursor:"not-allowed"},...qC(e,{status:"error",borderColor:e.colorError,hoverBorderColor:e.colorErrorBorderHover,activeBorderColor:e.colorError,activeShadow:e.errorActiveShadow,affixColor:e.colorErrorAffix}),...qC(e,{status:"warning",borderColor:e.colorWarning,hoverBorderColor:e.colorWarningBorderHover,activeBorderColor:e.colorWarning,activeShadow:e.warningActiveShadow,affixColor:e.colorWarningAffix}),...t}}),eT=e=>({"&::-moz-placeholder":{opacity:1},"&::placeholder":{color:e,userSelect:"none"},"&:placeholder-shown":{textOverflow:"ellipsis"}}),Wx=e=>{const{paddingBlockLG:t,lineHeightLG:n,borderRadiusLG:r,paddingInlineLG:o}=e;return{padding:`${G(t)} ${G(o)}`,fontSize:e.inputFontSizeLG,lineHeight:n,borderRadius:r}},Kx=e=>({padding:`${G(e.paddingBlockSM)} ${G(e.paddingInlineSM)}`,fontSize:e.inputFontSizeSM,borderRadius:e.borderRadiusSM}),km=(e,t={})=>({position:"relative",display:"inline-block",width:"100%",minWidth:0,padding:`${G(e.paddingBlock)} ${G(e.paddingInline)}`,color:e.colorText,fontSize:e.inputFontSize,lineHeight:e.lineHeight,borderRadius:e.borderRadius,transition:`all ${e.motionDurationMid}`,...eT(e.colorTextPlaceholder),"&-lg":{...Wx(e),...t.largeStyle},"&-sm":{...Kx(e),...t.smallStyle},"&-rtl, &-textarea-rtl":{direction:"rtl"}}),fH=e=>{const{componentCls:t,antCls:n}=e;return{position:"relative",display:"table",width:"100%",borderCollapse:"separate",borderSpacing:0,"&[class*='col-']":{paddingInlineEnd:e.paddingXS,"&:last-child":{paddingInlineEnd:0}},[`&-lg ${t}, &-lg > ${t}-group-addon`]:{...Wx(e)},[`&-sm ${t}, &-sm > ${t}-group-addon`]:{...Kx(e)},[`&-lg ${n}-select-single`]:{height:e.controlHeightLG},[`&-sm ${n}-select-single`]:{height:e.controlHeightSM},[`> ${t}`]:{display:"table-cell","&:not(:first-child):not(:last-child)":{borderRadius:0}},[`${t}-group`]:{"&-addon, &-wrap":{display:"table-cell",width:1,whiteSpace:"nowrap",verticalAlign:"middle","&:not(:first-child):not(:last-child)":{borderRadius:0}},"&-wrap > *":{display:"block !important"},"&-addon":{position:"relative",padding:`0 ${G(e.paddingInline)}`,color:e.colorText,fontWeight:"normal",fontSize:e.inputFontSize,textAlign:"center",borderRadius:e.borderRadius,transition:`all ${e.motionDurationSlow}`,lineHeight:1,[`${n}-select`]:{margin:`${G(e.calc(e.paddingBlock).add(1).mul(-1).equal())} ${G(e.calc(e.paddingInline).mul(-1).equal())}`,[`&${n}-select-single:not(${n}-select-customize-input):not(${n}-pagination-size-changer)`]:{backgroundColor:"inherit",border:`${G(e.lineWidth)} ${e.lineType} transparent`,boxShadow:"none"}},[`${n}-cascader-picker`]:{margin:`-9px ${G(e.calc(e.paddingInline).mul(-1).equal())}`,backgroundColor:"transparent",[`${n}-cascader-input`]:{textAlign:"start",border:0,boxShadow:"none"}}}},[t]:{width:"100%",marginBottom:0,textAlign:"inherit","&:focus":{zIndex:1,borderInlineEndWidth:1},"&:hover":{zIndex:1,borderInlineEndWidth:1}},[`> ${t}:first-child, ${t}-group-addon:first-child`]:{borderStartEndRadius:0,borderEndEndRadius:0,[`${n}-select`]:{borderStartEndRadius:0,borderEndEndRadius:0}},[`> ${t}-affix-wrapper`]:{[`&:not(:first-child) ${t}`]:{borderStartStartRadius:0,borderEndStartRadius:0},[`&:not(:last-child) ${t}`]:{borderStartEndRadius:0,borderEndEndRadius:0}},[`> ${t}:last-child, ${t}-group-addon:last-child`]:{borderStartStartRadius:0,borderEndStartRadius:0,[`${n}-select`]:{borderStartStartRadius:0,borderEndStartRadius:0}},[`${t}-affix-wrapper`]:{"&:not(:last-child)":{borderStartEndRadius:0,borderEndEndRadius:0},"&:not(:first-child)":{borderStartStartRadius:0,borderEndStartRadius:0}},[`&${t}-group-compact`]:{display:"block",...Ls(),[`${t}-group-addon, ${t}-group-wrap, > ${t}`]:{"&:not(:first-child):not(:last-child)":{borderInlineEndWidth:e.lineWidth,"&:hover, &:focus":{zIndex:1}}},"& > *":{display:"inline-flex",float:"none",verticalAlign:"top",borderRadius:0},[` + & > ${t}-affix-wrapper, + & > ${t}-number-affix-wrapper, + & > ${n}-picker-range + `]:{display:"inline-flex"},"& > *:not(:last-child)":{marginInlineEnd:e.calc(e.lineWidth).mul(-1).equal(),borderInlineEndWidth:e.lineWidth},[t]:{float:"none"},[`& > ${n}-select, + & > ${n}-select-auto-complete ${t}, + & > ${n}-cascader-picker ${t}, + & > ${t}-group-wrapper ${t}`]:{borderInlineEndWidth:e.lineWidth,borderRadius:0,"&:hover, &:focus":{zIndex:1}},[`& > ${n}-select-focused`]:{zIndex:1},[`& > ${n}-select > ${n}-select-arrow`]:{zIndex:1},[`& > *:first-child, + & > ${n}-select:first-child, + & > ${n}-select-auto-complete:first-child ${t}, + & > ${n}-cascader-picker:first-child ${t}`]:{borderStartStartRadius:e.borderRadius,borderEndStartRadius:e.borderRadius},[`& > *:last-child, + & > ${n}-select:last-child, + & > ${n}-cascader-picker:last-child ${t}, + & > ${n}-cascader-picker-focused:last-child ${t}`]:{borderInlineEndWidth:e.lineWidth,borderStartEndRadius:e.borderRadius,borderEndEndRadius:e.borderRadius},[`& > ${n}-select-auto-complete ${t}`]:{verticalAlign:"top"},[`${t}-group-wrapper + ${t}-group-wrapper`]:{marginInlineStart:e.calc(e.lineWidth).mul(-1).equal(),[`${t}-affix-wrapper`]:{}}}}},mH=e=>{const{componentCls:t,controlHeightSM:n,lineWidth:r,calc:o}=e,i=o(n).sub(o(r).mul(2)).sub(16).div(2).equal();return{[t]:{...Ft(e),...km(e),...U4(e),...Q4(e),...X4(e),...Z4(e),'&[type="color"]':{height:e.controlHeight,[`&${t}-lg`]:{height:e.controlHeightLG},[`&${t}-sm`]:{height:n,paddingTop:i,paddingBottom:i}},'&[type="search"]::-webkit-search-cancel-button, &[type="search"]::-webkit-search-decoration':{appearance:"none"}}}},pH=e=>{const{componentCls:t}=e;return{[`${t}-clear-icon`]:{margin:0,padding:0,lineHeight:0,color:e.colorTextQuaternary,fontSize:e.fontSizeIcon,verticalAlign:-1,cursor:"pointer",transition:`color ${e.motionDurationSlow}`,border:"none",outline:"none",backgroundColor:"transparent","&:hover":{color:e.colorIcon},"&:focus-visible":{color:e.colorIcon,borderRadius:e.borderRadiusSM,...jr(e)},"&:active":{color:e.colorText},"&-hidden":{visibility:"hidden"},"&-has-suffix":{margin:`0 ${G(e.inputAffixPadding)}`}}}},gH=e=>{const{componentCls:t,inputAffixPadding:n,colorTextDescription:r,motionDurationSlow:o,colorIcon:s,colorIconHover:i}=e,l=`${t}-affix-wrapper`,c=`${t}-affix-wrapper-disabled`;return{[l]:{...km(e),display:"inline-flex","&-focused, &:focus":{zIndex:1},[`> input${t}`]:{padding:0},[`> input${t}, > textarea${t}`]:{fontSize:"inherit",border:"none",borderRadius:0,outline:"none",background:"transparent",color:"inherit","&::-ms-reveal":{display:"none"},"&:focus":{boxShadow:"none !important"}},"&::before":{display:"inline-block",width:0,visibility:"hidden",content:'"\\a0"'},[t]:{"&-prefix, &-suffix":{display:"flex",flex:"none",alignItems:"center","> *:not(:last-child)":{marginInlineEnd:e.paddingXS}},"&-show-count-suffix":{color:r,direction:"ltr"},"&-show-count-has-suffix":{marginInlineEnd:e.paddingXXS},"&-prefix":{marginInlineEnd:n},"&-suffix":{marginInlineStart:n},"&-password-icon":{display:"inline-flex",color:s,cursor:"pointer",transition:`all ${o}`,"&:hover":{color:i}}},...pH(e)},[`${t}-underlined`]:{borderRadius:0},[c]:{[`${t}-password-icon`]:{color:s,cursor:"not-allowed","&:hover":{color:s}}}}},hH=e=>{const{componentCls:t,borderRadiusLG:n,borderRadiusSM:r}=e;return{[`${t}-group`]:{...Ft(e),...fH(e),"&-rtl":{direction:"rtl"},"&-wrapper":{display:"inline-block",width:"100%",textAlign:"start",verticalAlign:"top","&-rtl":{direction:"rtl"},"&-lg":{[`${t}-group-addon`]:{borderRadius:n,fontSize:e.inputFontSizeLG}},"&-sm":{[`${t}-group-addon`]:{borderRadius:r}},...uH(e),...dH(e),[`&:not(${t}-compact-first-item):not(${t}-compact-last-item)${t}-compact-item`]:{[`${t}, ${t}-group-addon`]:{borderRadius:0}},[`&:not(${t}-compact-last-item)${t}-compact-first-item`]:{[`${t}, ${t}-group-addon`]:{borderStartEndRadius:0,borderEndEndRadius:0}},[`&:not(${t}-compact-first-item)${t}-compact-last-item`]:{[`${t}, ${t}-group-addon`]:{borderStartStartRadius:0,borderEndStartRadius:0}},[`&:not(${t}-compact-last-item)${t}-compact-item`]:{[`${t}-affix-wrapper`]:{borderStartEndRadius:0,borderEndEndRadius:0}},[`&:not(${t}-compact-first-item)${t}-compact-item`]:{[`${t}-affix-wrapper`]:{borderStartStartRadius:0,borderEndStartRadius:0}}}}}},yH=e=>{const{componentCls:t}=e;return{[`${t}-out-of-range`]:{[`&, & input, & textarea, ${t}-show-count-suffix, ${t}-data-count`]:{color:e.colorError}}}},tT=Tt(["Input","Shared"],e=>{const t=Rt(e,Hi(e));return[mH(t),gH(t)]},Vi,{resetFont:!1}),nT=Tt(["Input","Component"],e=>{const t=Rt(e,Hi(e));return[hH(t),yH(t),Qc(t,{focus:!0,focusElCls:`${t.componentCls}-affix-wrapper-focused`})]},Vi,{resetFont:!1});var rT={};Object.defineProperty(rT,"__esModule",{value:!0});var vH={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"}},{tag:"path",attrs:{d:"M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"}}]},name:"plus",theme:"outlined"},bH=rT.default=vH;function $v(){return $v=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,$v({},e,{ref:t,icon:bH})),zo=a.forwardRef(xH),Ux=a.createContext(null),$H=e=>{const{activeTabOffset:t,horizontal:n,rtl:r,indicator:o={}}=e,{size:s,align:i="center"}=o,[l,c]=a.useState(),u=a.useRef(),d=J.useCallback(f=>typeof s=="function"?s(f):typeof s=="number"?s:f,[s]);function m(){Ct.cancel(u.current)}return a.useEffect(()=>{const f={};if(t)if(n){f.width=d(t.width);const p=r?"right":"left";i==="start"&&(f[p]=t[p]),i==="center"&&(f[p]=t[p]+t.width/2,f.transform=r?"translateX(50%)":"translateX(-50%)"),i==="end"&&(f[p]=t[p]+t.width,f.transform="translateX(-100%)")}else f.height=d(t.height),i==="start"&&(f.top=t.top),i==="center"&&(f.top=t.top+t.height/2,f.transform="translateY(-50%)"),i==="end"&&(f.top=t.top+t.height,f.transform="translateY(-100%)");return m(),u.current=Ct(()=>{l&&f&&Object.keys(f).every(y=>{const b=f[y],x=l[y];return typeof b=="number"&&typeof x=="number"?Math.round(b)===Math.round(x):b===x})||c(f)}),m},[JSON.stringify(t),n,r,i,d]),{style:l}},GC={width:0,height:0,left:0,top:0};function SH(e,t,n){return a.useMemo(()=>{var i,l;const r=new Map,o=t.get((i=e[0])==null?void 0:i.key)||GC,s=o.left+o.width;for(let c=0;cr.key).join("_"),t,n])}function XC(e,t){const n=a.useRef(e),[,r]=a.useState({});function o(s){const i=typeof s=="function"?s(n.current):s;i!==n.current&&t(i,n.current),n.current=i,r({})}return[n.current,o]}const CH=.1,YC=.01,Id=20,QC=.995**Id;function wH(e,t){const[n,r]=a.useState(),[o,s]=a.useState(0),[i,l]=a.useState(0),[c,u]=a.useState(),d=a.useRef();function m(v){const{screenX:g,screenY:h}=v.touches[0];r({x:g,y:h}),window.clearInterval(d.current)}function f(v){if(!n)return;const{screenX:g,screenY:h}=v.touches[0];r({x:g,y:h});const $=g-n.x,C=h-n.y;t($,C);const N=Date.now();s(N),l(N-o),u({x:$,y:C})}function p(){if(n&&(r(null),u(null),c)){const v=c.x/i,g=c.y/i,h=Math.abs(v),$=Math.abs(g);if(Math.max(h,$){if(Math.abs(C)N?($=g,y.current="x"):($=h,y.current="y"),t(-$,-$)&&v.preventDefault()}const x=a.useRef(null);x.current={onTouchStart:m,onTouchMove:f,onTouchEnd:p,onWheel:b},a.useEffect(()=>{function v(C){x.current.onTouchStart(C)}function g(C){x.current.onTouchMove(C)}function h(C){x.current.onTouchEnd(C)}function $(C){x.current.onWheel(C)}return document.addEventListener("touchmove",g,{passive:!1}),document.addEventListener("touchend",h,{passive:!0}),e.current.addEventListener("touchstart",v,{passive:!0}),e.current.addEventListener("wheel",$,{passive:!1}),()=>{document.removeEventListener("touchmove",g),document.removeEventListener("touchend",h)}},[])}function oT(e){const[t,n]=a.useState(0),r=a.useRef(0),o=a.useRef();return o.current=e,pd(()=>{var s;(s=o.current)==null||s.call(o)},[t]),()=>{r.current===t&&(r.current+=1,n(r.current))}}function EH(e){const t=a.useRef([]),[,n]=a.useState({}),r=a.useRef(typeof e=="function"?e():e),o=oT(()=>{let i=r.current;t.current.forEach(l=>{i=l(i)}),t.current=[],r.current=i,n({})});function s(i){t.current.push(i),o()}return[r.current,s]}const JC={width:0,height:0,left:0,top:0,right:0};function IH(e,t,n,r,o,s,{tabs:i,tabPosition:l,rtl:c}){let u,d,m;return["top","bottom"].includes(l)?(u="width",d=c?"right":"left",m=Math.abs(n)):(u="height",d="top",m=-n),a.useMemo(()=>{if(!i.length)return[0,0];const f=i.length;let p=f;for(let b=0;bMath.floor(m+t)){p=b-1;break}}let y=0;for(let b=f-1;b>=0;b-=1)if((e.get(i[b].key)||JC)[d]p?[0,-1]:[y,p]},[e,t,r,o,s,m,l,i.map(f=>f.key).join("_"),c])}function ZC(e){let t;return e instanceof Map?(t={},e.forEach((n,r)=>{t[r]=n})):t=e,JSON.stringify(t)}const PH="TABS_DQ";function sT(e){return String(e).replace(/"/g,PH)}function qx(e,t,n,r){return!(!n||r||e===!1||e===void 0&&(t===!1||t===null))}const iT=a.forwardRef((e,t)=>{const{prefixCls:n,editable:r,locale:o,style:s}=e;return!r||r.showAdd===!1?null:a.createElement("button",{ref:t,type:"button",className:`${n}-nav-add`,style:s,"aria-label":(o==null?void 0:o.addAriaLabel)||"Add tab",onClick:i=>{r.onEdit("add",{event:i})}},r.addIcon||"+")}),ew=a.forwardRef((e,t)=>{const{position:n,prefixCls:r,extra:o}=e;if(!o)return null;let s,i={};return typeof o=="object"&&!a.isValidElement(o)?i=o:i.right=o,n==="right"&&(s=i.right),n==="left"&&(s=i.left),s?a.createElement("div",{className:`${r}-extra-content`,ref:t},s):null});function Sv(){return Sv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,id:r,tabs:o,locale:s,mobile:i,more:l={},style:c,className:u,editable:d,tabBarGutter:m,rtl:f,removeAriaLabel:p,onTabClick:y,getPopupContainer:b,popupClassName:x,popupStyle:v,classNames:g,styles:h}=e,[$,C]=a.useState(!1),[N,S]=a.useState(null),{icon:E="More"}=l,w=`${r}-more-popup`,R=`${n}-dropdown`,P=N!==null?`${w}-${N}`:null,T=s==null?void 0:s.dropdownAriaLabel;function M(A,k){A.preventDefault(),A.stopPropagation(),d.onEdit("remove",{key:k,event:A})}const z=a.createElement(rl,{onClick:({key:A,domEvent:k})=>{y(A,k),C(!1)},prefixCls:`${R}-menu`,id:w,tabIndex:-1,role:"listbox","aria-activedescendant":P,selectedKeys:[N],"aria-label":T!==void 0?T:"expanded dropdown"},o.map(A=>{const{closable:k,disabled:_,closeIcon:D,key:V,label:W}=A,K=qx(k,D,d,_);return a.createElement(ru,{key:V,id:`${w}-${V}`,role:"option","aria-controls":r&&`${r}-panel-${V}`,disabled:_},a.createElement("span",null,W),K&&a.createElement("button",{type:"button","aria-label":p||"remove",tabIndex:0,className:H(`${R}-menu-item-remove`,g==null?void 0:g.remove),style:h==null?void 0:h.remove,onClick:q=>{q.stopPropagation(),M(q,V)}},D||d.removeIcon||"×"))}));function B(A){const k=o.filter(V=>!V.disabled);let _=k.findIndex(V=>V.key===N)||0;const D=k.length;for(let V=0;V{const A=document.getElementById(P);A!=null&&A.scrollIntoView&&A.scrollIntoView(!1)},[P,N]),a.useEffect(()=>{$||S(null)},[$]);const L={marginInlineStart:m};o.length||(L.visibility="hidden",L.order=1);const j=H(x,{[`${R}-rtl`]:f}),O=i?null:a.createElement(p4,Sv({prefixCls:R,overlay:z,visible:o.length?$:!1,onVisibleChange:C,overlayClassName:j,overlayStyle:v,mouseEnterDelay:.1,mouseLeaveDelay:.1,getPopupContainer:b},l),a.createElement("button",{type:"button",className:`${n}-nav-more`,style:L,"aria-haspopup":"listbox","aria-controls":w,id:`${r}-more`,"aria-expanded":$,onKeyDown:F},E));return a.createElement("div",{className:H(`${n}-nav-operations`,u),style:c,ref:t},O,a.createElement(iT,{prefixCls:n,locale:s,editable:d}))}),RH=a.memo(NH,(e,t)=>t.tabMoving),TH=e=>{const{prefixCls:t,id:n,active:r,focus:o,tab:{key:s,label:i,disabled:l,closeIcon:c,icon:u},closable:d,renderWrapper:m,removeAriaLabel:f,editable:p,onClick:y,onFocus:b,onBlur:x,onKeyDown:v,onMouseDown:g,onMouseUp:h,styles:$,classNames:C,tabCount:N,currentPosition:S}=e,E=`${t}-tab`,w=qx(d,c,p,l);function R(B){l||y(B)}function P(B){B.preventDefault(),B.stopPropagation(),p.onEdit("remove",{key:s,event:B})}const T=a.useMemo(()=>u&&typeof i=="string"?a.createElement("span",null,i):i,[i,u]),M=a.useRef(null);a.useEffect(()=>{o&&M.current&&M.current.focus()},[o]);const z=a.createElement("div",{key:s,"data-node-key":sT(s),className:H(E,C==null?void 0:C.item,{[`${E}-with-remove`]:w,[`${E}-active`]:r,[`${E}-disabled`]:l,[`${E}-focus`]:o}),style:$==null?void 0:$.item,onClick:R},a.createElement("div",{ref:M,role:"tab","aria-selected":r,id:n&&`${n}-tab-${s}`,className:`${E}-btn`,"aria-controls":n&&`${n}-panel-${s}`,"aria-disabled":l,tabIndex:l?null:r?0:-1,onClick:B=>{B.stopPropagation(),R(B)},onKeyDown:v,onMouseDown:g,onMouseUp:h,onFocus:b,onBlur:x},o&&a.createElement("div",{"aria-live":"polite",style:{width:0,height:0,position:"absolute",overflow:"hidden",opacity:0}},`Tab ${S} of ${N}`),u&&a.createElement("span",{className:`${E}-icon`},u),i&&T),w&&a.createElement("button",{type:"button","aria-label":f||"remove",tabIndex:r?0:-1,className:H(`${E}-remove`,C==null?void 0:C.remove),style:$==null?void 0:$.remove,onClick:B=>{B.stopPropagation(),P(B)}},c||p.removeIcon||"×"));return m?m(z):z};function Cv(){return Cv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{offsetWidth:n,offsetHeight:r,offsetTop:o,offsetLeft:s}=e,{width:i,height:l,left:c,top:u}=e.getBoundingClientRect();return Math.abs(i-n)<1?[i,l,c-t.left,u-t.top]:[n,r,s,o]},oa=e=>{const{offsetWidth:t=0,offsetHeight:n=0}=e.current||{};if(e.current){const{width:r,height:o}=e.current.getBoundingClientRect();if(Math.abs(r-t)<1)return[r,o]}return[t,n]},Wu=(e,t)=>e[t?0:1],tw=a.forwardRef((e,t)=>{const{className:n,style:r,id:o,animated:s,activeKey:i,rtl:l,extra:c,editable:u,locale:d,tabPosition:m,tabBarGutter:f,children:p,onTabClick:y,onTabScroll:b,indicator:x,classNames:v,styles:g}=e,{prefixCls:h,tabs:$}=a.useContext(Ux),C=a.useRef(null),N=a.useRef(null),S=a.useRef(null),E=a.useRef(null),w=a.useRef(null),R=a.useRef(null),P=a.useRef(null),T=m==="top"||m==="bottom",[M,z]=XC(0,($e,_e)=>{T&&b&&b({direction:$e>_e?"left":"right"})}),[B,F]=XC(0,($e,_e)=>{!T&&b&&b({direction:$e>_e?"top":"bottom"})}),[L,j]=a.useState([0,0]),[O,A]=a.useState([0,0]),[k,_]=a.useState([0,0]),[D,V]=a.useState([0,0]),[W,K]=EH(new Map),q=SH($,W,O[0]),Y=Wu(L,T),ee=Wu(O,T),ie=Wu(k,T),ae=Wu(D,T),U=Math.floor(Y)oe?oe:$e}const re=a.useRef(null),[X,se]=a.useState();function ge(){se(Date.now())}function de(){re.current&&clearTimeout(re.current)}wH(E,($e,_e)=>{function Ie(Be,te){Be(ye=>le(ye+te))}return U?(T?Ie(z,$e):Ie(F,_e),de(),ge(),!0):!1}),a.useEffect(()=>(de(),X&&(re.current=setTimeout(()=>{se(0)},100)),de),[X]);const[Se,ue]=IH(q,Q,T?M:B,ee,ie,ae,{...e,tabs:$}),be=vt(($e=i)=>{const _e=q.get($e)||{width:0,height:0,left:0,right:0,top:0};if(T){let Ie=M;l?_e.rightM+Q&&(Ie=_e.right+_e.width-Q):_e.left<-M?Ie=-_e.left:_e.left+_e.width>-M+Q&&(Ie=-(_e.left+_e.width-Q)),F(0),z(le(Ie))}else{let Ie=B;_e.top<-B?Ie=-_e.top:_e.top+_e.height>-B+Q&&(Ie=-(_e.top+_e.height-Q)),z(0),F(le(Ie))}}),[Ne,we]=a.useState(),[ze,he]=a.useState(!1),ke=$.filter($e=>!$e.disabled).map($e=>$e.key),Oe=$e=>{const _e=ke.indexOf(Ne||i),Ie=ke.length,Be=(_e+$e+Ie)%Ie,te=ke[Be];we(te)},Ce=($e,_e)=>{const Ie=ke.indexOf($e),Be=$.find(ye=>ye.key===$e);qx(Be==null?void 0:Be.closable,Be==null?void 0:Be.closeIcon,u,Be==null?void 0:Be.disabled)&&(_e.preventDefault(),_e.stopPropagation(),u.onEdit("remove",{key:$e,event:_e}),Ie===ke.length-1?Oe(-1):Oe(1))},Me=($e,_e)=>{he(!0),_e.button===1&&Ce($e,_e)},xe=$e=>{const{code:_e}=$e,Ie=l&&T,Be=ke[0],te=ke[ke.length-1];switch(_e){case"ArrowLeft":{T&&Oe(Ie?1:-1);break}case"ArrowRight":{T&&Oe(Ie?-1:1);break}case"ArrowUp":{$e.preventDefault(),T||Oe(-1);break}case"ArrowDown":{$e.preventDefault(),T||Oe(1);break}case"Home":{$e.preventDefault(),we(Be);break}case"End":{$e.preventDefault(),we(te);break}case"Enter":case"Space":{$e.preventDefault(),y(Ne??i,$e);break}case"Backspace":case"Delete":{Ce(Ne,$e);break}}},Ee={};T?Ee.marginInlineStart=f:Ee.marginTop=f;const Ve=$.map(($e,_e)=>{const{key:Ie}=$e;return a.createElement(TH,{id:o,prefixCls:h,key:Ie,tab:$e,classNames:{item:v==null?void 0:v.item,remove:v==null?void 0:v.remove},styles:{item:_e===0?g==null?void 0:g.item:{...Ee,...g==null?void 0:g.item},remove:g==null?void 0:g.remove},closable:$e.closable,editable:u,active:Ie===i,focus:Ie===Ne,renderWrapper:p,removeAriaLabel:d==null?void 0:d.removeAriaLabel,tabCount:ke.length,currentPosition:_e+1,onClick:Be=>{y(Ie,Be)},onKeyDown:xe,onFocus:()=>{ze||we(Ie),be(Ie),ge(),E.current&&(l||(E.current.scrollLeft=0),E.current.scrollTop=0)},onBlur:()=>{we(void 0)},onMouseDown:Be=>Me(Ie,Be),onMouseUp:()=>{he(!1)}})}),qe=()=>K(()=>{var Ie;const $e=new Map,_e=(Ie=w.current)==null?void 0:Ie.getBoundingClientRect();return $.forEach(({key:Be})=>{var ye;const te=(ye=w.current)==null?void 0:ye.querySelector(`[data-node-key="${sT(Be)}"]`);if(te){const[Ae,Je,St,ht]=MH(te,_e);$e.set(Be,{width:Ae,height:Je,left:St,top:ht})}}),$e});a.useEffect(()=>{qe()},[$.map($e=>$e.key).join("_")]);const me=oT(()=>{const $e=oa(C),_e=oa(N),Ie=oa(S);j([$e[0]-_e[0]-Ie[0],$e[1]-_e[1]-Ie[1]]);const Be=oa(P);_(Be);const te=oa(R);V(te);const ye=oa(w);A([ye[0]-Be[0],ye[1]-Be[1]]),qe()}),Re=$.slice(0,Se),Te=$.slice(ue+1),Ue=[...Re,...Te],Ge=q.get(i),{style:Fe}=$H({activeTabOffset:Ge,horizontal:T,indicator:x,rtl:l});a.useEffect(()=>{be()},[i,ne,oe,ZC(Ge),ZC(q),T]),a.useEffect(()=>{me()},[l]);const et=!!Ue.length,ve=`${h}-nav-wrap`;let je,ce,Pe,pe;return T?l?(ce=M>0,je=M!==oe):(je=M<0,ce=M!==ne):(Pe=B<0,pe=B!==ne),a.createElement(ir,{onResize:me},a.createElement("div",{ref:$o(t,C),role:"tablist","aria-orientation":T?"horizontal":"vertical",className:H(`${h}-nav`,n,v==null?void 0:v.header),style:{...g==null?void 0:g.header,...r},onKeyDown:()=>{ge()}},a.createElement(ew,{ref:N,position:"left",extra:c,prefixCls:h}),a.createElement(ir,{onResize:me},a.createElement("div",{className:H(ve,{[`${ve}-ping-left`]:je,[`${ve}-ping-right`]:ce,[`${ve}-ping-top`]:Pe,[`${ve}-ping-bottom`]:pe}),ref:E},a.createElement(ir,{onResize:me},a.createElement("div",{ref:w,className:`${h}-nav-list`,style:{transform:`translate(${M}px, ${B}px)`,transition:X?"none":void 0}},Ve,a.createElement(iT,{ref:P,prefixCls:h,locale:d,editable:u,style:{...Ve.length===0?void 0:Ee,visibility:et?"hidden":null}}),a.createElement("div",{className:H(`${h}-ink-bar`,v==null?void 0:v.indicator,{[`${h}-ink-bar-animated`]:s.inkBar}),style:{...Fe,...g==null?void 0:g.indicator}}))))),a.createElement(RH,Cv({},e,{removeAriaLabel:d==null?void 0:d.removeAriaLabel,ref:R,prefixCls:h,tabs:Ue,className:!et&&Z,popupStyle:g==null?void 0:g.popup,tabMoving:!!X})),a.createElement(ew,{ref:S,position:"right",extra:c,prefixCls:h})))}),OH=({renderTabBar:e,...t})=>e?e(t,tw):a.createElement(tw,t),_H=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,style:o,id:s,active:i,tabKey:l,children:c}=e,u=a.Children.count(c)>0;return a.createElement("div",{id:s&&`${s}-panel-${l}`,role:"tabpanel",tabIndex:i&&u?0:-1,"aria-labelledby":s&&`${s}-tab-${l}`,"aria-hidden":!i,style:o,className:H(n,i&&`${n}-active`,r),ref:t},c)});function Sf(){return Sf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{id:t,activeKey:n,animated:r,tabPosition:o,destroyOnHidden:s,bodyStyle:i,bodyClassName:l,contentStyle:c,contentClassName:u}=e,{prefixCls:d,tabs:m}=a.useContext(Ux),f=r.tabPane,p=`${d}-body`,y=`${d}-content`;return a.createElement("div",{className:H(`${p}-holder`)},a.createElement("div",{className:H(p,`${p}-${o}`,{[`${p}-animated`]:f},l),style:i},m.map(b=>{const{key:x,forceRender:v,style:g,className:h,destroyOnHidden:$,...C}=b,N=x===n;return a.createElement(fr,Sf({key:x,visible:N,forceRender:v,removeOnLeave:!!(s??$),leavedClassName:`${y}-hidden`},r.tabPaneMotion),({style:S,className:E},w)=>a.createElement(_H,Sf({},C,{prefixCls:y,id:t,tabKey:x,animated:f,active:N,style:{...c,...g,...S},className:H(u,h,E),ref:w})))})))};function jH(e={inkBar:!0,tabPane:!1}){let t;return e===!1?t={inkBar:!1,tabPane:!1}:e===!0?t={inkBar:!0,tabPane:!1}:t={inkBar:!0,...typeof e=="object"?e:{}},t.tabPaneMotion&&t.tabPane===void 0&&(t.tabPane=!0),!t.tabPaneMotion&&t.tabPane&&(t.tabPane=!1),t}function Yl(){return Yl=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var q;const{id:n,prefixCls:r="rc-tabs",className:o,items:s,direction:i,activeKey:l,defaultActiveKey:c,editable:u,animated:d,tabPosition:m="top",tabBarGutter:f,tabBarStyle:p,tabBarExtraContent:y,locale:b,more:x,destroyOnHidden:v,renderTabBar:g,onChange:h,onTabClick:$,onTabScroll:C,getPopupContainer:N,popupClassName:S,indicator:E,classNames:w,styles:R,...P}=e,T=a.useMemo(()=>(s||[]).filter(Y=>Y&&typeof Y=="object"&&"key"in Y),[s]),M=i==="rtl",z=jH(d),[B,F]=a.useState(!1);a.useEffect(()=>{F(DO())},[]);const[L,j]=nn(c??((q=T[0])==null?void 0:q.key),l),[O,A]=a.useState(()=>T.findIndex(Y=>Y.key===L));a.useEffect(()=>{var ee;let Y=T.findIndex(ie=>ie.key===L);Y===-1&&(Y=Math.max(0,Math.min(O,T.length-1)),j((ee=T[Y])==null?void 0:ee.key)),A(Y)},[T.map(Y=>Y.key).join("_"),L,O]);const[k,_]=nn(null,n);a.useEffect(()=>{n||(_(`rc-tabs-${nw}`),nw+=1)},[]);function D(Y,ee){$==null||$(Y,ee);const ie=Y!==L;j(Y),ie&&(h==null||h(Y))}const V={id:k,activeKey:L,animated:z,tabPosition:m,rtl:M,mobile:B},W={...V,editable:u,locale:b,more:x,tabBarGutter:f,onTabClick:D,onTabScroll:C,extra:y,style:p,getPopupContainer:N,popupClassName:H(S,w==null?void 0:w.popup),indicator:E,styles:R,classNames:w},K=a.useMemo(()=>({tabs:T,prefixCls:r}),[T,r]);return a.createElement(Ux.Provider,{value:K},a.createElement("div",Yl({ref:t,id:n,className:H(r,`${r}-${m}`,{[`${r}-mobile`]:B,[`${r}-editable`]:u,[`${r}-rtl`]:M},o)},P),a.createElement(OH,Yl({},W,{renderTabBar:g})),a.createElement(zH,Yl({destroyOnHidden:v},V,{bodyStyle:R==null?void 0:R.body,bodyClassName:w==null?void 0:w.body,contentStyle:R==null?void 0:R.content,contentClassName:w==null?void 0:w.content,animated:z}))))}),LH={motionAppear:!1,motionEnter:!0,motionLeave:!0};function kH(e,t={inkBar:!0,tabPane:!1}){let n;return t===!1?n={inkBar:!1,tabPane:!1}:t===!0?n={inkBar:!0,tabPane:!0}:n={inkBar:!0,...dt(t)?t:{}},n.tabPane&&(n.tabPaneMotion={...LH,motionName:ks(e,"switch")}),n}function AH(e){return e.filter(t=>t)}function DH(e,t){if(e)return e.map(r=>({...r,destroyOnHidden:r.destroyOnHidden??r.destroyInactiveTabPane}));const n=zn(t).map(r=>{if(a.isValidElement(r)){const{key:o,props:s}=r,{tab:i,...l}=s||{};return{key:String(o),...l,label:i}}return null});return AH(n)}const FH=e=>{const{componentCls:t,motionDurationSlow:n}=e;return[{[t]:{[`${t}-switch`]:{"&-appear, &-enter":{transition:"none","&-start":{opacity:0},"&-active":{opacity:1,transition:`opacity ${n}`}},"&-leave":{position:"absolute",transition:"none",inset:0,"&-start":{opacity:1},"&-active":{opacity:0,transition:`opacity ${n}`}}}}},[No(e,"slide-up"),No(e,"slide-down")]]},HH=e=>{const{componentCls:t,tabsCardPadding:n,cardBg:r,cardGutter:o,colorBorderSecondary:s,itemSelectedColor:i}=e;return{[`${t}-card`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab`]:{margin:0,padding:n,background:r,border:`${G(e.lineWidth)} ${e.lineType} ${s}`,transition:`all ${e.motionDurationSlow} ${e.motionEaseInOut}`},[`${t}-tab-active`]:{color:i,background:e.colorBgContainer},[`${t}-tab-focus:has(${t}-tab-btn:focus-visible)`]:jr(e,-3),[`& ${t}-tab${t}-tab-focus ${t}-tab-btn:focus-visible`]:{outline:"none"},[`${t}-ink-bar`]:{visibility:"hidden"}},[`&${t}-top, &${t}-bottom`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab + ${t}-tab`]:{marginLeft:{_skip_check_:!0,value:G(o)}}}},[`&${t}-top`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab`]:{borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0`},[`${t}-tab-active`]:{borderBottomColor:e.colorBgContainer}}},[`&${t}-bottom`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab`]:{borderRadius:`0 0 ${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)}`},[`${t}-tab-active`]:{borderTopColor:e.colorBgContainer}}},[`&${t}-left, &${t}-right`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab + ${t}-tab`]:{marginTop:G(o)}}},[`&${t}-left`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab`]:{borderRadius:{_skip_check_:!0,value:`${G(e.borderRadiusLG)} 0 0 ${G(e.borderRadiusLG)}`}},[`${t}-tab-active`]:{borderRightColor:{_skip_check_:!0,value:e.colorBgContainer}}}},[`&${t}-right`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab`]:{borderRadius:{_skip_check_:!0,value:`0 ${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0`}},[`${t}-tab-active`]:{borderLeftColor:{_skip_check_:!0,value:e.colorBgContainer}}}}}}},VH=e=>{const{antCls:t,componentCls:n,itemHoverColor:r,dropdownEdgeChildVerticalPadding:o}=e;return{[`${n}-dropdown`]:{...Ft(e),position:"absolute",top:-9999,left:{_skip_check_:!0,value:-9999},zIndex:e.zIndexPopup,display:"block","&-hidden":{display:"none"},[`&${t}-slide-down-enter${t}-slide-down-enter-active${n}-dropdown-placement-bottomLeft, + &${t}-slide-down-appear${t}-slide-down-appear-active${n}-dropdown-placement-bottomLeft, + &${t}-slide-down-enter${t}-slide-down-enter-active${n}-dropdown-placement-bottom, + &${t}-slide-down-appear${t}-slide-down-appear-active${n}-dropdown-placement-bottom, + &${t}-slide-down-enter${t}-slide-down-enter-active${n}-dropdown-placement-bottomRight, + &${t}-slide-down-appear${t}-slide-down-appear-active${n}-dropdown-placement-bottomRight`]:{animationName:Em},[`&${t}-slide-up-enter${t}-slide-up-enter-active${n}-dropdown-placement-topLeft, + &${t}-slide-up-appear${t}-slide-up-appear-active${n}-dropdown-placement-topLeft, + &${t}-slide-up-enter${t}-slide-up-enter-active${n}-dropdown-placement-top, + &${t}-slide-up-appear${t}-slide-up-appear-active${n}-dropdown-placement-top, + &${t}-slide-up-enter${t}-slide-up-enter-active${n}-dropdown-placement-topRight, + &${t}-slide-up-appear${t}-slide-up-appear-active${n}-dropdown-placement-topRight`]:{animationName:Pm},[`&${t}-slide-down-leave${t}-slide-down-leave-active${n}-dropdown-placement-bottomLeft, + &${t}-slide-down-leave${t}-slide-down-leave-active${n}-dropdown-placement-bottom, + &${t}-slide-down-leave${t}-slide-down-leave-active${n}-dropdown-placement-bottomRight`]:{animationName:Im},[`&${t}-slide-up-leave${t}-slide-up-leave-active${n}-dropdown-placement-topLeft, + &${t}-slide-up-leave${t}-slide-up-leave-active${n}-dropdown-placement-top, + &${t}-slide-up-leave${t}-slide-up-leave-active${n}-dropdown-placement-topRight`]:{animationName:Nm},[`${n}-dropdown-menu`]:{maxHeight:e.tabsDropdownHeight,margin:0,padding:`${G(o)} 0`,overflowX:"hidden",overflowY:"auto",textAlign:{_skip_check_:!0,value:"left"},listStyleType:"none",backgroundColor:e.colorBgContainer,backgroundClip:"padding-box",borderRadius:e.borderRadiusLG,outline:"none",boxShadow:e.boxShadowSecondary,"&-item":{...ar,display:"flex",alignItems:"center",minWidth:e.tabsDropdownWidth,margin:0,padding:`${G(e.paddingXXS)} ${G(e.paddingSM)}`,color:e.colorText,fontWeight:"normal",fontSize:e.fontSize,lineHeight:e.lineHeight,cursor:"pointer",transition:`all ${e.motionDurationSlow}`,"> span":{flex:1,whiteSpace:"nowrap"},"&-remove":{flex:"none",marginLeft:{_skip_check_:!0,value:e.marginSM},color:e.colorIcon,fontSize:e.fontSizeSM,background:"transparent",border:0,cursor:"pointer","&:hover":{color:r}},"&:hover":{background:e.controlItemBgHover},"&-disabled":{"&, &:hover":{color:e.colorTextDisabled,background:"transparent",cursor:"not-allowed"}}}}}}},WH=e=>{const{componentCls:t,margin:n,colorBorderSecondary:r,horizontalMargin:o,verticalItemPadding:s,verticalItemMargin:i,motionDurationSlow:l,calc:c}=e;return{[`${t}-top, ${t}-bottom`]:{flexDirection:"column",[`> ${t}-nav, > div > ${t}-nav`]:{margin:o,"&::before":{position:"absolute",right:{_skip_check_:!0,value:0},left:{_skip_check_:!0,value:0},borderBottom:`${G(e.lineWidth)} ${e.lineType} ${r}`,content:"''"},[`${t}-ink-bar`]:{height:e.lineWidthBold,"&-animated":{transition:["width","left","right"].map(u=>`${u} ${l}`).join(", ")}},[`${t}-nav-wrap`]:{"&::before, &::after":{top:0,bottom:0,width:e.controlHeight},"&::before":{left:{_skip_check_:!0,value:0},boxShadow:e.boxShadowTabsOverflowLeft},"&::after":{right:{_skip_check_:!0,value:0},boxShadow:e.boxShadowTabsOverflowRight},[`&${t}-nav-wrap-ping-left::before`]:{opacity:1},[`&${t}-nav-wrap-ping-right::after`]:{opacity:1}}}},[`${t}-top`]:{[`> ${t}-nav, + > div > ${t}-nav`]:{"&::before":{bottom:0},[`${t}-ink-bar`]:{bottom:0}}},[`${t}-bottom`]:{[`> ${t}-nav, > div > ${t}-nav`]:{order:1,marginTop:n,marginBottom:0,"&::before":{top:0},[`${t}-ink-bar`]:{top:0}},[`> ${t}-body-holder, > div > ${t}-body-holder`]:{order:0}},[`${t}-left, ${t}-right`]:{[`> ${t}-nav, > div > ${t}-nav`]:{flexDirection:"column",minWidth:c(e.controlHeight).mul(1.25).equal(),[`${t}-tab`]:{padding:s,textAlign:"center"},[`${t}-tab + ${t}-tab`]:{margin:i},[`${t}-nav-wrap`]:{flexDirection:"column","&::before, &::after":{right:{_skip_check_:!0,value:0},left:{_skip_check_:!0,value:0},height:e.controlHeight},"&::before":{top:0,boxShadow:e.boxShadowTabsOverflowTop},"&::after":{bottom:0,boxShadow:e.boxShadowTabsOverflowBottom},[`&${t}-nav-wrap-ping-top::before`]:{opacity:1},[`&${t}-nav-wrap-ping-bottom::after`]:{opacity:1}},[`${t}-ink-bar`]:{width:e.lineWidthBold,"&-animated":{transition:["height","top"].map(u=>`${u} ${l}`).join(", ")}},[`${t}-nav-list, ${t}-nav-operations`]:{flex:"1 0 auto",flexDirection:"column"}}},[`${t}-left`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-ink-bar`]:{right:{_skip_check_:!0,value:0}}},[`> ${t}-body-holder, > div > ${t}-body-holder`]:{marginLeft:{_skip_check_:!0,value:G(c(e.lineWidth).mul(-1).equal())},borderLeft:{_skip_check_:!0,value:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`},[`> ${t}-body > ${t}-content`]:{paddingLeft:{_skip_check_:!0,value:e.paddingLG}}}},[`${t}-right`]:{[`> ${t}-nav, > div > ${t}-nav`]:{order:1,[`${t}-ink-bar`]:{left:{_skip_check_:!0,value:0}}},[`> ${t}-body-holder, > div > ${t}-body-holder`]:{order:0,marginRight:{_skip_check_:!0,value:c(e.lineWidth).mul(-1).equal()},borderRight:{_skip_check_:!0,value:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`},[`> ${t}-body > ${t}-content`]:{paddingRight:{_skip_check_:!0,value:e.paddingLG}}}}}},KH=e=>{const{componentCls:t,cardPaddingSM:n,cardPaddingLG:r,cardHeightSM:o,cardHeightLG:s,horizontalItemPaddingSM:i,horizontalItemPaddingLG:l}=e;return{[t]:{"&-small":{[`> ${t}-nav`]:{[`${t}-tab`]:{padding:i,fontSize:e.titleFontSizeSM}}},"&-large":{[`> ${t}-nav`]:{[`${t}-tab`]:{padding:l,fontSize:e.titleFontSizeLG,lineHeight:e.lineHeightLG}}}},[`${t}-card`]:{[`&${t}-small`]:{[`> ${t}-nav`]:{[`${t}-tab`]:{padding:n},[`${t}-nav-add`]:{minWidth:o,minHeight:o}},[`&${t}-bottom`]:{[`> ${t}-nav ${t}-tab`]:{borderRadius:`0 0 ${G(e.borderRadius)} ${G(e.borderRadius)}`}},[`&${t}-top`]:{[`> ${t}-nav ${t}-tab`]:{borderRadius:`${G(e.borderRadius)} ${G(e.borderRadius)} 0 0`}},[`&${t}-right`]:{[`> ${t}-nav ${t}-tab`]:{borderRadius:{_skip_check_:!0,value:`0 ${G(e.borderRadius)} ${G(e.borderRadius)} 0`}}},[`&${t}-left`]:{[`> ${t}-nav ${t}-tab`]:{borderRadius:{_skip_check_:!0,value:`${G(e.borderRadius)} 0 0 ${G(e.borderRadius)}`}}}},[`&${t}-large`]:{[`> ${t}-nav`]:{[`${t}-tab`]:{padding:r},[`${t}-nav-add`]:{minWidth:s,minHeight:s}}}}}},UH=e=>{const{componentCls:t,itemActiveColor:n,itemHoverColor:r,iconCls:o,tabsHorizontalItemMargin:s,horizontalItemPadding:i,itemSelectedColor:l,itemColor:c}=e,u=`${t}-tab`;return{[u]:{position:"relative",WebkitTouchCallout:"none",WebkitTapHighlightColor:"transparent",display:"inline-flex",alignItems:"center",padding:i,fontSize:e.titleFontSize,background:"transparent",border:0,outline:"none",cursor:"pointer",color:c,"&-btn, &-remove":{"&:focus:not(:focus-visible), &:active":{color:n}},"&-btn":{outline:"none",transition:`all ${e.motionDurationSlow}`,[`${u}-icon:not(:last-child)`]:{marginInlineEnd:e.marginSM}},"&-remove":{flex:"none",lineHeight:1,marginRight:{_skip_check_:!0,value:e.calc(e.marginXXS).mul(-1).equal()},marginLeft:{_skip_check_:!0,value:e.marginXS},color:e.colorIcon,fontSize:e.fontSizeSM,background:"transparent",border:"none",outline:"none",cursor:"pointer",transition:`all ${e.motionDurationSlow}`,"&:hover":{color:e.colorTextHeading},...Br(e)},"&:hover":{color:r},[`&${u}-active ${u}-btn`]:{color:l},[`&${u}-focus ${u}-btn:focus-visible`]:jr(e),[`&${u}-disabled`]:{color:e.colorTextDisabled,cursor:"not-allowed"},[`&${u}-disabled ${u}-btn, &${u}-disabled ${t}-remove`]:{"&:focus, &:active":{color:e.colorTextDisabled}},[`& ${u}-remove ${o}`]:{margin:0,verticalAlign:"middle"},[`${o}:not(:last-child)`]:{marginRight:{_skip_check_:!0,value:e.marginSM}}},[`${u} + ${u}`]:{margin:{_skip_check_:!0,value:s}}}},qH=e=>{const{componentCls:t,tabsHorizontalItemMarginRTL:n,iconCls:r,cardGutter:o,calc:s}=e;return{[`${t}-rtl`]:{direction:"rtl",[`${t}-nav`]:{[`${t}-tab`]:{margin:{_skip_check_:!0,value:n},[`${t}-tab:last-of-type`]:{marginLeft:{_skip_check_:!0,value:0}},[r]:{marginRight:{_skip_check_:!0,value:0},marginLeft:{_skip_check_:!0,value:e.marginSM}},[`${t}-tab-remove`]:{marginRight:{_skip_check_:!0,value:e.marginXS},marginLeft:{_skip_check_:!0,value:s(e.marginXXS).mul(-1).equal()},[r]:{margin:0}}}},[`&${t}-left`]:{[`> ${t}-nav`]:{order:1},[`> ${t}-body-holder`]:{order:0}},[`&${t}-right`]:{[`> ${t}-nav`]:{order:0},[`> ${t}-body-holder`]:{order:1}},[`&${t}-card${t}-top, &${t}-card${t}-bottom`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-tab + ${t}-tab`]:{marginRight:{_skip_check_:!0,value:o},marginLeft:{_skip_check_:!0,value:0}}}}},[`${t}-dropdown-rtl`]:{direction:"rtl"},[`${t}-menu-item`]:{[`${t}-dropdown-rtl`]:{textAlign:{_skip_check_:!0,value:"right"}}}}},GH=e=>{const{componentCls:t,tabsCardPadding:n,cardHeight:r,cardGutter:o,itemHoverColor:s,itemActiveColor:i,colorBorderSecondary:l}=e;return{[t]:{...Ft(e),display:"flex",[`> ${t}-nav, > div > ${t}-nav`]:{position:"relative",display:"flex",flex:"none",alignItems:"center",[`${t}-nav-wrap`]:{position:"relative",display:"flex",flex:"auto",alignSelf:"stretch",overflow:"hidden",whiteSpace:"nowrap",transform:"translate(0)","&::before, &::after":{position:"absolute",zIndex:1,opacity:0,transition:`opacity ${e.motionDurationSlow}`,content:"''",pointerEvents:"none"}},[`${t}-nav-list`]:{position:"relative",display:"flex",transition:`opacity ${e.motionDurationSlow}`},[`${t}-nav-operations`]:{display:"flex",alignSelf:"stretch"},[`${t}-nav-operations-hidden`]:{position:"absolute",visibility:"hidden",pointerEvents:"none"},[`${t}-nav-more`]:{position:"relative",padding:n,background:"transparent",border:0,color:e.colorText,"&::after":{position:"absolute",right:{_skip_check_:!0,value:0},bottom:0,left:{_skip_check_:!0,value:0},height:e.calc(e.controlHeightLG).div(8).equal(),transform:"translateY(100%)",content:"''"}},[`${t}-nav-add`]:{minWidth:r,minHeight:r,marginLeft:{_skip_check_:!0,value:o},background:"transparent",border:`${G(e.lineWidth)} ${e.lineType} ${l}`,borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0`,outline:"none",cursor:"pointer",color:e.colorText,transition:`all ${e.motionDurationSlow} ${e.motionEaseInOut}`,"&:hover":{color:s},"&:active, &:focus:not(:focus-visible)":{color:i},...Br(e,-3)}},[`${t}-extra-content`]:{flex:"none"},[`${t}-ink-bar`]:{position:"absolute",background:e.inkBarColor,pointerEvents:"none"},...UH(e),[`${t}-body`]:{position:"relative",width:"100%"},[`${t}-body-holder`]:{flex:"auto",minWidth:0,minHeight:0},[`${t}-content`]:{...Br(e),"&-hidden":{display:"none"}}},[`${t}-centered`]:{[`> ${t}-nav, > div > ${t}-nav`]:{[`${t}-nav-wrap`]:{[`&:not([class*='${t}-nav-wrap-ping']) > ${t}-nav-list`]:{margin:"auto"}}}}}},XH=e=>{const{cardHeight:t,cardHeightSM:n,cardHeightLG:r,controlHeight:o,controlHeightLG:s}=e,i=t||s,l=n||o,c=r||s+8;return{zIndexPopup:e.zIndexPopupBase+50,cardBg:e.colorFillAlter,cardHeight:i,cardHeightSM:l,cardHeightLG:c,cardPadding:`${(i-e.fontHeight)/2-e.lineWidth}px ${e.padding}px`,cardPaddingSM:`${(l-e.fontHeight)/2-e.lineWidth}px ${e.paddingXS}px`,cardPaddingLG:`${(c-e.fontHeightLG)/2-e.lineWidth}px ${e.padding}px`,titleFontSize:e.fontSize,titleFontSizeLG:e.fontSizeLG,titleFontSizeSM:e.fontSize,inkBarColor:e.colorPrimary,horizontalMargin:`0 0 ${e.margin}px 0`,horizontalItemGutter:32,horizontalItemMargin:"",horizontalItemMarginRTL:"",horizontalItemPadding:`${e.paddingSM}px 0`,horizontalItemPaddingSM:`${e.paddingXS}px 0`,horizontalItemPaddingLG:`${e.padding}px 0`,verticalItemPadding:`${e.paddingXS}px ${e.paddingLG}px`,verticalItemMargin:`${e.margin}px 0 0 0`,itemColor:e.colorText,itemSelectedColor:e.colorPrimary,itemHoverColor:e.colorPrimaryHover,itemActiveColor:e.colorPrimaryActive,cardGutter:e.marginXXS/2}},YH=Tt("Tabs",e=>{const t=Rt(e,{tabsCardPadding:e.cardPadding,dropdownEdgeChildVerticalPadding:e.paddingXXS,tabsDropdownHeight:200,tabsDropdownWidth:120,tabsHorizontalItemMargin:`0 0 0 ${G(e.horizontalItemGutter)}`,tabsHorizontalItemMarginRTL:`0 0 0 ${G(e.horizontalItemGutter)}`});return[KH(t),qH(t),WH(t),VH(t),HH(t),GH(t),FH(t)]},XH),QH=()=>null,JH=a.forwardRef((e,t)=>{var le,re,X,se;const{type:n,className:r,rootClassName:o,size:s,onEdit:i,hideAdd:l,centered:c,addIcon:u,removeIcon:d,moreIcon:m,more:f,popupClassName:p,children:y,items:b,animated:x,style:v,indicatorSize:g,indicator:h,classNames:$,styles:C,destroyInactiveTabPane:N,destroyOnHidden:S,tabPlacement:E,tabPosition:w,...R}=e,{prefixCls:P}=R,{getPrefixCls:T,direction:M,getPopupContainer:z,className:B,style:F,classNames:L,styles:j}=Pt("tabs"),{tabs:O}=a.useContext(ct),A=T("tabs",P),k=on(A),[_,D]=YH(A,k),V=a.useRef(null);a.useImperativeHandle(t,()=>({nativeElement:V.current}));let W;n==="editable-card"&&(W={onEdit:(ge,{key:de,event:Se})=>{i==null||i(ge==="add"?Se:de,ge)},removeIcon:d??(O==null?void 0:O.removeIcon)??a.createElement(Us,null),addIcon:(u??(O==null?void 0:O.addIcon))||a.createElement(zo,null),showAdd:l!==!0});const K=T(),q=Cn(s),Y=DH(b,y),ee=kH(A,x),ie={align:(h==null?void 0:h.align)??((le=O==null?void 0:O.indicator)==null?void 0:le.align),size:(h==null?void 0:h.size)??g??((re=O==null?void 0:O.indicator)==null?void 0:re.size)??(O==null?void 0:O.indicatorSize)},ae=a.useMemo(()=>{const ge=E??w??void 0,de=M==="rtl";switch(ge){case"start":return de?"right":"left";case"end":return de?"left":"right";default:return ge}},[E,w,M]),U={...e,size:q,tabPlacement:ae,items:Y},Q=Mt(F),Z=Mt(v),[ne,oe]=Ot([L,$],[j,Q,C,Z],{props:U},{popup:{_default:"root"}});return a.createElement(BH,{ref:V,direction:M,getPopupContainer:z,...R,items:Y,className:H({[`${A}-large`]:q==="large",[`${A}-small`]:q==="small",[`${A}-card`]:["card","editable-card"].includes(n),[`${A}-editable-card`]:n==="editable-card",[`${A}-centered`]:c},B,r,o,ne.root,_,D,k),classNames:{...ne,popup:H(p,_,D,k,(X=ne.popup)==null?void 0:X.root)},styles:oe,style:oe.root,editable:W,more:{icon:((se=O==null?void 0:O.more)==null?void 0:se.icon)??(O==null?void 0:O.moreIcon)??m??a.createElement(Bm,null),transitionName:`${K}-slide-up`,...f},prefixCls:A,animated:ee,indicator:ie,destroyOnHidden:S??N,tabPosition:ae})}),aT=JH;aT.TabPane=QH;const lT=({prefixCls:e,className:t,hoverable:n=!0,...r})=>{const{getPrefixCls:o}=a.useContext(ct),s=o("card",e),i=H(`${s}-grid`,t,{[`${s}-grid-hoverable`]:n});return a.createElement("div",{...r,className:i})},ZH=e=>{const{antCls:t,componentCls:n,headerHeight:r,headerPadding:o,tabsMarginBottom:s}=e;return{display:"flex",justifyContent:"center",flexDirection:"column",minHeight:r,marginBottom:-1,padding:`0 ${G(o)}`,color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.headerFontSize,background:e.headerBg,borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`,borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0`,...Ls(),"&-wrapper":{width:"100%",display:"flex",alignItems:"center"},"&-title":{display:"inline-block",flex:1,...ar,[` + > ${n}-typography, + > ${n}-typography-edit-content + `]:{insetInlineStart:0,marginTop:0,marginBottom:0}},[`${t}-tabs-top`]:{clear:"both",marginBottom:s,color:e.colorText,fontWeight:"normal",fontSize:e.fontSize,"&-bar":{borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorderSecondary}`}}}},e9=e=>{const{cardPaddingBase:t,colorBorderSecondary:n,cardShadow:r,lineWidth:o}=e;return{width:"33.33%",padding:t,border:0,borderRadius:0,boxShadow:` + ${G(o)} 0 0 0 ${n}, + 0 ${G(o)} 0 0 ${n}, + ${G(o)} ${G(o)} 0 0 ${n}, + ${G(o)} 0 0 0 ${n} inset, + 0 ${G(o)} 0 0 ${n} inset; + `,transition:`all ${e.motionDurationMid}`,"&-hoverable:hover":{position:"relative",zIndex:1,boxShadow:r}}},t9=e=>{const{componentCls:t,iconCls:n,actionsLiMargin:r,cardActionsIconSize:o,colorBorderSecondary:s,actionsBg:i}=e;return{margin:0,padding:0,listStyle:"none",background:i,borderTop:`${G(e.lineWidth)} ${e.lineType} ${s}`,display:"flex",borderRadius:`0 0 ${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)}`,...Ls(),"& > li":{margin:r,color:e.colorTextDescription,textAlign:"center","> span":{position:"relative",display:"block",minWidth:e.calc(e.cardActionsIconSize).mul(2).equal(),fontSize:e.fontSize,lineHeight:e.lineHeight,cursor:"pointer","&:hover":{color:e.colorPrimary,transition:`color ${e.motionDurationMid}`},[`a:not(${t}-btn), > ${n}`]:{display:"inline-block",width:"100%",color:e.colorIcon,lineHeight:G(e.fontHeight),transition:`color ${e.motionDurationMid}`,"&:hover":{color:e.colorPrimary}},[`> ${n}`]:{fontSize:o,lineHeight:G(e.calc(o).mul(e.lineHeight).equal())}},"&:not(:last-child)":{borderInlineEnd:`${G(e.lineWidth)} ${e.lineType} ${s}`}}}},n9=e=>({margin:`${G(e.calc(e.marginXXS).mul(-1).equal())} 0`,display:"flex",...Ls(),"&-avatar":{paddingInlineEnd:e.padding},"&-section":{overflow:"hidden",flex:1,"> div:not(:last-child)":{marginBottom:e.marginXS}},"&-title":{color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:e.fontSizeLG,...ar},"&-description":{color:e.colorTextDescription}}),r9=e=>{const{componentCls:t,colorFillAlter:n,headerPadding:r,bodyPadding:o}=e;return{[`${t}-head`]:{padding:`0 ${G(r)}`,background:n,"&-title":{fontSize:e.fontSize}},[`${t}-body`]:{padding:`${G(e.padding)} ${G(o)}`}}},o9=e=>{const{componentCls:t}=e;return{overflow:"hidden",[`${t}-body`]:{userSelect:"none"}}},s9=e=>{const{componentCls:t,cardShadow:n,cardHeadPadding:r,colorBorderSecondary:o,boxShadowTertiary:s,bodyPadding:i,extraColor:l,motionDurationMid:c}=e;return{[t]:{...Ft(e),position:"relative",background:e.colorBgContainer,borderRadius:e.borderRadiusLG,[`&:not(${t}-bordered)`]:{boxShadow:s},[`${t}-head`]:ZH(e),[`${t}-extra`]:{marginInlineStart:"auto",color:l,fontWeight:"normal",fontSize:e.fontSize},[`${t}-body`]:{padding:i,borderRadius:`0 0 ${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)}`,"&:first-child":{borderStartStartRadius:e.borderRadiusLG,borderStartEndRadius:e.borderRadiusLG},"&:not(:last-child)":{borderEndStartRadius:0,borderEndEndRadius:0}},[`${t}-grid`]:e9(e),[`${t}-cover`]:{"> *":{display:"block",width:"100%",borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0`}},[`${t}-actions`]:t9(e),[`${t}-meta`]:n9(e)},[`${t}-bordered`]:{border:`${G(e.lineWidth)} ${e.lineType} ${o}`,[`${t}-cover`]:{marginTop:-1,marginInlineStart:-1,marginInlineEnd:-1}},[`${t}-hoverable`]:{cursor:"pointer",transition:["box-shadow","border-color"].map(u=>`${u} ${c}`).join(", "),"&:hover":{borderColor:"transparent",boxShadow:n}},[`${t}-contain-grid`]:{borderRadius:`${G(e.borderRadiusLG)} ${G(e.borderRadiusLG)} 0 0 `,[`&:not(:has(> ${t}-head))`]:{borderRadius:0},[`${t}-body`]:{display:"flex",flexWrap:"wrap"},[`&:not(${t}-loading) ${t}-body`]:{marginBlockStart:e.calc(e.lineWidth).mul(-1).equal(),marginInlineStart:e.calc(e.lineWidth).mul(-1).equal(),padding:0}},[`${t}-contain-tabs`]:{[`> div${t}-head`]:{minHeight:0,[`${t}-head-title, ${t}-extra`]:{paddingTop:r}}},[`${t}-type-inner`]:r9(e),[`${t}-loading`]:o9(e),[`${t}-rtl`]:{direction:"rtl"}}},i9=e=>{const{componentCls:t,bodyPaddingSM:n,headerPaddingSM:r,headerHeightSM:o,headerFontSizeSM:s}=e;return{[`${t}-small`]:{[`> ${t}-head`]:{minHeight:o,padding:`0 ${G(r)}`,fontSize:s,[`> ${t}-head-wrapper`]:{[`> ${t}-extra`]:{fontSize:e.fontSize}}},[`> ${t}-body`]:{padding:n}},[`${t}-small${t}-contain-tabs`]:{[`> ${t}-head`]:{[`${t}-head-title, ${t}-extra`]:{paddingTop:0,display:"flex",alignItems:"center"}}}}},a9=e=>({headerBg:"transparent",headerFontSize:e.fontSizeLG,headerFontSizeSM:e.fontSize,headerHeight:e.fontSizeLG*e.lineHeightLG+e.padding*2,headerHeightSM:e.fontSize*e.lineHeight+e.paddingXS*2,actionsBg:e.colorBgContainer,actionsLiMargin:`${e.paddingSM}px 0`,tabsMarginBottom:-e.padding-e.lineWidth,extraColor:e.colorText,bodyPaddingSM:12,headerPaddingSM:12,bodyPadding:e.bodyPadding??e.paddingLG,headerPadding:e.headerPadding??e.paddingLG}),l9=Tt("Card",e=>{const t=Rt(e,{cardShadow:e.boxShadowCard,cardHeadPadding:e.padding,cardPaddingBase:e.paddingLG,cardActionsIconSize:e.fontSize});return[s9(t),i9(t)]},a9),c9=e=>{const{actionClasses:t,actions:n=[],actionStyle:r}=e;return a.createElement("ul",{className:t,style:r},n.map((o,s)=>{const i=`action-${s}`;return a.createElement("li",{style:{width:`${100/n.length}%`},key:i},a.createElement("span",null,o))}))},u9=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,rootClassName:o,style:s,extra:i,headStyle:l={},bodyStyle:c={},title:u,loading:d,bordered:m,variant:f,size:p,type:y,cover:b,actions:x,tabList:v,children:g,activeTabKey:h,defaultActiveTabKey:$,tabBarExtraContent:C,hoverable:N,tabProps:S={},classNames:E,styles:w,...R}=e,{getPrefixCls:P,direction:T,className:M,style:z,classNames:B,styles:F}=Pt("card"),[L]=tl("card",f,m),j=Cn(p),O={...e,size:j,variant:L},A=Mt(z),k=Mt(s),[_,D]=Ot([B,E],[F,A,w,k],{props:O}),V=Ne=>{var we;(we=e.onTabChange)==null||we.call(e,Ne)},W=a.useMemo(()=>zn(g),[g]),K=a.useMemo(()=>W.some(Ne=>a.isValidElement(Ne)&&Ne.type===lT),[W]),q=P("card",n),[Y,ee]=l9(q),ie=a.createElement(Ai,{loading:!0,active:!0,paragraph:{rows:4},title:!1},g),ae=h!==void 0,U={...S,[ae?"activeKey":"defaultActiveKey"]:ae?h:$,tabBarExtraContent:C};let Q;const Z=j!=="small"?"large":j,ne=v?a.createElement(aT,{size:Z,...U,className:`${q}-head-tabs`,onChange:V,items:v.map(({tab:Ne,...we})=>({label:Ne,...we}))}):null;if(u||i||ne){const Ne=H(`${q}-head`,_.header),we=H(`${q}-head-title`,_.title),ze=H(`${q}-extra`,_.extra),he={...l,...D.header};Q=a.createElement("div",{className:Ne,style:he},a.createElement("div",{className:`${q}-head-wrapper`},u&&a.createElement("div",{className:we,style:D.title},u),i&&a.createElement("div",{className:ze,style:D.extra},i)),ne)}const oe=H(`${q}-cover`,_.cover),le=b?a.createElement("div",{className:oe,style:D.cover},b):null,re=H(`${q}-body`,_.body),X={...c,...D.body},se=d||W.length?a.createElement("div",{className:re,style:X},d?ie:g):null,ge=H(`${q}-actions`,_.actions),de=x!=null&&x.length?a.createElement(c9,{actionClasses:ge,actionStyle:D.actions,actions:x}):null,Se=Dt(R,["onTabChange"]),ue=H(q,M,{[`${q}-loading`]:d,[`${q}-bordered`]:L!=="borderless",[`${q}-hoverable`]:N,[`${q}-contain-grid`]:K,[`${q}-contain-tabs`]:v==null?void 0:v.length,[`${q}-small`]:j==="small",[`${q}-type-${y}`]:!!y,[`${q}-rtl`]:T==="rtl"},r,o,Y,ee,_.root),be={...D.root};return a.createElement("div",{ref:t,...Se,className:ue,style:be},Q,le,se,de)}),d9=e=>{const{prefixCls:t,className:n,avatar:r,title:o,description:s,style:i,classNames:l,styles:c,...u}=e,{getPrefixCls:d,className:m,style:f,classNames:p,styles:y}=Pt("cardMeta"),x=`${d("card",t)}-meta`,v=Mt(f),g=Mt(i),[h,$]=Ot([p,l],[y,v,c,g],{props:e}),C=H(x,n,m,h.root),N={...$.root},S=H(`${x}-avatar`,h.avatar),E=H(`${x}-title`,h.title),w=H(`${x}-description`,h.description),R=H(`${x}-section`,h.section),P=r?a.createElement("div",{className:S,style:$.avatar},r):null,T=o?a.createElement("div",{className:E,style:$.title},o):null,M=s?a.createElement("div",{className:w,style:$.description},s):null,z=T||M?a.createElement("div",{className:R,style:$.section},T,M):null;return a.createElement("div",{...u,className:C,style:N},P,z)},tr=u9;tr.Grid=lT;tr.Meta=d9;function f9(e,t,n){var r=n||{},o=r.noTrailing,s=o===void 0?!1:o,i=r.noLeading,l=i===void 0?!1:i,c=r.debounceMode,u=c===void 0?void 0:c,d,m=!1,f=0;function p(){d&&clearTimeout(d)}function y(x){var v=x||{},g=v.upcomingOnly,h=g===void 0?!1:g;p(),m=!h}function b(){for(var x=arguments.length,v=new Array(x),g=0;ge?l?(f=Date.now(),s||(d=setTimeout(u?N:C,e))):C():s!==!0&&(d=setTimeout(u?N:C,u===void 0?e-$:e))}return b.cancel=y,b}function m9(e,t,n){var r={},o=r.atBegin,s=o===void 0?!1:o;return f9(e,t,{debounceMode:s!==!1})}const Gx=a.createContext(null),p9=a.createContext({}),g9=e=>{const{dropPosition:t,dropLevelOffset:n,indent:r}=e,o={pointerEvents:"none",position:"absolute",right:0,backgroundColor:"red",height:2};switch(t){case-1:o.top=0,o.left=-n*r;break;case 1:o.bottom=0,o.left=-n*r;break;case 0:o.bottom=0,o.left=r;break}return J.createElement("div",{style:o})},h9=({prefixCls:e,level:t,isStart:n,isEnd:r})=>{const o=`${e}-indent-unit`,s=[];for(let i=0;i{if(!v9(o))return fn(!o,"Tree/TreeNode can only accept TreeNode as children."),null;const{key:s}=o,{children:i,...l}=o.props,c={key:s,...l},u=t(i);return u.length&&(c.children=u),c}).filter(o=>o)}return t(e)}function dg(e,t,n){const{_title:r,key:o,children:s}=Xa(n),i=new Set(t===!0?[]:t),l=[];function c(u,d=null){return u.map((m,f)=>{const p=cT(d?d.pos:"0",f),y=iu(m[o],p);let b;for(let v=0;vf[s]:typeof s=="function"&&(d=f=>s(f)):d=(f,p)=>iu(f[l],p);function m(f,p,y,b){const x=f?f[u]:e,v=f?cT(y.pos,p):"0",g=f?[...b,f]:[];if(f){const h=d(f,v),$={node:f,index:p,pos:v,key:h,parentPos:y.node?y.pos:null,level:y.level+1,nodes:g};t($)}x&&x.forEach((h,$)=>{m(h,$,{node:f,pos:v,level:y?y.level+1:-1},g)})}m(null)}function Xx(e,{initWrapper:t,processEntity:n,onProcessFinished:r,externalGetKey:o,childrenPropName:s,fieldNames:i}={},l){const c=o||l,u={},d={};let m={posEntities:u,keyEntities:d};return t&&(m=t(m)||m),b9(e,f=>{const{node:p,index:y,pos:b,key:x,parentPos:v,level:g,nodes:h}=f,$={node:p,nodes:h,index:y,key:x,pos:b,level:g},C=iu(x,b);u[b]=$,d[C]=$,$.parent=u[v],$.parent&&($.parent.children=$.parent.children||[],$.parent.children.push($)),n&&n($,m)},{externalGetKey:c,childrenPropName:s,fieldNames:i}),r&&r(m),m}function dT(e,t,n,r){return e===!1?!1:e||!t&&!n||t&&r&&!n}function Ql(e,{expandedKeys:t,selectedKeys:n,loadedKeys:r,loadingKeys:o,checkedKeys:s,halfCheckedKeys:i,dragOverNodeKey:l,dropPosition:c,keyEntities:u}){const d=or(u,e);return{eventKey:e,expanded:t.indexOf(e)!==-1,selected:n.indexOf(e)!==-1,loaded:r.indexOf(e)!==-1,loading:o.indexOf(e)!==-1,checked:s.indexOf(e)!==-1,halfChecked:i.indexOf(e)!==-1,pos:String(d?d.pos:""),dragOver:l===e&&c===0,dragOverGapTop:l===e&&c===-1,dragOverGapBottom:l===e&&c===1}}function wn(e){const{data:t,expanded:n,selected:r,checked:o,loaded:s,loading:i,halfChecked:l,dragOver:c,dragOverGapTop:u,dragOverGapBottom:d,pos:m,active:f,eventKey:p}=e,y={...t,expanded:n,selected:r,checked:o,loaded:s,loading:i,halfChecked:l,dragOver:c,dragOverGapTop:u,dragOverGapBottom:d,pos:m,active:f,key:p};return"props"in y||Object.defineProperty(y,"props",{get(){return fn(!1,"Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`."),e}}),y}function wv(){return wv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var we,ze;const{eventKey:t,className:n,style:r,dragOver:o,dragOverGapTop:s,dragOverGapBottom:i,isLeaf:l,isStart:c,isEnd:u,expanded:d,selected:m,checked:f,halfChecked:p,loading:y,domRef:b,active:x,data:v,onMouseMove:g,selectable:h,treeId:$,...C}=e,N=jI($,t),S=J.useContext(Gx),{classNames:E,styles:w}=S||{},R=J.useContext(p9),P=J.useRef(null),[T,M]=J.useState(!1),z=!!(S.disabled||e.disabled||(we=R.nodeDisabled)!=null&&we.call(R,v)),B=J.useMemo(()=>!S.checkable||e.checkable===!1?!1:S.checkable,[S.checkable,e.checkable]),F=he=>{z||S.onNodeSelect(he,wn(e))},L=he=>{z||!B||e.disableCheckbox||S.onNodeCheck(he,wn(e),!f)},j=J.useMemo(()=>typeof h=="boolean"?h:S.selectable,[h,S.selectable]),O=he=>{S.onNodeClick(he,wn(e)),j?F(he):L(he)},A=he=>{S.onNodeDoubleClick(he,wn(e))},k=he=>{S.onNodeMouseEnter(he,wn(e))},_=he=>{S.onNodeMouseLeave(he,wn(e))},D=he=>{S.onNodeContextMenu(he,wn(e))},V=J.useMemo(()=>!!(S.draggable&&(!S.draggable.nodeDraggable||S.draggable.nodeDraggable(v))),[S.draggable,v]),W=he=>{he.stopPropagation(),M(!0),S.onNodeDragStart(he,e);try{he.dataTransfer.setData("text/plain","")}catch{}},K=he=>{he.preventDefault(),he.stopPropagation(),S.onNodeDragEnter(he,e)},q=he=>{he.preventDefault(),he.stopPropagation(),S.onNodeDragOver(he,e)},Y=he=>{he.stopPropagation(),S.onNodeDragLeave(he,e)},ee=he=>{he.stopPropagation(),M(!1),S.onNodeDragEnd(he,e)},ie=he=>{he.preventDefault(),he.stopPropagation(),M(!1),S.onNodeDrop(he,e)},ae=he=>{y||S.onNodeExpand(he,wn(e))},U=J.useMemo(()=>{const{children:he}=or(S.keyEntities,t)||{};return!!(he||[]).length},[S.keyEntities,t]),Q=J.useMemo(()=>dT(l,S.loadData,U,e.loaded),[l,S.loadData,U,e.loaded]);J.useEffect(()=>{y||typeof S.loadData=="function"&&d&&!Q&&!e.loaded&&S.onNodeLoad(wn(e))},[y,S.loadData,S.onNodeLoad,d,Q,e]);const Z=J.useMemo(()=>{var he;return(he=S.draggable)!=null&&he.icon?J.createElement("span",{className:`${S.prefixCls}-draggable-icon`},S.draggable.icon):null},[S.draggable]),ne=he=>{const ke=e.switcherIcon||S.switcherIcon;return typeof ke=="function"?ke({...e,isLeaf:he}):ke},oe=()=>{if(Q){const ke=ne(!0);return ke!==!1?J.createElement("span",{className:H(`${S.prefixCls}-switcher`,`${S.prefixCls}-switcher-noop`,E==null?void 0:E.itemSwitcher),style:w==null?void 0:w.itemSwitcher},ke):null}const he=ne(!1);return he!==!1?J.createElement("span",{onClick:ae,className:H(`${S.prefixCls}-switcher`,`${S.prefixCls}-switcher_${d?rw:ow}`,E==null?void 0:E.itemSwitcher),style:w==null?void 0:w.itemSwitcher},he):null},le=J.useMemo(()=>{if(!B)return null;const he=typeof B!="boolean"?B:null;return J.createElement("span",{className:H(`${S.prefixCls}-checkbox`,{[`${S.prefixCls}-checkbox-checked`]:f,[`${S.prefixCls}-checkbox-indeterminate`]:!f&&p,[`${S.prefixCls}-checkbox-disabled`]:z||e.disableCheckbox}),onClick:L,role:"checkbox","aria-checked":p?"mixed":f,"aria-disabled":z||e.disableCheckbox,"aria-labelledby":N},he)},[B,f,p,z,e.disableCheckbox,N]),re=J.useMemo(()=>Q?null:d?rw:ow,[Q,d]),X=J.useMemo(()=>J.createElement("span",{className:H(E==null?void 0:E.itemIcon,`${S.prefixCls}-iconEle`,`${S.prefixCls}-icon__${re||"docu"}`,{[`${S.prefixCls}-icon_loading`]:y}),style:w==null?void 0:w.itemIcon}),[S.prefixCls,re,y]),se=J.useMemo(()=>{const he=!!S.draggable;return!e.disabled&&he&&S.dragOverNodeKey===t?S.dropIndicatorRender({dropPosition:S.dropPosition,dropLevelOffset:S.dropLevelOffset,indent:S.indent,prefixCls:S.prefixCls,direction:S.direction}):null},[S.dropPosition,S.dropLevelOffset,S.indent,S.prefixCls,S.direction,S.draggable,S.dragOverNodeKey,S.dropIndicatorRender]),ge=J.useMemo(()=>{const{title:he=x9}=e,ke=`${S.prefixCls}-node-content-wrapper`;let Oe;if(S.showIcon){const Me=e.icon||S.icon;Oe=Me?J.createElement("span",{className:H(E==null?void 0:E.itemIcon,`${S.prefixCls}-iconEle`,`${S.prefixCls}-icon__customize`),style:w==null?void 0:w.itemIcon},typeof Me=="function"?Me(e):Me):X}else S.loadData&&y&&(Oe=X);let Ce;return typeof he=="function"?Ce=he(v):S.titleRender?Ce=S.titleRender(v):Ce=he,J.createElement("span",{ref:P,title:typeof he=="string"?he:"",className:H(ke,`${ke}-${re||"normal"}`,{[`${S.prefixCls}-node-selected`]:!z&&(m||T)}),onMouseEnter:k,onMouseLeave:_,onContextMenu:D,onClick:O,onDoubleClick:A},Oe,J.createElement("span",{className:H(`${S.prefixCls}-title`,E==null?void 0:E.itemTitle),style:w==null?void 0:w.itemTitle},Ce),se)},[S.prefixCls,S.showIcon,e,S.icon,X,S.titleRender,v,re,k,_,D,O,A]),de=Nn(C,{aria:!0,data:!0}),{level:Se}=or(S.keyEntities,t)||{},ue=u[u.length-1],be=!z&&V,Ne=S.draggingNodeKey===t;return J.createElement("div",wv({ref:b,role:"treeitem",id:N,"aria-expanded":Q?void 0:d,"aria-selected":j&&!z?m:void 0,"aria-checked":B&&!z?p?"mixed":f:void 0,"aria-disabled":z,className:H(n,`${S.prefixCls}-treenode`,E==null?void 0:E.item,{[`${S.prefixCls}-treenode-disabled`]:z,[`${S.prefixCls}-treenode-switcher-${d?"open":"close"}`]:!l,[`${S.prefixCls}-treenode-checkbox-checked`]:f,[`${S.prefixCls}-treenode-checkbox-indeterminate`]:p,[`${S.prefixCls}-treenode-selected`]:m,[`${S.prefixCls}-treenode-loading`]:y,[`${S.prefixCls}-treenode-active`]:x,[`${S.prefixCls}-treenode-leaf-last`]:ue,[`${S.prefixCls}-treenode-draggable`]:V,dragging:Ne,"drop-target":S.dropTargetKey===t,"drop-container":S.dropContainerKey===t,"drag-over":!z&&o,"drag-over-gap-top":!z&&s,"drag-over-gap-bottom":!z&&i,"filter-node":(ze=S.filterTreeNode)==null?void 0:ze.call(S,wn(e)),[`${S.prefixCls}-treenode-leaf`]:Q}),style:{...r,...w==null?void 0:w.item},draggable:be,onDragStart:be?W:void 0,onDragEnter:V?K:void 0,onDragOver:V?q:void 0,onDragLeave:V?Y:void 0,onDrop:V?ie:void 0,onDragEnd:V?ee:void 0,onMouseMove:g},de),J.createElement(y9,{prefixCls:S.prefixCls,level:Se,isStart:c,isEnd:u}),Z,oe(),le,ge)};zc.isTreeNode=1;function $9(e,t){const[n,r]=a.useState(!1);It(()=>{if(n)return e(),()=>{t()}},[n]),It(()=>(r(!0),()=>{r(!1)}),[])}function Jl(){return Jl=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{className:n,style:r,motion:o,motionNodes:s,motionType:i,onMotionStart:l,onMotionEnd:c,active:u,treeNodeRequiredProps:d,...m}=e,[f,p]=a.useState(!0),{prefixCls:y}=a.useContext(Gx),b=s&&i!=="hide";It(()=>{s&&b!==f&&p(b)},[s]);const x=()=>{s&&l()},v=a.useRef(!1),g=()=>{s&&!v.current&&(v.current=!0,c())};$9(x,g);const h=$=>{b===$&&g()};return s?a.createElement(fr,Jl({ref:t,visible:f},o,{motionAppear:i==="show",onVisibleChanged:h}),({className:$,style:C},N)=>a.createElement("div",{ref:N,className:H(`${y}-treenode-motion`,$),style:C},s.map(S=>{const{data:{...E},title:w,key:R,isStart:P,isEnd:T}=S;delete E.children;const M=Ql(R,d);return a.createElement(zc,Jl({},E,M,{title:w,active:u,data:S.data,key:R,isStart:P,isEnd:T}))}))):a.createElement(zc,Jl({domRef:t,className:n,style:r},m,{active:u}))});function C9(e=[],t=[]){const n=e.length,r=t.length;if(Math.abs(n-r)!==1)return{add:!1,key:null};function o(s,i){const l=new Map;s.forEach(u=>{l.set(u,!0)});const c=i.filter(u=>!l.has(u));return c.length===1?c[0]:null}return ni.key===n),o=e[r+1],s=t.findIndex(i=>i.key===n);if(o){const i=t.findIndex(l=>l.key===o.key);return t.slice(s+1,i)}return t.slice(s+1)}function Cf(){return Cf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,data:r,selectable:o,checkable:s,expandedKeys:i,selectedKeys:l,checkedKeys:c,loadedKeys:u,loadingKeys:d,halfCheckedKeys:m,keyEntities:f,disabled:p,dragging:y,dragOverNodeKey:b,dropPosition:x,motion:v,height:g,itemHeight:h,virtual:$,scrollWidth:C,focusable:N,activeItem:S,tabIndex:E,onKeyDown:w,onFocus:R,onBlur:P,onMouseDown:T,onActiveChange:M,onListChangeStart:z,onListChangeEnd:B,...F}=e,L=jo(),j=a.useRef(null),O=a.useRef(null);a.useImperativeHandle(t,()=>({scrollTo:Z=>{j.current.scrollTo(Z)},getIndentWidth:()=>O.current.offsetWidth}));const[A,k]=a.useState(i),[_,D]=a.useState(r),[V,W]=a.useState(r),[K,q]=a.useState([]),[Y,ee]=a.useState(null),ie=a.useRef(r);ie.current=r;function ae(){const Z=ie.current;D(Z),W(Z),q([]),ee(null),B()}It(()=>{k(i);const Z=C9(A,i);if(Z.key!==null)if(Z.add){const ne=_.findIndex(({key:re})=>re===Z.key),oe=aw(sw(_,r,Z.key),$,g,h),le=_.slice();le.splice(ne+1,0,iw),W(le),q(oe),ee("show")}else{const ne=r.findIndex(({key:re})=>re===Z.key),oe=aw(sw(r,_,Z.key),$,g,h),le=r.slice();le.splice(ne+1,0,iw),W(le),q(oe),ee("hide")}else _!==r&&(D(r),W(r))},[i,r]),a.useEffect(()=>{y||ae()},[y]);const U=v?V:r,Q={expandedKeys:i,selectedKeys:l,loadedKeys:u,loadingKeys:d,checkedKeys:c,halfCheckedKeys:m,dragOverNodeKey:b,dropPosition:x,keyEntities:f};return a.createElement(a.Fragment,null,a.createElement("div",{className:`${n}-treenode`,"aria-hidden":!0,style:{position:"absolute",pointerEvents:"none",visibility:"hidden",height:0,overflow:"hidden",border:0,padding:0}},a.createElement("div",{className:`${n}-indent`},a.createElement("div",{ref:O,className:`${n}-indent-unit`}))),a.createElement(Tm,Cf({},F,{data:U,itemKey:lw,height:g,fullHeight:!1,virtual:$,itemHeight:h,scrollWidth:C,prefixCls:`${n}-list`,ref:j,role:"tree",tabIndex:N!==!1&&!p?E:void 0,"aria-activedescendant":S?jI(L,S.key):void 0,onKeyDown:w,onFocus:R,onBlur:P,onMouseDown:T,onVisibleChange:Z=>{Z.every(ne=>lw(ne)!==Ti)&&ae()}}),Z=>{const{pos:ne,data:{...oe},title:le,key:re,isStart:X,isEnd:se}=Z,ge=iu(re,ne);delete oe.key,delete oe.children;const de=Ql(ge,Q);return a.createElement(S9,Cf({},oe,de,{title:le,active:!!S&&re===S.key,pos:ne,data:Z.data,isStart:X,isEnd:se,motion:v,motionNodes:re===Ti?K:null,motionType:Y,onMotionStart:z,onMotionEnd:ae,treeNodeRequiredProps:Q,treeId:L,onMouseMove:()=>{M(null)}}))}))});function wo(e,t){if(!e)return[];const n=e.slice(),r=n.indexOf(t);return r>=0&&n.splice(r,1),n}function Fo(e,t){const n=(e||[]).slice();return n.indexOf(t)===-1&&n.push(t),n}function Yx(e){return e.split("-")}function E9(e,t){const n=[],r=or(t,e);function o(s=[]){s.forEach(({key:i,children:l})=>{n.push(i),o(l)})}return o(r.children),n}function I9(e){if(e.parent){const t=Yx(e.pos);return Number(t[t.length-1])===e.parent.children.length-1}return!1}function P9(e){const t=Yx(e.pos);return Number(t[t.length-1])===0}function cw(e,t,n,r,o,s,i,l,c,u){var R;const{clientX:d,clientY:m}=e,{top:f,height:p}=e.target.getBoundingClientRect(),b=((u==="rtl"?-1:1)*(((o==null?void 0:o.x)||0)-d)-12)/r,x=c.filter(P=>{var T,M;return(M=(T=l[P])==null?void 0:T.children)==null?void 0:M.length});let v=or(l,n.eventKey);if(mz.key===v.key),T=P<=0?0:P-1,M=i[T].key;v=or(l,M)}const g=v.key,h=v,$=v.key;let C=0,N=0;if(!x.includes(g))for(let P=0;P-1.5?s({dragNode:S,dropNode:E,dropPosition:1})?C=1:w=!1:s({dragNode:S,dropNode:E,dropPosition:0})?C=0:s({dragNode:S,dropNode:E,dropPosition:1})?C=1:w=!1:s({dragNode:S,dropNode:E,dropPosition:1})?C=1:w=!1,{dropPosition:C,dropLevelOffset:N,dropTargetKey:v.key,dropTargetPos:v.pos,dragOverNodeKey:$,dropContainerKey:C===0?null:((R=v.parent)==null?void 0:R.key)||null,dropAllowed:w}}function uw(e,t){if(!e)return;const{multiple:n}=t;return n?e.slice():e.length?[e[0]]:e}function fg(e){if(!e)return null;let t;if(Array.isArray(e))t={checkedKeys:e,halfCheckedKeys:void 0};else if(typeof e=="object")t={checkedKeys:e.checked||void 0,halfCheckedKeys:e.halfChecked||void 0};else return fn(!1,"`checkedKeys` is not an array or an object"),null;return t}function Iv(e,t){const n=new Set;function r(o){if(n.has(o))return;const s=or(t,o);if(!s)return;n.add(o);const{parent:i,node:l}=s;l.disabled||i&&r(i.key)}return(e||[]).forEach(o=>{r(o)}),[...n]}function mT(e,t){const n=new Set;return e.forEach(r=>{t.has(r)||n.add(r)}),n}function N9(e){const{disabled:t,disableCheckbox:n,checkable:r}=e||{};return!!(t||n)||r===!1}function R9(e,t,n,r){const o=new Set(e),s=new Set;for(let l=0;l<=n;l+=1)(t.get(l)||new Set).forEach(u=>{const{key:d,node:m,children:f=[]}=u;o.has(d)&&!r(m)&&f.filter(p=>!r(p.node)).forEach(p=>{o.add(p.key)})});const i=new Set;for(let l=n;l>=0;l-=1)(t.get(l)||new Set).forEach(u=>{const{parent:d,node:m}=u;if(r(m)||!u.parent||i.has(u.parent.key))return;if(r(u.parent.node)){i.add(d.key);return}let f=!0,p=!1;(d.children||[]).filter(y=>!r(y.node)).forEach(({key:y})=>{const b=o.has(y);f&&!b&&(f=!1),!p&&(b||s.has(y))&&(p=!0)}),f&&o.add(d.key),p&&s.add(d.key),i.add(d.key)});return{checkedKeys:Array.from(o),halfCheckedKeys:Array.from(mT(s,o))}}function T9(e,t,n,r,o){const s=new Set(e);let i=new Set(t);for(let c=0;c<=r;c+=1)(n.get(c)||new Set).forEach(d=>{const{key:m,node:f,children:p=[]}=d;!s.has(m)&&!i.has(m)&&!o(f)&&p.filter(y=>!o(y.node)).forEach(y=>{s.delete(y.key)})});i=new Set;const l=new Set;for(let c=r;c>=0;c-=1)(n.get(c)||new Set).forEach(d=>{const{parent:m,node:f}=d;if(o(f)||!d.parent||l.has(d.parent.key))return;if(o(d.parent.node)){l.add(m.key);return}let p=!0,y=!1;(m.children||[]).filter(b=>!o(b.node)).forEach(({key:b})=>{const x=s.has(b);p&&!x&&(p=!1),!y&&(x||i.has(b))&&(y=!0)}),p||s.delete(m.key),y&&i.add(m.key),l.add(m.key)});return{checkedKeys:Array.from(s),halfCheckedKeys:Array.from(mT(i,s))}}function Ta(e,t,n,r){const o=[];let s;r?s=r:s=N9;const i=new Set(e.filter(d=>{const m=!!or(n,d);return m||o.push(d),m})),l=new Map;let c=0;Object.keys(n).forEach(d=>{const m=n[d],{level:f}=m;let p=l.get(f);p||(p=new Set,l.set(f,p)),p.add(m),c=Math.max(c,f)}),fn(!o.length,`Tree missing follow keys: ${o.slice(0,100).map(d=>`'${d}'`).join(", ")}`);let u;return t===!0?u=R9(i,l,c,s):u=T9(i,t.halfCheckedKeys,l,c,s),u}function Pv(){return Pv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{expandedKeys:o,keyEntities:s}=this.state,{onDragStart:i}=this.props,{eventKey:l}=r;this.dragNodeProps=r,this.dragStartMousePosition={x:n.clientX,y:n.clientY};const c=wo(o,l);this.setState({draggingNodeKey:l,dragChildrenKeys:E9(l,s),indent:this.listRef.current.getIndentWidth()}),this.setExpandedKeys(c),window.addEventListener("dragend",this.onWindowDragEnd),i==null||i({event:n,node:wn(r)})});fe(this,"onNodeDragEnter",(n,r)=>{const{expandedKeys:o,keyEntities:s,dragChildrenKeys:i,flattenNodes:l,indent:c}=this.state,{onDragEnter:u,onExpand:d,allowDrop:m,direction:f}=this.props,{pos:p,eventKey:y}=r;if(this.currentMouseOverDroppableNodeKey!==y&&(this.currentMouseOverDroppableNodeKey=y),!this.dragNodeProps){this.resetDragState();return}const{dropPosition:b,dropLevelOffset:x,dropTargetKey:v,dropContainerKey:g,dropTargetPos:h,dropAllowed:$,dragOverNodeKey:C}=cw(n,this.dragNodeProps,r,c,this.dragStartMousePosition,m,l,s,o,f);if(i.includes(v)||!$){this.resetDragState();return}if(this.delayedDragEnterLogic||(this.delayedDragEnterLogic={}),Object.keys(this.delayedDragEnterLogic).forEach(N=>{clearTimeout(this.delayedDragEnterLogic[N])}),this.dragNodeProps.eventKey!==r.eventKey&&(n.persist(),this.delayedDragEnterLogic[p]=window.setTimeout(()=>{if(this.state.draggingNodeKey===null)return;let N=[...o];const S=or(s,r.eventKey);S&&(S.children||[]).length&&(N=Fo(o,r.eventKey)),this.props.hasOwnProperty("expandedKeys")||this.setExpandedKeys(N),d==null||d(N,{node:wn(r),expanded:!0,nativeEvent:n.nativeEvent})},800)),this.dragNodeProps.eventKey===v&&x===0){this.resetDragState();return}this.setState({dragOverNodeKey:C,dropPosition:b,dropLevelOffset:x,dropTargetKey:v,dropContainerKey:g,dropTargetPos:h,dropAllowed:$}),u==null||u({event:n,node:wn(r),expandedKeys:o})});fe(this,"onNodeDragOver",(n,r)=>{const{dragChildrenKeys:o,flattenNodes:s,keyEntities:i,expandedKeys:l,indent:c}=this.state,{onDragOver:u,allowDrop:d,direction:m}=this.props;if(!this.dragNodeProps)return;const{dropPosition:f,dropLevelOffset:p,dropTargetKey:y,dropContainerKey:b,dropTargetPos:x,dropAllowed:v,dragOverNodeKey:g}=cw(n,this.dragNodeProps,r,c,this.dragStartMousePosition,d,s,i,l,m);o.includes(y)||!v||(this.dragNodeProps.eventKey===y&&p===0?this.state.dropPosition===null&&this.state.dropLevelOffset===null&&this.state.dropTargetKey===null&&this.state.dropContainerKey===null&&this.state.dropTargetPos===null&&this.state.dropAllowed===!1&&this.state.dragOverNodeKey===null||this.resetDragState():f===this.state.dropPosition&&p===this.state.dropLevelOffset&&y===this.state.dropTargetKey&&b===this.state.dropContainerKey&&x===this.state.dropTargetPos&&v===this.state.dropAllowed&&g===this.state.dragOverNodeKey||this.setState({dropPosition:f,dropLevelOffset:p,dropTargetKey:y,dropContainerKey:b,dropTargetPos:x,dropAllowed:v,dragOverNodeKey:g}),u==null||u({event:n,node:wn(r)}))});fe(this,"onNodeDragLeave",(n,r)=>{this.currentMouseOverDroppableNodeKey===r.eventKey&&!n.currentTarget.contains(n.relatedTarget)&&(this.resetDragState(),this.currentMouseOverDroppableNodeKey=null);const{onDragLeave:o}=this.props;o==null||o({event:n,node:wn(r)})});fe(this,"onWindowDragEnd",n=>{this.onNodeDragEnd(n,null,!0),window.removeEventListener("dragend",this.onWindowDragEnd)});fe(this,"onNodeDragEnd",(n,r)=>{const{onDragEnd:o}=this.props;this.setState({dragOverNodeKey:null}),this.cleanDragState(),o==null||o({event:n,node:wn(r)}),this.dragNodeProps=null,window.removeEventListener("dragend",this.onWindowDragEnd)});fe(this,"onNodeDrop",(n,r,o=!1)=>{var b;const{dragChildrenKeys:s,dropPosition:i,dropTargetKey:l,dropTargetPos:c,dropAllowed:u}=this.state;if(!u)return;const{onDrop:d}=this.props;if(this.setState({dragOverNodeKey:null}),this.cleanDragState(),l===null)return;const m={...Ql(l,this.getTreeNodeRequiredProps()),active:((b=this.getActiveItem())==null?void 0:b.key)===l,data:or(this.state.keyEntities,l).node},f=s.includes(l);fn(!f,"Can not drop to dragNode's children node. This is a bug of rc-tree. Please report an issue.");const p=Yx(c),y={event:n,node:wn(m),dragNode:this.dragNodeProps?wn(this.dragNodeProps):null,dragNodesKeys:[this.dragNodeProps.eventKey].concat(s),dropToGap:i!==0,dropPosition:i+Number(p[p.length-1])};o||d==null||d(y),this.dragNodeProps=null});fe(this,"cleanDragState",()=>{const{draggingNodeKey:n}=this.state;n!==null&&this.setState({draggingNodeKey:null,dropPosition:null,dropContainerKey:null,dropTargetKey:null,dropLevelOffset:null,dropAllowed:!0,dragOverNodeKey:null}),this.dragStartMousePosition=null,this.currentMouseOverDroppableNodeKey=null});fe(this,"triggerExpandActionExpand",(n,r)=>{const{expandedKeys:o,flattenNodes:s}=this.state,{expanded:i,key:l,isLeaf:c}=r;if(c||n.shiftKey||n.metaKey||n.ctrlKey)return;const u=s.filter(m=>m.key===l)[0],d=wn({...Ql(l,this.getTreeNodeRequiredProps()),data:u.data});this.setExpandedKeys(i?wo(o,l):Fo(o,l)),this.onNodeExpand(n,d)});fe(this,"onNodeClick",(n,r)=>{const{onClick:o,expandAction:s}=this.props;s==="click"&&this.triggerExpandActionExpand(n,r),o==null||o(n,r)});fe(this,"onNodeDoubleClick",(n,r)=>{const{onDoubleClick:o,expandAction:s}=this.props;s==="doubleClick"&&this.triggerExpandActionExpand(n,r),o==null||o(n,r)});fe(this,"onNodeSelect",(n,r)=>{let{selectedKeys:o}=this.state;const{keyEntities:s,fieldNames:i}=this.state,{onSelect:l,multiple:c}=this.props,{selected:u}=r,d=r[i.key],m=!u;m?c?o=Fo(o,d):o=[d]:o=wo(o,d);const f=o.map(p=>{const y=or(s,p);return y?y.node:null}).filter(Boolean);this.setUncontrolledState({selectedKeys:o}),l==null||l(o,{event:"select",selected:m,node:r,selectedNodes:f,nativeEvent:n.nativeEvent})});fe(this,"onNodeCheck",(n,r,o)=>{const{keyEntities:s,checkedKeys:i,halfCheckedKeys:l}=this.state,{checkStrictly:c,onCheck:u}=this.props,{key:d}=r;let m;const f={event:"check",node:r,checked:o,nativeEvent:n.nativeEvent};if(c){const p=o?Fo(i,d):wo(i,d),y=wo(l,d);m={checked:p,halfChecked:y},f.checkedNodes=p.map(b=>or(s,b)).filter(Boolean).map(b=>b.node),this.setUncontrolledState({checkedKeys:p})}else{let{checkedKeys:p,halfCheckedKeys:y}=Ta([...i,d],!0,s);if(!o){const b=new Set(p);b.delete(d),{checkedKeys:p,halfCheckedKeys:y}=Ta(Array.from(b),{halfCheckedKeys:y},s)}m=p,f.checkedNodes=[],f.checkedNodesPositions=[],f.halfCheckedKeys=y,p.forEach(b=>{const x=or(s,b);if(!x)return;const{node:v,pos:g}=x;f.checkedNodes.push(v),f.checkedNodesPositions.push({node:v,pos:g})}),this.setUncontrolledState({checkedKeys:p},!1,{halfCheckedKeys:y})}u==null||u(m,f)});fe(this,"onNodeLoad",n=>{var l;const{key:r}=n,{keyEntities:o}=this.state,s=or(o,r);if((l=s==null?void 0:s.children)!=null&&l.length)return;const i=new Promise((c,u)=>{this.setState(({loadedKeys:d=[],loadingKeys:m=[]})=>{const{loadData:f,onLoad:p}=this.props;return!f||d.includes(r)||m.includes(r)?null:(f(n).then(()=>{const{loadedKeys:b}=this.state,x=Fo(b,r);p==null||p(x,{event:"load",node:n}),this.setUncontrolledState({loadedKeys:x}),this.setState(v=>({loadingKeys:wo(v.loadingKeys,r)})),c()}).catch(b=>{if(this.setState(x=>({loadingKeys:wo(x.loadingKeys,r)})),this.loadingRetryTimes[r]=(this.loadingRetryTimes[r]||0)+1,this.loadingRetryTimes[r]>=M9){const{loadedKeys:x}=this.state;fn(!1,"Retry for `loadData` many times but still failed. No more retry."),this.setUncontrolledState({loadedKeys:Fo(x,r)}),c()}u(b)}),{loadingKeys:Fo(m,r)})})});return i.catch(()=>{}),i});fe(this,"onNodeMouseEnter",(n,r)=>{const{onMouseEnter:o}=this.props;o==null||o({event:n,node:r})});fe(this,"onNodeMouseLeave",(n,r)=>{const{onMouseLeave:o}=this.props;o==null||o({event:n,node:r})});fe(this,"onNodeContextMenu",(n,r)=>{const{onRightClick:o}=this.props;o&&(n.preventDefault(),o({event:n,node:r}))});fe(this,"onMouseDown",n=>{this.focusedByMouse=!0;const{onMouseDown:r}=this.props;r==null||r(n)});fe(this,"onGlobalMouseUp",()=>{this.focusedByMouse=!1});fe(this,"onFocus",(...n)=>{var c;const{onFocus:r,disabled:o}=this.props,{activeKey:s,selectedKeys:i,flattenNodes:l}=this.state;if(!this.focusedByMouse&&!o&&s===null){const u=i.find(d=>l.some(m=>m.key===d));u!==void 0?this.onActiveChange(u):this.onActiveChange(((c=l==null?void 0:l[0])==null?void 0:c.key)||null)}r==null||r(...n)});fe(this,"onBlur",(...n)=>{const{onBlur:r}=this.props;this.onActiveChange(null),r==null||r(...n)});fe(this,"getTreeNodeRequiredProps",()=>{const{expandedKeys:n,selectedKeys:r,loadedKeys:o,loadingKeys:s,checkedKeys:i,halfCheckedKeys:l,dragOverNodeKey:c,dropPosition:u,keyEntities:d}=this.state;return{expandedKeys:n||[],selectedKeys:r||[],loadedKeys:o||[],loadingKeys:s||[],checkedKeys:i||[],halfCheckedKeys:l||[],dragOverNodeKey:c,dropPosition:u,keyEntities:d}});fe(this,"setExpandedKeys",n=>{const{treeData:r,fieldNames:o}=this.state,s=dg(r,n,o);this.setUncontrolledState({expandedKeys:n,flattenNodes:s},!0)});fe(this,"onNodeExpand",(n,r)=>{let{expandedKeys:o}=this.state;const{listChanging:s,fieldNames:i}=this.state,{onExpand:l,loadData:c}=this.props,{expanded:u}=r,d=r[i.key];if(s)return;const m=o.includes(d),f=!u;if(fn(u&&m||!u&&!m,"Expand state not sync with index check"),o=f?Fo(o,d):wo(o,d),this.setExpandedKeys(o),l==null||l(o,{node:r,expanded:f,nativeEvent:n.nativeEvent}),f&&c){const p=this.onNodeLoad(r);p&&p.then(()=>{const y=dg(this.state.treeData,o,i);this.setUncontrolledState({flattenNodes:y})}).catch(()=>{const{expandedKeys:y}=this.state,b=wo(y,d);this.setExpandedKeys(b)})}});fe(this,"onListChangeStart",()=>{this.setUncontrolledState({listChanging:!0})});fe(this,"onListChangeEnd",()=>{setTimeout(()=>{this.setUncontrolledState({listChanging:!1})})});fe(this,"onActiveChange",n=>{const{activeKey:r}=this.state,{onActiveChange:o,itemScrollOffset:s=0}=this.props;r!==n&&(this.setState({activeKey:n}),n!==null&&this.scrollTo({key:n,offset:s}),o==null||o(n))});fe(this,"getActiveItem",()=>{const{activeKey:n,flattenNodes:r}=this.state;return n===null?null:r.find(({key:o})=>o===n)||null});fe(this,"offsetActiveKey",n=>{const{flattenNodes:r,activeKey:o}=this.state;let s=r.findIndex(({key:l})=>l===o);s===-1&&n<0&&(s=r.length),s=(s+n+r.length)%r.length;const i=r[s];if(i){const{key:l}=i;this.onActiveChange(l)}else this.onActiveChange(null)});fe(this,"onKeyDown",n=>{var y,b,x;const{activeKey:r,expandedKeys:o,checkedKeys:s,flattenNodes:i,keyEntities:l}=this.state,{onKeyDown:c,checkable:u,selectable:d,disabled:m,loadData:f}=this.props;if(m)return;switch(n.key){case"ArrowUp":{this.offsetActiveKey(-1),n.preventDefault();break}case"ArrowDown":{this.offsetActiveKey(1),n.preventDefault();break}case"Home":{this.onActiveChange((y=i==null?void 0:i[0])==null?void 0:y.key),n.preventDefault();break}case"End":{this.onActiveChange((b=i==null?void 0:i[i.length-1])==null?void 0:b.key),n.preventDefault();break}}const p=this.getActiveItem();if(p&&p.data){const v=this.getTreeNodeRequiredProps(),g=wn({...Ql(r,v),data:p.data,active:!0}),h=or(l,r),$=!!((x=h==null?void 0:h.children)!=null&&x.length),C=!dT(p.data.isLeaf,f,$,g.loaded),N=u&&!g.disabled&&g.checkable!==!1&&!g.disableCheckbox,S=!u&&d&&!g.disabled&&g.selectable!==!1;switch(n.key){case"ArrowLeft":{C&&o.includes(r)?this.onNodeExpand({},g):p.parent&&this.onActiveChange(p.parent.key),n.preventDefault();break}case"ArrowRight":{C&&!o.includes(r)?this.onNodeExpand({},g):p.children&&p.children.length&&this.onActiveChange(p.children[0].key),n.preventDefault();break}case"Enter":{C?(n.preventDefault(),this.onNodeExpand({},g)):N?s.includes(r)||(n.preventDefault(),this.onNodeCheck({},g,!0)):S&&!g.selected&&(n.preventDefault(),this.onNodeSelect({},g));break}case" ":{N?(n.preventDefault(),this.onNodeCheck({},g,!s.includes(r))):S&&(n.preventDefault(),this.onNodeSelect({},g));break}}}c==null||c(n)});fe(this,"setUncontrolledState",(n,r=!1,o=null)=>{if(!this.destroyed){let s=!1,i=!0;const l={};Object.keys(n).forEach(c=>{if(this.props.hasOwnProperty(c)){i=!1;return}s=!0,l[c]=n[c]}),s&&(!r||i)&&this.setState({...l,...o})}});fe(this,"scrollTo",n=>{this.listRef.current.scrollTo(n)})}componentDidMount(){this.destroyed=!1,this.onUpdated(),window.addEventListener("mouseup",this.onGlobalMouseUp)}componentDidUpdate(){this.onUpdated()}onUpdated(){const{activeKey:n,itemScrollOffset:r=0}=this.props;n!==void 0&&n!==this.state.activeKey&&(this.setState({activeKey:n}),n!==null&&this.scrollTo({key:n,offset:r}))}componentWillUnmount(){window.removeEventListener("dragend",this.onWindowDragEnd),window.removeEventListener("mouseup",this.onGlobalMouseUp),this.destroyed=!0}static getDerivedStateFromProps(n,r){const{prevProps:o}=r,s={prevProps:n};function i(d){return!o&&n.hasOwnProperty(d)||o&&o[d]!==n[d]}let l,{fieldNames:c}=r;if(i("fieldNames")&&(c=Xa(n.fieldNames),s.fieldNames=c),i("treeData")?{treeData:l}=n:i("children")&&(fn(!1,"`children` of Tree is deprecated. Please use `treeData` instead."),l=uT(n.children)),l){s.treeData=l;const d=Xx(l,{fieldNames:c});s.keyEntities={[Ti]:fT,...d.keyEntities}}const u=s.keyEntities||r.keyEntities;if(i("expandedKeys")||o&&i("autoExpandParent"))s.expandedKeys=n.autoExpandParent||!o&&n.defaultExpandParent?Iv(n.expandedKeys,u):n.expandedKeys;else if(!o&&n.defaultExpandAll){const d={...u};delete d[Ti];const m=[];Object.keys(d).forEach(f=>{const p=d[f];p.children&&p.children.length&&m.push(p.key)}),s.expandedKeys=m}else!o&&n.defaultExpandedKeys&&(s.expandedKeys=n.autoExpandParent||n.defaultExpandParent?Iv(n.defaultExpandedKeys,u):n.defaultExpandedKeys);if(s.expandedKeys||delete s.expandedKeys,l||s.expandedKeys){const d=dg(l||r.treeData,s.expandedKeys||r.expandedKeys,c);s.flattenNodes=d}if(n.selectable&&(i("selectedKeys")?s.selectedKeys=uw(n.selectedKeys,n):!o&&n.defaultSelectedKeys&&(s.selectedKeys=uw(n.defaultSelectedKeys,n))),n.checkable){let d;if(i("checkedKeys")?d=fg(n.checkedKeys)||{}:!o&&n.defaultCheckedKeys?d=fg(n.defaultCheckedKeys)||{}:l&&(d=fg(n.checkedKeys)||{checkedKeys:r.checkedKeys,halfCheckedKeys:r.halfCheckedKeys}),d){let{checkedKeys:m=[],halfCheckedKeys:f=[]}=d;n.checkStrictly||({checkedKeys:m,halfCheckedKeys:f}=Ta(m,!0,u)),s.checkedKeys=m,s.halfCheckedKeys=f}}return i("loadedKeys")&&(s.loadedKeys=n.loadedKeys),s}resetDragState(){this.setState({dragOverNodeKey:null,dropPosition:null,dropLevelOffset:null,dropTargetKey:null,dropContainerKey:null,dropTargetPos:null,dropAllowed:!1})}render(){const{flattenNodes:n,keyEntities:r,draggingNodeKey:o,dropLevelOffset:s,dropContainerKey:i,dropTargetKey:l,dropPosition:c,dragOverNodeKey:u,indent:d}=this.state,{prefixCls:m,className:f,style:p,styles:y,classNames:b,showLine:x,focusable:v,tabIndex:g=0,selectable:h,showIcon:$,icon:C,switcherIcon:N,draggable:S,checkable:E,checkStrictly:w,disabled:R,motion:P,loadData:T,filterTreeNode:M,height:z,itemHeight:B,scrollWidth:F,virtual:L,titleRender:j,dropIndicatorRender:O,onContextMenu:A,onScroll:k,direction:_,rootClassName:D,rootStyle:V}=this.props,W=Nn(this.props,{aria:!0,data:!0});let K;S&&(typeof S=="object"?K=S:typeof S=="function"?K={nodeDraggable:S}:K={});const q={styles:y,classNames:b,prefixCls:m,selectable:h,showIcon:$,icon:C,switcherIcon:N,draggable:K,draggingNodeKey:o,checkable:E,checkStrictly:w,disabled:R,keyEntities:r,dropLevelOffset:s,dropContainerKey:i,dropTargetKey:l,dropPosition:c,dragOverNodeKey:u,indent:d,direction:_,dropIndicatorRender:O,loadData:T,filterTreeNode:M,titleRender:j,onNodeClick:this.onNodeClick,onNodeDoubleClick:this.onNodeDoubleClick,onNodeExpand:this.onNodeExpand,onNodeSelect:this.onNodeSelect,onNodeCheck:this.onNodeCheck,onNodeLoad:this.onNodeLoad,onNodeMouseEnter:this.onNodeMouseEnter,onNodeMouseLeave:this.onNodeMouseLeave,onNodeContextMenu:this.onNodeContextMenu,onNodeDragStart:this.onNodeDragStart,onNodeDragEnter:this.onNodeDragEnter,onNodeDragOver:this.onNodeDragOver,onNodeDragLeave:this.onNodeDragLeave,onNodeDragEnd:this.onNodeDragEnd,onNodeDrop:this.onNodeDrop};return a.createElement(Gx.Provider,{value:q},a.createElement("div",{className:H(m,f,D,{[`${m}-show-line`]:x}),style:V},a.createElement(w9,Pv({ref:this.listRef,prefixCls:m,style:p,data:n,disabled:R,selectable:h,checkable:!!E,motion:P,dragging:o!==null,height:z,itemHeight:B,virtual:L,focusable:v,tabIndex:g,activeItem:this.getActiveItem(),onFocus:this.onFocus,onMouseDown:this.onMouseDown,onBlur:this.onBlur,onKeyDown:this.onKeyDown,onActiveChange:this.onActiveChange,onListChangeStart:this.onListChangeStart,onListChangeEnd:this.onListChangeEnd,onContextMenu:A,onScroll:k,scrollWidth:F},this.getTreeNodeRequiredProps(),W))))}},fe(td,"defaultProps",{prefixCls:"rc-tree",showLine:!1,showIcon:!0,selectable:!0,multiple:!1,checkable:!1,disabled:!1,checkStrictly:!1,draggable:!1,defaultExpandParent:!0,autoExpandParent:!1,defaultExpandAll:!1,defaultExpandedKeys:[],defaultCheckedKeys:[],defaultSelectedKeys:[],dropIndicatorRender:g9,allowDrop:()=>!0,expandAction:!1}),fe(td,"TreeNode",zc),td);const _9=e=>{const{checkboxCls:t,checkboxSize:n,lineWidth:r}=e,o=`${t}-wrapper`,s="@media (hover: hover) and (pointer: fine)";return[{[`${t}-group`]:{...Ft(e),display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,[`> ${e.antCls}-row`]:{flex:1}},[o]:{...Ft(e),display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},[`& + ${o}`]:{marginInlineStart:0}},[t]:{...Ft(e),position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",alignSelf:"center",boxSizing:"border-box",display:"block",width:n,height:n,direction:"ltr",backgroundColor:e.colorBgContainer,border:`${G(r)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:`all ${e.motionDurationSlow}`,flex:"none",...Sd(),"&:after":{boxSizing:"border-box",position:"absolute",top:`calc(${n} / 2 - ${r})`,insetInlineStart:`calc(${n} / 4 - ${r})`,display:"table",width:e.calc(n).div(14).mul(5).equal(),height:e.calc(n).div(14).mul(8).equal(),border:`${G(e.lineWidthBold)} solid ${e.colorWhite}`,borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:`all ${e.motionDurationFast} ${e.motionEaseInBack}, opacity ${e.motionDurationFast}`,...Sd()},[`${t}-input`]:{position:"absolute",inset:`calc(-1 * (${r}))`,zIndex:1,cursor:"pointer",opacity:0,margin:0},[`&:has(${t}-input:focus-visible)`]:jr(e),"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}}},{[s]:{[` + ${o}:not(${o}-disabled), + ${t}:not(${t}-disabled) + `]:{[`&:hover ${t}`]:{borderColor:e.colorPrimary}},[`${o}:not(${o}-disabled)`]:{[`&:hover ${t}-checked:not(${t}-disabled)`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}}},{[`${t}-checked`]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:`all ${e.motionDurationMid} ${e.motionEaseOutBack} ${e.motionDurationFast}`,...Sd()},[s]:{[`&:not(${t}-disabled):hover`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}}},{[t]:{"&-indeterminate":{backgroundColor:e.colorBgContainer,borderColor:e.colorBorder,"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'},[s]:{[`&:not(${t}-disabled):hover`]:{backgroundColor:e.colorBgContainer,borderColor:e.colorPrimary}}}}},{[`${o}-disabled`]:{cursor:"not-allowed"},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled},"& + span":{color:e.colorTextDisabled},[`&${t}-indeterminate::after`]:{background:e.colorTextDisabled}}}]};function pT(e,t){const n=Rt(t,{checkboxCls:`.${e}`,checkboxSize:t.controlInteractiveSize});return _9(n)}const gT=Tt("Checkbox",(e,{prefixCls:t})=>[pT(t,e)]),hT=J.createContext(null),z9=(e,t)=>{const{prefixCls:n,children:r,indeterminate:o=!1,onMouseEnter:s,onMouseLeave:i,skipGroup:l=!1,disabled:c,rootClassName:u,className:d,style:m,classNames:f,styles:p,name:y,value:b,checked:x,defaultChecked:v,onChange:g,...h}=e,{getPrefixCls:$,direction:C,className:N,style:S,classNames:E,styles:w}=Pt("checkbox"),R=a.useContext(hT),{isFormItemInput:P}=a.useContext(Hn),T=a.useContext(cr),M=((R==null?void 0:R.disabled)||c)??T,[z,B]=nn(v,x);let F=z;const L=vt(Z=>{B(Z.target.checked),g==null||g(Z),!l&&(R!=null&&R.toggleOption)&&R.toggleOption({label:r,value:b})});R&&!l&&(F=R.value.includes(b));const j=a.useRef(null),O=$o(t,j);a.useEffect(()=>{if(!(l||!R))return R.registerValue(b),()=>{R.cancelValue(b)}},[b,l]),a.useEffect(()=>{var Z;(Z=j.current)!=null&&Z.input&&(j.current.input.indeterminate=o)},[o]);const A=$("checkbox",n),k=on(A),[_,D]=gT(A,k),V={...h},W={...e,indeterminate:o,disabled:M,checked:F},K=Mt(S),q=Mt(m),[Y,ee]=Ot([E,f],[w,K,p,q],{props:W}),ie=H(`${A}-wrapper`,{[`${A}-rtl`]:C==="rtl",[`${A}-wrapper-checked`]:F,[`${A}-wrapper-disabled`]:M,[`${A}-wrapper-in-form-item`]:P},N,d,Y.root,u,D,k,_),ae=H(Y.icon,{[`${A}-indeterminate`]:o},$m,_),[U,Q]=W4(V.onClick);return a.createElement(Sm,{component:"Checkbox",disabled:M},a.createElement("label",{className:ie,style:ee.root,onMouseEnter:s,onMouseLeave:i,onClick:U},a.createElement(V4,{...V,name:!l&&R?R.name:y,checked:F,onClick:Q,onChange:L,prefixCls:A,className:ae,style:ee.icon,disabled:M,ref:O,value:b}),$n(r)&&a.createElement("span",{className:H(`${A}-label`,Y.label),style:ee.label},r)))},yT=a.forwardRef(z9),j9=a.forwardRef((e,t)=>{const{defaultValue:n,children:r,options:o=[],prefixCls:s,className:i,rootClassName:l,style:c,onChange:u,role:d="group",...m}=e,{getPrefixCls:f,direction:p}=a.useContext(ct),[y,b]=a.useState(m.value||n||[]),[x,v]=a.useState([]);a.useEffect(()=>{"value"in m&&b(m.value||[])},[m.value]);const g=a.useMemo(()=>o.map(B=>typeof B=="string"||Rn(B)?{label:B,value:B}:B),[o]),h=B=>{v(F=>F.filter(L=>L!==B))},$=B=>{v(F=>[].concat($t(F),[B]))},C=B=>{const F=y.indexOf(B.value),L=$t(y);F===-1?L.push(B.value):L.splice(F,1),"value"in m||b(L),u==null||u(L.filter(j=>x.includes(j)).sort((j,O)=>{const A=g.findIndex(_=>_.value===j),k=g.findIndex(_=>_.value===O);return A-k}))},N=f("checkbox",s),S=`${N}-group`,E=on(N),[w,R]=gT(N,E),P=Dt(m,["value","disabled"]),T=o.length?g.map(B=>a.createElement(yT,{prefixCls:N,key:B.value.toString(),disabled:"disabled"in B?B.disabled:m.disabled,value:B.value,checked:y.includes(B.value),onChange:B.onChange,className:H(`${S}-item`,B.className),style:B.style,title:B.title,id:B.id,required:B.required},B.label)):r,M=a.useMemo(()=>({toggleOption:C,value:y,disabled:m.disabled,name:m.name,registerValue:$,cancelValue:h}),[C,y,m.disabled,m.name,$,h]),z=H(S,{[`${S}-rtl`]:p==="rtl"},i,l,R,E,w);return a.createElement("div",{className:z,style:c,role:d,...P,ref:t},a.createElement(hT.Provider,{value:M},T))}),as=yT;as.Group=j9;as.__ANT_CHECKBOX=!0;const vT=a.createContext({});function dw(e){return e==="auto"?"1 1 auto":Rn(e)?`${e} ${e} auto`:/^\d+(\.\d+)?(px|em|rem|%)$/.test(e)?`0 0 ${e}`:e}const si=a.forwardRef((e,t)=>{const{getPrefixCls:n,direction:r}=a.useContext(ct),{gutter:o,wrap:s}=a.useContext(vT),{prefixCls:i,span:l,order:c,offset:u,push:d,pull:m,className:f,children:p,flex:y,style:b,...x}=e,v=n("col",i),g=n(),[h,$]=bA(v),[C]=rn(g,"col"),N={};let S={};t4.forEach(R=>{let P={};const T=e[R];Rn(T)?P.span=T:dt(T)&&(P=T||{}),delete x[R],S={...S,[`${v}-${R}-${P.span}`]:bn(P.span),[`${v}-${R}-order-${P.order}`]:P.order||P.order===0,[`${v}-${R}-offset-${P.offset}`]:P.offset||P.offset===0,[`${v}-${R}-push-${P.push}`]:P.push||P.push===0,[`${v}-${R}-pull-${P.pull}`]:P.pull||P.pull===0,[`${v}-rtl`]:r==="rtl"},P.flex&&(S[`${v}-${R}-flex`]=!0,N[C(`${R}-flex`)]=dw(P.flex))});const E=H(v,{[`${v}-${l}`]:l!==void 0,[`${v}-order-${c}`]:c,[`${v}-offset-${u}`]:u,[`${v}-push-${d}`]:d,[`${v}-pull-${m}`]:m},f,S,h,$),w={};if(o!=null&&o[0]){const R=Rn(o[0])?`${o[0]/2}px`:`calc(${o[0]} / 2)`;w.paddingInline=R}return y&&(w.flex=dw(y),s===!1&&!w.minWidth&&(w.minWidth=0)),a.createElement("div",{...x,style:{...w,...b,...N},className:E,ref:t},p)});function B9(e,t){const n=[void 0,void 0],r=Array.isArray(e)?e:[e,void 0],o=t||{xs:!0,sm:!0,md:!0,lg:!0,xl:!0,xxl:!0,xxxl:!0};return r.forEach((s,i)=>{if(dt(s))for(let l=0;l{const[n,r]=a.useState(()=>Zo(e)?e:""),o=()=>{if(Zo(e)&&r(e),!!dt(e))for(let s=0;s{o()},[JSON.stringify(e),t]),n},Nv=a.forwardRef((e,t)=>{const{prefixCls:n,justify:r,align:o,className:s,style:i,children:l,gutter:c=0,wrap:u,...d}=e,{getPrefixCls:m,direction:f}=a.useContext(ct),p=Mm(!0,null),y=fw(o,p),b=fw(r,p),x=m("row",n),[v,g]=vA(x),h=B9(c,p),$=H(x,{[`${x}-no-wrap`]:u===!1,[`${x}-${b}`]:b,[`${x}-${y}`]:y,[`${x}-rtl`]:f==="rtl"},s,v,g),C={};if(h!=null&&h[0]){const w=Rn(h[0])?`${h[0]/-2}px`:`calc(${h[0]} / -2)`;C.marginInline=w}const[N,S]=h;C.rowGap=S;const E=a.useMemo(()=>({gutter:[N,S],wrap:u}),[N,S,u]);return a.createElement(vT.Provider,{value:E},a.createElement("div",{...d,className:$,style:{...C,...i},ref:t},l))}),L9=e=>{const{componentCls:t}=e;return{[t]:{"&-horizontal":{[`&${t}`]:{"&-sm":{marginBlock:e.marginXS},"&-md":{marginBlock:e.margin}}}}}},k9=e=>{const{componentCls:t,sizePaddingEdgeHorizontal:n,colorSplit:r,lineWidth:o,textPaddingInline:s,orientationMargin:i,verticalMarginInline:l}=e,c=`${t}-rail`;return{[t]:{...Ft(e),borderBlockStart:`${G(o)} solid ${r}`,[c]:{borderBlockStart:`${G(o)} solid ${r}`},"&-vertical":{position:"relative",top:"-0.06em",display:"inline-block",height:"0.9em",marginInline:l,marginBlock:0,verticalAlign:"middle",borderTop:0,borderInlineStart:`${G(o)} solid ${r}`},"&-horizontal":{display:"flex",clear:"both",width:"100%",minWidth:"100%",margin:`${G(e.marginLG)} 0`},[`&-horizontal${t}-with-text`]:{display:"flex",alignItems:"center",margin:`${G(e.dividerHorizontalWithTextGutterMargin)} 0`,color:e.colorTextHeading,fontWeight:500,fontSize:e.fontSizeLG,whiteSpace:"nowrap",textAlign:"center",borderBlockStart:`0 ${r}`,[`${c}-start, ${c}-end`]:{width:"50%",borderBlockStartColor:"inherit",borderBlockEnd:0,content:"''"}},[`&-horizontal${t}-with-text-start`]:{[`${c}-start`]:{width:`calc(${i} * 100%)`},[`${c}-end`]:{width:`calc(100% - ${i} * 100%)`}},[`&-horizontal${t}-with-text-end`]:{[`${c}-start`]:{width:`calc(100% - ${i} * 100%)`},[`${c}-end`]:{width:`calc(${i} * 100%)`}},[`${t}-inner-text`]:{display:"inline-block",paddingBlock:0,paddingInline:s},"&-dashed":{background:"none",borderColor:r,borderStyle:"dashed",borderWidth:`${G(o)} 0 0`,[c]:{borderBlockStart:`${G(o)} dashed ${r}`}},[`&-horizontal${t}-with-text${t}-dashed`]:{[`${c}-start, ${c}-end`]:{borderStyle:"dashed none none"}},[`&-vertical${t}-dashed`]:{borderInlineStartWidth:o,borderInlineEnd:0,borderBlockStart:0,borderBlockEnd:0},"&-dotted":{background:"none",borderColor:r,borderStyle:"dotted",borderWidth:`${G(o)} 0 0`,[c]:{borderBlockStart:`${G(o)} dotted ${r}`}},[`&-horizontal${t}-with-text${t}-dotted`]:{"&::before, &::after":{borderStyle:"dotted none none"}},[`&-vertical${t}-dotted`]:{borderInlineStartWidth:o,borderInlineEnd:0,borderBlockStart:0,borderBlockEnd:0},[`&-plain${t}-with-text`]:{color:e.colorText,fontWeight:"normal",fontSize:e.fontSize},[`&-horizontal${t}-with-text-start${t}-no-default-orientation-margin-start`]:{[`${c}-start`]:{width:0},[`${c}-end`]:{width:"100%"},[`${t}-inner-text`]:{paddingInlineStart:n}},[`&-horizontal${t}-with-text-end${t}-no-default-orientation-margin-end`]:{[`${c}-start`]:{width:"100%"},[`${c}-end`]:{width:0},[`${t}-inner-text`]:{paddingInlineEnd:n}}}}},A9=e=>({textPaddingInline:"1em",orientationMargin:.05,verticalMarginInline:e.marginXS}),D9=Tt("Divider",e=>{const t=Rt(e,{dividerHorizontalWithTextGutterMargin:e.margin,sizePaddingEdgeHorizontal:0});return[k9(t),L9(t)]},A9,{unitless:{orientationMargin:!0}}),F9=["left","right","center","start","end"],H9=e=>{const{getPrefixCls:t,direction:n,className:r,style:o,classNames:s,styles:i}=Pt("divider"),{prefixCls:l,type:c,orientation:u,vertical:d,titlePlacement:m,orientationMargin:f,className:p,rootClassName:y,children:b,dashed:x,variant:v="solid",plain:g,style:h,size:$,classNames:C,styles:N,...S}=e,E=t("divider",l),w=`${E}-rail`,[R,P]=D9(E),T=Cn($),M=!!b,z=F9.includes(u||""),B=a.useMemo(()=>{const K=m??(z?u:"center");return K==="left"?n==="rtl"?"end":"start":K==="right"?n==="rtl"?"start":"end":K},[n,u,m,z]),F=B==="start"&&f!=null,L=B==="end"&&f!=null,[j,O]=Gc(u,d,c),A={...e,orientation:j,titlePlacement:B,size:T},[k,_]=Ot([s,C],[i,N],{props:A}),D=H(E,r,R,P,`${E}-${j}`,{[`${E}-with-text`]:M,[`${E}-with-text-${B}`]:M,[`${E}-dashed`]:!!x,[`${E}-${v}`]:v!=="solid",[`${E}-plain`]:!!g,[`${E}-rtl`]:n==="rtl",[`${E}-no-default-orientation-margin-start`]:F,[`${E}-no-default-orientation-margin-end`]:L,[`${E}-md`]:T==="medium"||T==="middle",[`${E}-sm`]:T==="small",[w]:!b,[k.rail]:k.rail&&!b},p,y,k.root),V=a.useMemo(()=>Rn(f)?f:/^\d+$/.test(f)?Number(f):f,[f]),W={marginInlineStart:F?V:void 0,marginInlineEnd:L?V:void 0};return a.createElement("div",{className:D,style:{...o,..._.root,...b?{}:_.rail,...h},...S,role:"separator"},b&&!O&&a.createElement(a.Fragment,null,a.createElement("div",{className:H(w,`${w}-start`,k.rail),style:_.rail}),a.createElement("span",{className:H(`${E}-inner-text`,k.content),style:{...W,..._.content}},b),a.createElement("div",{className:H(w,`${w}-end`,k.rail),style:_.rail})))},mw=(e,t)=>{if(!e)return null;const n={left:e.offsetLeft,right:e.parentElement.clientWidth-e.clientWidth-e.offsetLeft,width:e.clientWidth,top:e.offsetTop,bottom:e.parentElement.clientHeight-e.clientHeight-e.offsetTop,height:e.clientHeight};return t?{left:0,right:0,width:0,top:n.top,bottom:n.bottom,height:n.height}:{left:n.left,right:n.right,width:n.width,top:0,bottom:0,height:0}},Co=e=>e!==void 0?`${e}px`:void 0;function V9(e){const{prefixCls:t,containerRef:n,value:r,getValueIndex:o,motionName:s,onMotionStart:i,onMotionEnd:l,direction:c,vertical:u=!1}=e,d=a.useRef(null),[m,f]=a.useState(r),p=S=>{var R;const E=o(S),w=(R=n.current)==null?void 0:R.querySelectorAll(`.${t}-item`)[E];return(w==null?void 0:w.offsetParent)&&w},[y,b]=a.useState(null),[x,v]=a.useState(null);It(()=>{if(m!==r){const S=p(m),E=p(r),w=mw(S,u),R=mw(E,u);f(r),b(w),v(R),S&&E?i():l()}},[r]);const g=a.useMemo(()=>Co(u?(y==null?void 0:y.top)??0:c==="rtl"?-(y==null?void 0:y.right):y==null?void 0:y.left),[u,c,y]),h=a.useMemo(()=>Co(u?(x==null?void 0:x.top)??0:c==="rtl"?-(x==null?void 0:x.right):x==null?void 0:x.left),[u,c,x]),$=()=>u?{transform:"translateY(var(--thumb-start-top))",height:"var(--thumb-start-height)"}:{transform:"translateX(var(--thumb-start-left))",width:"var(--thumb-start-width)"},C=()=>u?{transform:"translateY(var(--thumb-active-top))",height:"var(--thumb-active-height)"}:{transform:"translateX(var(--thumb-active-left))",width:"var(--thumb-active-width)"},N=()=>{b(null),v(null),l()};return!y||!x?null:a.createElement(fr,{visible:!0,motionName:s,motionAppear:!0,onAppearStart:$,onAppearActive:C,onVisibleChanged:N},({className:S,style:E},w)=>{const R={...E,"--thumb-start-left":g,"--thumb-start-width":Co(y==null?void 0:y.width),"--thumb-active-left":h,"--thumb-active-width":Co(x==null?void 0:x.width),"--thumb-start-top":g,"--thumb-start-height":Co(y==null?void 0:y.height),"--thumb-active-top":h,"--thumb-active-height":Co(x==null?void 0:x.height)},P={ref:Tn(d,w),style:R,className:H(`${t}-thumb`,S)};return a.createElement("div",P)})}function W9(e){var t;if(typeof e.title<"u")return e.title;if(typeof e.label!="object")return(t=e.label)==null?void 0:t.toString()}function K9(e){return e.map(t=>{if(typeof t=="object"&&t!==null){const n=W9(t);return{...t,title:n}}return{label:t==null?void 0:t.toString(),title:t==null?void 0:t.toString(),value:t}})}const U9=({prefixCls:e,className:t,style:n,styles:r,classNames:o,data:s,disabled:i,checked:l,label:c,title:u,value:d,name:m,onChange:f,onFocus:p,onBlur:y,onKeyDown:b,onKeyUp:x,onMouseDown:v,itemRender:g=h=>h})=>{const h=C=>{i||f(C,d)},$=a.createElement("label",{className:H(t,{[`${e}-item-disabled`]:i}),style:n,onMouseDown:v},a.createElement("input",{name:m,className:`${e}-item-input`,type:"radio",disabled:i,checked:l,onChange:h,onFocus:p,onBlur:y,onKeyDown:b,onKeyUp:x}),a.createElement("div",{className:H(`${e}-item-label`,o==null?void 0:o.label),title:u,style:r==null?void 0:r.label},c));return g($,{item:s})},q9=a.forwardRef((e,t)=>{var _;const{prefixCls:n="rc-segmented",direction:r,vertical:o,options:s=[],disabled:i,defaultValue:l,value:c,name:u,onChange:d,className:m="",style:f,styles:p,classNames:y,motionName:b="thumb-motion",itemRender:x,...v}=e,g=a.useRef(null),h=a.useMemo(()=>Tn(g,t),[g,t]),$=a.useMemo(()=>K9(s),[s]),[C,N]=nn(l??((_=$[0])==null?void 0:_.value),c),[S,E]=a.useState(!1),w=(D,V)=>{N(V),d==null||d(V)},R=Dt(v,["children"]),[P,T]=a.useState(!1),[M,z]=a.useState(!1),B=()=>{z(!0)},F=()=>{z(!1)},L=()=>{T(!1)},j=D=>{D.key==="Tab"&&T(!0)},O=D=>{const V=$.findIndex(Y=>Y.value===C),W=$.length,K=(V+D+W)%W,q=$[K];q&&(N(q.value),d==null||d(q.value))},A=D=>{switch(D.key){case"ArrowLeft":case"ArrowUp":O(-1);break;case"ArrowRight":case"ArrowDown":O(1);break}},k=D=>{const{value:V,disabled:W}=D;return a.createElement(U9,qr({},D,{name:u,data:D,itemRender:x,key:V,prefixCls:n,className:H(D.className,`${n}-item`,y==null?void 0:y.item,{[`${n}-item-selected`]:V===C&&!S,[`${n}-item-focused`]:M&&P&&V===C}),style:p==null?void 0:p.item,classNames:y,styles:p,checked:V===C,onChange:w,onFocus:B,onBlur:F,onKeyDown:A,onKeyUp:j,onMouseDown:L,disabled:!!i||!!W}))};return a.createElement("div",qr({role:"radiogroup","aria-label":"segmented control",tabIndex:i?void 0:0,"aria-orientation":o?"vertical":"horizontal",style:f},R,{className:H(n,{[`${n}-rtl`]:r==="rtl",[`${n}-disabled`]:i,[`${n}-vertical`]:o},m),ref:h}),a.createElement("div",{className:`${n}-group`},a.createElement(V9,{vertical:o,prefixCls:n,value:C,containerRef:g,motionName:`${n}-${b}`,direction:r,getValueIndex:D=>$.findIndex(V=>V.value===D),onMotionStart:()=>{E(!0)},onMotionEnd:()=>{E(!1)}}),$.map(k)))}),G9=q9;function pw(e,t){return{[`${e}, ${e}:hover, ${e}:focus`]:{color:t.colorTextDisabled,cursor:"not-allowed"}}}const gw=e=>({background:e.itemSelectedBg,boxShadow:e.boxShadowTertiary}),X9={overflow:"hidden",...ar},Y9=e=>{const{componentCls:t,motionDurationSlow:n,motionEaseInOut:r,motionDurationMid:o}=e,s=e.calc(e.controlHeight).sub(e.calc(e.trackPadding).mul(2)).equal(),i=e.calc(e.controlHeightLG).sub(e.calc(e.trackPadding).mul(2)).equal(),l=e.calc(e.controlHeightSM).sub(e.calc(e.trackPadding).mul(2)).equal();return{[t]:{...Ft(e),display:"inline-block",padding:e.trackPadding,color:e.itemColor,background:e.trackBg,borderRadius:e.borderRadius,transition:`all ${o}`,...Br(e),[`${t}-group`]:{position:"relative",display:"flex",alignItems:"stretch",justifyItems:"flex-start",flexDirection:"row",width:"100%"},[`&${t}-rtl`]:{direction:"rtl"},[`&${t}-vertical`]:{[`${t}-group`]:{flexDirection:"column"},[`${t}-thumb`]:{width:"100%",height:0,padding:`0 ${G(e.paddingXXS)}`}},[`&${t}-block`]:{display:"flex"},[`&${t}-block ${t}-item`]:{flex:1,minWidth:0},[`${t}-item`]:{position:"relative",textAlign:"center",cursor:"pointer",transition:`color ${o}`,borderRadius:e.borderRadiusSM,transform:"translateZ(0)","&-selected":{...gw(e),color:e.itemSelectedColor},"&-focused":jr(e),"&::after":{content:'""',position:"absolute",zIndex:-1,width:"100%",height:"100%",top:0,insetInlineStart:0,borderRadius:"inherit",opacity:0,pointerEvents:"none",transition:["opacity","background-color"].map(c=>`${c} ${o}`).join(", ")},[`&:not(${t}-item-selected):not(${t}-item-disabled)`]:{"&:hover, &:active":{color:e.itemHoverColor},"&:hover::after":{opacity:1,backgroundColor:e.itemHoverBg},"&:active::after":{opacity:1,backgroundColor:e.itemActiveBg}},"&-label":{minHeight:s,lineHeight:G(s),padding:`0 ${G(e.segmentedPaddingHorizontal)}`,...X9},"&-icon + *":{marginInlineStart:e.calc(e.marginSM).div(2).equal()},"&-input":{position:"absolute",insetBlockStart:0,insetInlineStart:0,width:0,height:0,opacity:0,pointerEvents:"none"}},[`${t}-thumb`]:{...gw(e),position:"absolute",insetBlockStart:0,insetInlineStart:0,width:0,height:"100%",padding:`${G(e.paddingXXS)} 0`,borderRadius:e.borderRadiusSM,[`& ~ ${t}-item:not(${t}-item-selected):not(${t}-item-disabled)::after`]:{backgroundColor:"transparent"}},[`&${t}-lg`]:{borderRadius:e.borderRadiusLG,[`${t}-item-label`]:{minHeight:i,lineHeight:G(i),padding:`0 ${G(e.segmentedPaddingHorizontal)}`,fontSize:e.fontSizeLG},[`${t}-item, ${t}-thumb`]:{borderRadius:e.borderRadius}},[`&${t}-sm`]:{borderRadius:e.borderRadiusSM,[`${t}-item-label`]:{minHeight:l,lineHeight:G(l),padding:`0 ${G(e.segmentedPaddingHorizontalSM)}`},[`${t}-item, ${t}-thumb`]:{borderRadius:e.borderRadiusXS}},...pw(`&-disabled ${t}-item`,e),...pw(`${t}-item-disabled`,e),[`${t}-thumb-motion-appear-active`]:{willChange:"transform, width",transition:["transform","width"].map(c=>`${c} ${n} ${r}`).join(", ")},[`&${t}-shape-round`]:{borderRadius:9999,[`${t}-item, ${t}-thumb`]:{borderRadius:9999}}}}},Q9=e=>{const{colorTextLabel:t,colorText:n,colorFillSecondary:r,colorBgElevated:o,colorFill:s,lineWidthBold:i,colorBgLayout:l}=e;return{trackPadding:i,trackBg:l,itemColor:t,itemHoverColor:n,itemHoverBg:r,itemSelectedBg:o,itemActiveBg:s,itemSelectedColor:n}},J9=Tt("Segmented",e=>{const{lineWidth:t,calc:n}=e,r=Rt(e,{segmentedPaddingHorizontal:n(e.controlPaddingHorizontal).sub(t).equal(),segmentedPaddingHorizontalSM:n(e.controlPaddingHorizontalSM).sub(t).equal()});return Y9(r)},Q9);function Z9(e){return dt(e)&&!!(e!=null&&e.icon)}const eV=a.forwardRef((e,t)=>{const n=jo(),{prefixCls:r,className:o,rootClassName:s,block:i,options:l=[],size:c,style:u,vertical:d,orientation:m,shape:f="default",name:p=n,styles:y,classNames:b,...x}=e,{getPrefixCls:v,direction:g,className:h,style:$,classNames:C,styles:N}=Pt("segmented"),S={...e,options:l,size:c,shape:f},E=Mt($),w=Mt(u),[R,P]=Ot([C,b],[N,E,y,w],{props:S}),T=v("segmented",r),[M,z]=J9(T),B=Cn(c),F=a.useMemo(()=>l.map(A=>{if(Z9(A)){const{icon:k,label:_,...D}=A;return{...D,label:a.createElement(a.Fragment,null,a.createElement("span",{className:H(`${T}-item-icon`,R.icon),style:P.icon},k),_&&a.createElement("span",null,_))}}return A}),[l,T,R.icon,P.icon]),[,L]=Gc(m,d),j=H(o,s,h,R.root,{[`${T}-block`]:i,[`${T}-sm`]:B==="small",[`${T}-lg`]:B==="large",[`${T}-vertical`]:L,[`${T}-shape-${f}`]:f==="round"},M,z),O=(A,{item:k})=>{if(!k.tooltip)return A;const _=dt(k.tooltip)?k.tooltip:{title:k.tooltip};return a.createElement(bo,{..._},A)};return a.createElement(G9,{...x,name:p,className:j,style:P.root,classNames:R,styles:P,itemRender:O,options:F,ref:t,prefixCls:T,direction:g,vertical:L})}),tV=eV;var bT={};Object.defineProperty(bT,"__esModule",{value:!0});var nV={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"minus",theme:"outlined"},rV=bT.default=nV;function Rv(){return Rv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Rv({},e,{ref:t,icon:rV})),sV=a.forwardRef(oV);var xT={};Object.defineProperty(xT,"__esModule",{value:!0});var iV={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"}}]},name:"up",theme:"outlined"},aV=xT.default=iV;function Tv(){return Tv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Tv({},e,{ref:t,icon:aV})),cV=a.forwardRef(lV);function Mv(){return typeof BigInt=="function"}function $T(e){return!e&&e!==0&&!Number.isNaN(e)||!String(e).trim()}function xi(e){var t=e.trim(),n=t.startsWith("-");n&&(t=t.slice(1)),t=t.replace(/(\.\d*[^0])0*$/,"$1").replace(/\.0*$/,"").replace(/^0+/,""),t.startsWith(".")&&(t="0".concat(t));var r=t||"0",o=r.split("."),s=o[0]||"0",i=o[1]||"0";s==="0"&&i==="0"&&(n=!1);var l=n?"-":"";return{negative:n,negativeStr:l,trimStr:r,integerStr:s,decimalStr:i,fullStr:"".concat(l).concat(r)}}function Am(e){var t=String(e);return!Number.isNaN(Number(t))&&t.includes("e")}function ST(e){var t=e.toLowerCase().split("e"),n=nf(t,2),r=n[0],o=n[1],s=o===void 0?"0":o,i=r.startsWith("-"),l=i?r.slice(1):r,c=l.split("."),u=nf(c,2),d=u[0],m=d===void 0?"0":d,f=u[1],p=f===void 0?"":f,y="".concat(m).concat(p).replace(/^0+/,"")||"0";return{decimal:p,digits:y,exponent:Number(s),integer:m,negative:i}}function uV(e){var t=e.decimal,n=e.digits,r=e.exponent,o=e.integer,s=e.negative;if(n==="0")return"0";var i=o.replace(/^0+/,"").length,l=(t.match(/^0*/)||[""])[0].length,c=i||-l,u=c+r,d="";return u<=0?d="0.".concat("0".repeat(-u)).concat(n):u>=n.length?d="".concat(n).concat("0".repeat(u-n.length)):d="".concat(n.slice(0,u),".").concat(n.slice(u)),"".concat(s?"-":"").concat(d)}function CT(e){return e.exponent>=0?Math.max(0,e.decimal.length-e.exponent):Math.abs(e.exponent)+e.decimal.length}function ii(e){var t=String(e);return Am(e)?CT(ST(t)):t.includes(".")&&Qx(t)?t.length-t.indexOf(".")-1:0}function Dm(e){var t=String(e);if(Am(e)){if(e>Number.MAX_SAFE_INTEGER)return String(Mv()?BigInt(e).toString():Number.MAX_SAFE_INTEGER);if(e100?uV(n):e.toFixed(r)}return xi(t).fullStr}function Qx(e){return typeof e=="number"?!Number.isNaN(e):e?/^\s*-?\d+(\.\d+)?\s*$/.test(e)||/^\s*-?\d+\.\s*$/.test(e)||/^\s*-?\.\d+\s*$/.test(e):!1}var dV=function(){function e(t){if(zi(this,e),En(this,"origin",""),En(this,"negative",void 0),En(this,"integer",void 0),En(this,"decimal",void 0),En(this,"decimalLen",void 0),En(this,"empty",void 0),En(this,"nan",void 0),$T(t)){this.empty=!0;return}if(this.origin=String(t),t==="-"||Number.isNaN(t)){this.nan=!0;return}var n=t;if(Am(n)&&(n=Number(n)),n=typeof n=="string"?n:Dm(n),Qx(n)){var r=xi(n);this.negative=r.negative;var o=r.trimStr.split(".");this.integer=BigInt(o[0]);var s=o[1]||"0";this.decimal=BigInt(s),this.decimalLen=s.length}else this.nan=!0}return ji(e,[{key:"getMark",value:function(){return this.negative?"-":""}},{key:"getIntegerStr",value:function(){return this.integer.toString()}},{key:"getDecimalStr",value:function(){return this.decimal.toString().padStart(this.decimalLen,"0")}},{key:"alignDecimal",value:function(n){var r="".concat(this.getMark()).concat(this.getIntegerStr()).concat(this.getDecimalStr().padEnd(n,"0"));return BigInt(r)}},{key:"negate",value:function(){var n=new e(this.toString());return n.negative=!n.negative,n}},{key:"cal",value:function(n,r,o){var s=Math.max(this.getDecimalStr().length,n.getDecimalStr().length),i=this.alignDecimal(s),l=n.alignDecimal(s),c=r(i,l).toString(),u=o(s),d=xi(c),m=d.negativeStr,f=d.trimStr,p="".concat(m).concat(f.padStart(u+1,"0"));return new e("".concat(p.slice(0,-u),".").concat(p.slice(-u)))}},{key:"add",value:function(n){if(this.isInvalidate())return new e(n);var r=new e(n);return r.isInvalidate()?this:this.cal(r,function(o,s){return o+s},function(o){return o})}},{key:"multi",value:function(n){var r=new e(n);return this.isInvalidate()||r.isInvalidate()?new e(NaN):this.cal(r,function(o,s){return o*s},function(o){return o*2})}},{key:"isEmpty",value:function(){return this.empty}},{key:"isNaN",value:function(){return this.nan}},{key:"isInvalidate",value:function(){return this.isEmpty()||this.isNaN()}},{key:"equals",value:function(n){return this.toString()===(n==null?void 0:n.toString())}},{key:"lessEquals",value:function(n){return this.add(n.negate().toString()).toNumber()<=0}},{key:"toNumber",value:function(){return this.isNaN()?NaN:Number(this.toString())}},{key:"toString",value:function(){var n=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0;return n?this.isInvalidate()?"":xi("".concat(this.getMark()).concat(this.getIntegerStr(),".").concat(this.getDecimalStr())).fullStr:this.origin}}]),e}(),fV=function(){function e(t){if(zi(this,e),En(this,"origin",""),En(this,"number",void 0),En(this,"empty",void 0),$T(t)){this.empty=!0;return}this.origin=String(t),this.number=Number(t)}return ji(e,[{key:"negate",value:function(){return new e(-this.toNumber())}},{key:"add",value:function(n){if(this.isInvalidate())return new e(n);var r=Number(n);if(Number.isNaN(r))return this;var o=this.number+r;if(o>Number.MAX_SAFE_INTEGER)return new e(Number.MAX_SAFE_INTEGER);if(oNumber.MAX_SAFE_INTEGER)return new e(Number.MAX_SAFE_INTEGER);if(o0&&arguments[0]!==void 0?arguments[0]:!0;return n?this.isInvalidate()?"":Am(this.number)&&ii(this.number)>100?String(this.number):Dm(this.number):this.origin}}]),e}();function oo(e){return Mv()?new dV(e):new fV(e)}function Pd(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(e==="")return"";var o=xi(e),s=o.negativeStr,i=o.integerStr,l=o.decimalStr,c="".concat(t).concat(l),u="".concat(s).concat(i);if(n>=0){var d=Number(l[n]);if(d>=5&&!r){var m=oo(e).add("".concat(s,"0.").concat("0".repeat(n)).concat(10-d));return Pd(m.toString(),t,n,r)}return n===0?u:"".concat(u).concat(t).concat(l.padEnd(n,"0").slice(0,n))}return c===".0"?u:"".concat(u).concat(c)}function mV(e,t){const n=a.useRef(null);function r(){try{const{selectionStart:s,selectionEnd:i,value:l}=e,c=l.substring(0,s),u=l.substring(i);n.current={start:s,end:i,value:l,beforeTxt:c,afterTxt:u}}catch{}}function o(){if(e&&n.current&&t)try{const{value:s}=e,{beforeTxt:i,afterTxt:l,start:c}=n.current;let u=s.length;if(s.startsWith(i))u=i.length;else if(s.endsWith(l))u=s.length-n.current.afterTxt.length;else{const d=i[c-1],m=s.indexOf(d,c-1);m!==-1&&(u=m+1)}e.setSelectionRange(u,u)}catch(s){fn(!1,`Something warning of cursor restore. Please fire issue about this: ${s.message}`)}}return[r,o]}const pV=200,gV=600;function hw({prefixCls:e,action:t,children:n,disabled:r,className:o,style:s,onStep:i}){const l=t==="up",c=a.useRef(),u=a.useRef([]),d=()=>{clearTimeout(c.current)},m=b=>{b.preventDefault(),d(),i(l,"handler");function x(){i(l,"handler"),c.current=setTimeout(x,pV)}c.current=setTimeout(x,gV)};a.useEffect(()=>()=>{d(),u.current.forEach(b=>{Ct.cancel(b)})},[]);const f=`${e}-action`,p=H(f,`${f}-${t}`,{[`${f}-${t}-disabled`]:r},o),y=()=>u.current.push(Ct(d));return a.createElement("span",{unselectable:"on",role:"button",onMouseUp:y,onMouseLeave:y,onMouseDown:b=>{m(b)},"aria-label":l?"Increase Value":"Decrease Value","aria-disabled":r,className:p,style:s},n||a.createElement("span",{unselectable:"on",className:`${e}-action-${t}-inner`}))}function yw(e){const t=typeof e=="number"?Dm(e):xi(e).fullStr;return t.includes(".")?xi(t.replace(/(\d)\.(\d)/g,"$1$2.")).fullStr:e+"0"}const hV=()=>{const e=a.useRef(0),t=()=>{Ct.cancel(e.current)};return a.useEffect(()=>t,[]),n=>{t(),e.current=Ct(()=>{n()})}};function Zl(){return Zl=Object.assign?Object.assign.bind():function(e){for(var t=1;te||t.isEmpty()?t.toString():t.toNumber(),bw=e=>{const t=oo(e);return t.isInvalidate()?null:t},yV=a.forwardRef((e,t)=>{const{mode:n="input",prefixCls:r="rc-input-number",className:o,style:s,classNames:i,styles:l,min:c,max:u,step:d=1,defaultValue:m,value:f,disabled:p,readOnly:y,upHandler:b,downHandler:x,keyboard:v,changeOnWheel:g=!1,controls:h=!0,prefix:$,suffix:C,stringMode:N,parser:S,formatter:E,precision:w,decimalSeparator:R,onChange:P,onInput:T,onPressEnter:M,onStep:z,onMouseDown:B,onClick:F,onMouseUp:L,onMouseLeave:j,onMouseMove:O,onMouseEnter:A,onMouseOut:k,changeOnBlur:_=!0,...D}=e,[V,W]=a.useState(!1),K=a.useRef(!1),q=a.useRef(!1),Y=a.useRef(!1),ee=a.useRef(null),ie=a.useRef(null);a.useImperativeHandle(t,()=>UO(ie.current,{focus:ve=>{Db(ie.current,ve)},blur:()=>{var ve;(ve=ie.current)==null||ve.blur()},nativeElement:ee.current}));const[ae,U]=a.useState(()=>oo(f??m));function Q(ve){f===void 0&&U(ve)}const Z=a.useCallback((ve,je)=>{if(!je)return w>=0?w:Math.max(ii(ve),ii(d))},[w,d]),ne=a.useCallback(ve=>{const je=String(ve);if(S)return S(je);let ce=je;return R&&(ce=ce.replace(R,".")),ce.replace(/[^\w.-]+/g,"")},[S,R]),oe=a.useRef(""),le=a.useCallback((ve,je)=>{if(E)return E(ve,{userTyping:je,input:String(oe.current)});let ce=typeof ve=="number"?Dm(ve):ve;if(!je){const Pe=Z(ce,je);Qx(ce)&&(R||Pe>=0)&&(ce=Pd(ce,R||".",Pe))}return ce},[E,Z,R]),[re,X]=a.useState(()=>{const ve=m??f;return ae.isInvalidate()&&["string","number"].includes(typeof ve)?Number.isNaN(ve)?"":ve:le(ae.toString(),!1)});oe.current=re;function se(ve,je){X(le(ve.isInvalidate()?ve.toString(!1):ve.toString(!je),je))}const ge=a.useMemo(()=>bw(u),[u,w]),de=a.useMemo(()=>bw(c),[c,w]),Se=a.useMemo(()=>!ge||!ae||ae.isInvalidate()?!1:ge.lessEquals(ae),[ge,ae]),ue=a.useMemo(()=>!de||!ae||ae.isInvalidate()?!1:ae.lessEquals(de),[de,ae]),[be,Ne]=mV(ie.current,V),we=ve=>ge&&!ve.lessEquals(ge)?ge:de&&!de.lessEquals(ve)?de:null,ze=ve=>!we(ve),he=(ve,je)=>{let ce=ve,Pe=ze(ce)||ce.isEmpty();if(!ce.isEmpty()&&!je&&(ce=we(ce)||ce,Pe=!0),!y&&!p&&Pe){const pe=ce.toString(),$e=Z(pe,je);return $e>=0&&(ce=oo(Pd(pe,".",$e)),ze(ce)||(ce=oo(Pd(pe,".",$e,!0)))),ce.equals(ae)||(Q(ce),P==null||P(ce.isEmpty()?null:vw(N,ce)),f===void 0&&se(ce,je)),ce}return ae},ke=hV(),Oe=ve=>{if(be(),oe.current=ve,X(ve),!q.current){const je=ne(ve),ce=oo(je);ce.isNaN()||he(ce,!0)}T==null||T(ve),ke(()=>{let je=ve;S||(je=ve.replace(/。/g,".")),je!==ve&&Oe(je)})},Ce=()=>{q.current=!0},Me=()=>{q.current=!1,Oe(ie.current.value)},xe=ve=>{Oe(ve.target.value)},Ee=vt((ve,je)=>{var $e;if(ve&&Se||!ve&&ue)return;K.current=!1;let ce=oo(Y.current?yw(d):d);ve||(ce=ce.negate());const Pe=(ae||oo(0)).add(ce.toString()),pe=he(Pe,!1);z==null||z(vw(N,pe),{offset:Y.current?yw(d):d,type:ve?"up":"down",emitter:je}),($e=ie.current)==null||$e.focus()}),Ve=ve=>{const je=oo(ne(re));let ce;je.isNaN()?ce=he(ae,ve):ce=he(je,ve),f!==void 0?se(ae,!1):ce.isNaN()||se(ce,!1)},qe=()=>{K.current=!0},me=ve=>{const{key:je,shiftKey:ce}=ve;K.current=!0,Y.current=ce,je==="Enter"&&(q.current||(K.current=!1),Ve(!1),M==null||M(ve)),v!==!1&&!q.current&&["Up","ArrowUp","Down","ArrowDown"].includes(je)&&(Ee(je==="Up"||je==="ArrowUp","keyboard"),ve.preventDefault())},Re=()=>{K.current=!1,Y.current=!1};a.useEffect(()=>{if(g&&V){const ve=ce=>{Ee(ce.deltaY<0,"wheel"),ce.preventDefault()},je=ie.current;if(je)return je.addEventListener("wheel",ve,{passive:!1}),()=>je.removeEventListener("wheel",ve)}});const Te=()=>{_&&Ve(!1),W(!1),K.current=!1},Ue=ve=>{ie.current&&ve.target!==ie.current&&(ie.current.focus(),ve.preventDefault()),B==null||B(ve)};pd(()=>{ae.isInvalidate()||se(ae,!1)},[w,E]),pd(()=>{const ve=oo(f);U(ve);const je=oo(ne(re));(!ve.equals(je)||!K.current||E)&&se(ve,K.current)},[f]),pd(()=>{E&&Ne()},[re]);const Ge={prefixCls:r,onStep:Ee,className:i==null?void 0:i.action,style:l==null?void 0:l.action},Fe=a.createElement(hw,Zl({},Ge,{action:"up",disabled:Se}),b),et=a.createElement(hw,Zl({},Ge,{action:"down",disabled:ue}),x);return a.createElement("div",{ref:ee,className:H(r,`${r}-mode-${n}`,o,i==null?void 0:i.root,{[`${r}-focused`]:V,[`${r}-disabled`]:p,[`${r}-readonly`]:y,[`${r}-not-a-number`]:ae.isNaN(),[`${r}-out-of-range`]:!ae.isInvalidate()&&!ze(ae)}),style:{...l==null?void 0:l.root,...s},onMouseDown:Ue,onMouseUp:L,onMouseLeave:j,onMouseMove:O,onMouseEnter:A,onMouseOut:k,onClick:F,onFocus:()=>{W(!0)},onBlur:Te,onKeyDown:me,onKeyUp:Re,onCompositionStart:Ce,onCompositionEnd:Me,onBeforeInput:qe},n==="spinner"&&h&&et,$!==void 0&&a.createElement("div",{className:H(`${r}-prefix`,i==null?void 0:i.prefix),style:l==null?void 0:l.prefix},$),a.createElement("input",Zl({autoComplete:"off",role:"spinbutton","aria-valuemin":c,"aria-valuemax":u,"aria-valuenow":ae.isInvalidate()?null:ae.toString(),step:d,ref:ie,className:H(`${r}-input`,i==null?void 0:i.input),style:l==null?void 0:l.input,value:re,onChange:xe,disabled:p,readOnly:y},D)),C!==void 0&&a.createElement("div",{className:H(`${r}-suffix`,i==null?void 0:i.suffix),style:l==null?void 0:l.suffix},C),n==="spinner"&&h&&Fe,n==="input"&&h&&a.createElement("div",{className:H(`${r}-actions`,i==null?void 0:i.actions),style:l==null?void 0:l.actions},Fe,et))}),vV=e=>{const{componentCls:t,borderRadius:n,paddingSM:r,colorBorder:o,paddingXS:s,fontSizeLG:i,fontSizeSM:l,borderRadiusLG:c,borderRadiusSM:u,colorBgContainerDisabled:d,lineWidth:m,antCls:f}=e,[p,y]=rn(f,"space-addon");return{[t]:[{...Ft(e),display:"inline-flex",alignItems:"center",gap:0,whiteSpace:"nowrap",paddingInline:r,margin:0,borderWidth:m,borderStyle:"solid",borderRadius:n,"&:hover":{zIndex:0},[`&${t}-disabled`]:{color:e.colorTextDisabled},"&-large":{fontSize:i,borderRadius:c},"&-small":{paddingInline:s,borderRadius:u,fontSize:l},"&-compact-last-item":{borderEndStartRadius:0,borderStartStartRadius:0},"&-compact-first-item":{borderEndEndRadius:0,borderStartEndRadius:0},"&-compact-item:not(:first-child):not(:last-child)":{borderRadius:0},"&-compact-item:not(:last-child)":{borderInlineEndWidth:0},"&-compact-item:not(:first-child)":{borderInlineStartWidth:0}},{[p("addon-border-color")]:o,[p("addon-background")]:d,[p("addon-border-color-outlined")]:o,[p("addon-background-filled")]:d,borderColor:y("addon-border-color"),background:y("addon-background"),"&-variant-outlined":{[p("addon-border-color")]:y("addon-border-color-outlined")},"&-variant-filled":{[p("addon-border-color")]:"transparent",[p("addon-background")]:y("addon-background-filled"),[`&${t}-disabled`]:{[p("addon-border-color")]:o,[p("addon-background")]:d}},"&-variant-borderless":{border:"none",background:"transparent"},"&-variant-underlined":{border:"none",background:"transparent"}},{"&-status-error":{[p("addon-border-color-outlined")]:e.colorError,[p("addon-background-filled")]:e.colorErrorBg,color:e.colorError},"&-status-warning":{[p("addon-border-color-outlined")]:e.colorWarning,[p("addon-background-filled")]:e.colorWarningBg,color:e.colorWarning}}]}},bV=Tt("Addon",e=>[vV(e),Qc(e,{focus:!1})]),wT=J.forwardRef((e,t)=>{const{className:n,children:r,style:o,prefixCls:s,variant:i="outlined",disabled:l,status:c,...u}=e,{getPrefixCls:d,direction:m}=J.useContext(ct),f=d("space-addon",s),[p,y]=bV(f),{compactItemClassnames:b,compactSize:x}=qs(f,m),v=qa(f,c),g=H(f,p,b,y,`${f}-variant-${i}`,v,{[`${f}-${x}`]:x,[`${f}-disabled`]:l},n);return J.createElement("div",{ref:t,className:g,style:o,...u},r)}),xV=e=>{const t=e.handleVisible??"auto",n=e.controlHeightSM-e.lineWidth*2;return{...Vi(e),controlWidth:90,handleWidth:n,handleFontSize:e.fontSize/2,handleVisible:t,handleActiveBg:e.colorFillAlter,handleBg:e.colorBgContainer,filledHandleBg:new Gt(e.colorFillSecondary).onBackground(e.colorBgContainer).toHexString(),handleHoverColor:e.colorPrimary,handleBorderColor:e.colorBorder,handleOpacity:t===!0?1:0,handleVisibleWidth:t===!0?n:0}},$V=e=>{const{componentCls:t,lineWidth:n,lineType:r,borderRadius:o,inputFontSizeSM:s,inputFontSizeLG:i,colorError:l,paddingInlineSM:c,paddingBlockSM:u,paddingBlockLG:d,paddingInlineLG:m,colorIcon:f,colorTextDisabled:p,motionDurationMid:y,handleHoverColor:b,handleOpacity:x,paddingInline:v,paddingBlock:g,handleBg:h,handleActiveBg:$,inputAffixPadding:C,borderRadiusSM:N,controlWidth:S,handleBorderColor:E,filledHandleBg:w,lineHeightLG:R,antCls:P}=e,T=`${G(n)} ${r} ${E}`,[M,z]=rn(P,"input-number");return[{[t]:{...Ft(e),...km(e),[M("input-padding-block")]:G(g),[M("input-padding-inline")]:G(v),display:"inline-flex",width:S,margin:0,paddingBlock:0,borderRadius:o,...U4(e,{[`${t}-actions`]:{background:h,[`${t}-action-down`]:{borderBlockStart:T}}}),...Q4(e,{[`${t}-actions`]:{background:w,[`${t}-action-down`]:{borderBlockStart:T}},"&:focus-within":{[`${t}-actions`]:{background:h}}}),...Z4(e,{[`${t}-actions`]:{background:h,[`${t}-action-down`]:{borderBlockStart:T}}}),...X4(e),[`&${t}-borderless`]:{paddingBlock:0,[M("input-padding-block")]:G(e.calc(g).add(n).equal())},[`&${t}-borderless${t}-sm`]:{paddingBlock:0,[M("input-padding-block")]:G(e.calc(u).add(n).equal())},[`&${t}-borderless${t}-lg`]:{paddingBlock:0,[M("input-padding-block")]:G(e.calc(d).add(n).equal())},"&-rtl":{direction:"rtl",[`${t}-input`]:{direction:"rtl"}},[`&${t}-out-of-range`]:{[`${t}-input`]:{color:l}},[`${t}-input`]:{...Ft(e),width:"100%",paddingBlock:z("input-padding-block"),textAlign:"start",backgroundColor:"transparent",border:0,borderRadius:0,outline:0,transition:`all ${y} linear`,appearance:"textfield",fontSize:"inherit",lineHeight:"inherit",...eT(e.colorTextPlaceholder),'&[type="number"]::-webkit-inner-spin-button, &[type="number"]::-webkit-outer-spin-button':{margin:0,appearance:"none"}},[`&:hover ${t}-handler-wrap, &-focused ${t}-handler-wrap`]:{width:e.handleWidth,opacity:1},[`&-disabled ${t}-input`]:{cursor:"not-allowed",color:e.colorTextDisabled}}},{[t]:{[`${t}-action`]:{...Kc(),userSelect:"none",overflow:"hidden",fontWeight:"bold",lineHeight:0,textAlign:"center",cursor:"pointer",transition:`all ${y} linear`,[`&:active:not(${t}-action-up-disabled):not(${t}-action-down-disabled)`]:{background:$},[`&:hover:not(${t}-action-up-disabled):not(${t}-action-down-disabled)`]:{color:b},[`&${t}-action-up-disabled, &${t}-action-down-disabled`]:{cursor:"not-allowed",color:p}},"&-mode-input":{overflow:"hidden",[`${t}-actions`]:{position:"absolute",insetBlockStart:0,insetInlineEnd:0,width:e.handleVisibleWidth,opacity:x,height:"100%",borderRadius:0,display:"flex",flexDirection:"column",alignItems:"stretch",transition:`all ${y}`,overflow:"hidden",[`${t}-action`]:{display:"flex",alignItems:"center",justifyContent:"center",flex:"auto",height:"40%",marginInlineEnd:0,fontSize:e.handleFontSize}},[`&:hover ${t}-actions, &-focused ${t}-actions`]:{width:e.handleWidth,opacity:1},[`${t}-action`]:{color:f,height:"50%",borderInlineStart:T,[`&:hover:not(${t}-action-up-disabled):not(${t}-action-down-disabled)`]:{height:"60%"}},[`&${t}-disabled, &${t}-readonly`]:{[`${t}-actions`]:{display:"none"}}},[`&${t}-mode-spinner`]:{padding:0,width:"auto",[`${t}-action`]:{flex:"none",paddingInline:z("input-padding-inline"),"&-up":{borderInlineStart:T},"&-down":{borderInlineEnd:T}},[`${t}-input`]:{textAlign:"center",paddingInline:z("input-padding-inline")}}}},{[t]:{"&-lg":{[M("input-padding-block")]:G(d),[M("input-padding-inline")]:G(m),paddingBlock:0,fontSize:i,lineHeight:R},"&-sm":{[M("input-padding-block")]:G(u),[M("input-padding-inline")]:G(c),paddingBlock:0,fontSize:s,borderRadius:N}}},{[t]:{[`${t}-prefix, ${t}-suffix`]:{display:"flex",flex:"none",alignItems:"center",alignSelf:"center",pointerEvents:"none"},[`${t}-prefix`]:{marginInlineEnd:C},[`${t}-suffix`]:{height:"100%",marginInlineStart:C,transition:`margin ${y}`},[`&:hover:not(${t}-without-controls)`]:{[`${t}-suffix`]:{marginInlineEnd:e.handleWidth}}}}]},SV=e=>{const{componentCls:t,antCls:n}=e;return{[`${t}-addon`]:{[`&:has(${n}-select)`]:{border:0,padding:0}}}},CV=Tt("InputNumber",e=>{const t=Rt(e,Hi(e));return[$V(t),SV(t),Qc(t)]},xV,{unitless:{handleOpacity:!0},resetFont:!1}),wV=a.forwardRef((e,t)=>{const n=a.useRef(null);a.useImperativeHandle(t,()=>n.current);const{rootClassName:r,size:o,disabled:s,prefixCls:i,addonBefore:l,addonAfter:c,prefix:u,suffix:d,bordered:m,readOnly:f,status:p,controls:y=!0,variant:b,className:x,style:v,classNames:g,styles:h,mode:$,...C}=e,{direction:N,className:S,style:E,styles:w,classNames:R}=Pt("inputNumber"),P=a.useContext(cr),T=s??P,M=a.useMemo(()=>!y||T||f?!1:y,[y,T,f]),{compactSize:z,compactItemClassnames:B}=qs(i,N);let F=$==="spinner"?a.createElement(zo,null):a.createElement(cV,null),L=$==="spinner"?a.createElement(sV,null):a.createElement(Rx,null);const j=typeof M=="boolean"?M:void 0;dt(M)&&(F=M.upIcon||F,L=M.downIcon||L);const{hasFeedback:O,isFormItemInput:A,feedbackIcon:k}=a.useContext(Hn),_=Cn(ae=>o??z??ae),[D,V]=tl("inputNumber",b,m),W=O&&a.createElement(a.Fragment,null,k),K={...e,size:_,disabled:T,controls:M},q=Mt(E),Y=Mt(v),[ee,ie]=Ot([R,g],[w,q,h,Y],{props:K});return a.createElement(yV,{ref:n,mode:$,disabled:T,className:H(x,r,ee.root,S,B,qa(i,p,O),{[`${i}-${D}`]:V,[`${i}-lg`]:_==="large",[`${i}-sm`]:_==="small",[`${i}-rtl`]:N==="rtl",[`${i}-in-form-item`]:A,[`${i}-without-controls`]:!M}),style:ie.root,upHandler:F,downHandler:L,prefixCls:i,readOnly:f,controls:j,prefix:u,suffix:W||d,classNames:ee,styles:ie,...C})}),ET=a.forwardRef((e,t)=>{const{addonBefore:n,addonAfter:r,prefixCls:o,className:s,status:i,rootClassName:l,...c}=e,{getPrefixCls:u}=Pt("inputNumber"),d=u("input-number",o),{status:m}=a.useContext(Hn),f=nu(m,i),p=on(d),[y,b]=CV(d,p),x=n||r,v=a.createElement(wV,{ref:t,...c,prefixCls:d,status:f,className:H(b,p,y,s),rootClassName:x?void 0:l});if(x){const g=C=>C?a.createElement(wT,{className:H(`${d}-addon`,b,y),variant:e.variant,disabled:e.disabled,status:f},a.createElement(Ri,{form:!0},C)):null,h=g(n),$=g(r);return a.createElement(mx,{rootClassName:l},h,v,$)}return v}),wf=ET,EV=e=>a.createElement(to,{theme:{components:{InputNumber:{handleVisible:!0}}}},a.createElement(ET,{...e}));wf._InternalPanelDoNotUseOrYouWillBeFired=EV;function IV(e){return!!(e.addonBefore||e.addonAfter)}function PV(e){return!!(e.prefix||e.suffix||e.allowClear)}function xw(e,t,n){const r=t.cloneNode(!0),o=Object.create(e,{target:{value:r},currentTarget:{value:r}});return r.value=n,typeof t.selectionStart=="number"&&typeof t.selectionEnd=="number"&&(r.selectionStart=t.selectionStart,r.selectionEnd=t.selectionEnd),r.setSelectionRange=(...s)=>{t.setSelectionRange(...s)},o}function Ef(e,t,n,r){if(!n)return;let o=t;if(t.type==="click"){o=xw(t,e,""),n(o);return}if(e.type!=="file"&&r!==void 0){o=xw(t,e,r),n(o);return}n(o)}function Ov(){return Ov=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var O,A,k;const{inputElement:n,children:r,prefixCls:o,prefix:s,suffix:i,addonBefore:l,addonAfter:c,className:u,style:d,disabled:m,readOnly:f,focused:p,triggerFocus:y,allowClear:b,value:x,handleReset:v,hidden:g,classes:h,classNames:$,dataAttrs:C,styles:N,components:S,onClear:E}=e,w=r??n,R=(S==null?void 0:S.affixWrapper)||"span",P=(S==null?void 0:S.groupWrapper)||"span",T=(S==null?void 0:S.wrapper)||"span",M=(S==null?void 0:S.groupAddon)||"span",z=a.useRef(null),B=_=>{var D;(D=z.current)!=null&&D.contains(_.target)&&(y==null||y())},F=PV(e);let L=a.cloneElement(w,{value:x,className:H((O=w.props)==null?void 0:O.className,!F&&($==null?void 0:$.variant))||null});const j=a.useRef(null);if(J.useImperativeHandle(t,()=>({nativeElement:j.current||z.current})),F){let _=null;if(b){const K=!m&&!f&&x&&!(typeof b=="object"&&b.disabled),q=`${o}-clear-icon`,Y=typeof b=="object"&&(b!=null&&b.clearIcon)?b.clearIcon:"✖";_=J.createElement("button",{type:"button",onClick:ee=>{v==null||v(ee),E==null||E()},onMouseDown:ee=>ee.preventDefault(),className:H(q,{[`${q}-hidden`]:!K,[`${q}-has-suffix`]:!!i},$==null?void 0:$.clear),style:N==null?void 0:N.clear},Y)}const D=`${o}-affix-wrapper`,V=H(D,{[`${o}-disabled`]:m,[`${D}-disabled`]:m,[`${D}-focused`]:p,[`${D}-readonly`]:f,[`${D}-input-with-clear-btn`]:i&&b&&x},h==null?void 0:h.affixWrapper,$==null?void 0:$.affixWrapper,$==null?void 0:$.variant),W=(i||b)&&J.createElement("span",{className:H(`${o}-suffix`,$==null?void 0:$.suffix),style:N==null?void 0:N.suffix},_,i);L=J.createElement(R,Ov({className:V,style:N==null?void 0:N.affixWrapper,onClick:B},C==null?void 0:C.affixWrapper,{ref:z}),s&&J.createElement("span",{className:H(`${o}-prefix`,$==null?void 0:$.prefix),style:N==null?void 0:N.prefix},s),L,W)}if(IV(e)){const _=`${o}-group`,D=`${_}-addon`,V=`${_}-wrapper`,W=H(`${o}-wrapper`,_,h==null?void 0:h.wrapper,$==null?void 0:$.wrapper),K=H(V,{[`${V}-disabled`]:m},h==null?void 0:h.group,$==null?void 0:$.groupWrapper);L=J.createElement(P,{className:K,ref:j},J.createElement(T,{className:W},l&&J.createElement(M,{className:D},l),L,c&&J.createElement(M,{className:D},c)))}return J.cloneElement(L,{className:H((A=L.props)==null?void 0:A.className,u)||null,style:{...(k=L.props)==null?void 0:k.style,...d},hidden:g})});function PT(e,t){return a.useMemo(()=>{let n={};t&&(n.show=typeof t=="object"&&t.formatter?t.formatter:!!t),n={...n,...e};const{show:r,...o}=n;return{...o,show:!!r,showFormatter:typeof r=="function"?r:void 0,strategy:o.strategy||(s=>s.length)}},[e,t])}function NT({countConfig:e,value:t,maxLength:n}){return a.useMemo(()=>{const r=e.max??n,o=e.strategy(t),s=!!r&&o>r,i=Number(r)>0,l=e.show?e.showFormatter?e.showFormatter({value:t,count:o,maxLength:r}):`${o}${i?` / ${r}`:""}`:void 0;return{mergedMax:r,isOutOfRange:s,dataCount:l}},[e,n,t])}function RT({countConfig:e,getTarget:t}){const[n,r]=a.useState(null),o=a.useRef(t);return a.useEffect(()=>{o.current=t},[t]),a.useEffect(()=>{var i;n&&((i=o.current())==null||i.setSelectionRange(...n),r(null))},[n]),a.useCallback((i,l)=>{var u,d;let c=i;return!l&&e.exceedFormatter&&e.max&&e.strategy(i)>e.max&&(c=e.exceedFormatter(i,{max:e.max}),i!==c&&r([((u=o.current())==null?void 0:u.selectionStart)||0,((d=o.current())==null?void 0:d.selectionEnd)||0])),c},[e])}function TT(e,t){const[n,r]=nn(e,t),o=n==null?"":String(n);return{value:n,setValue:r,formatValue:o}}function If(){return If=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{autoComplete:n,onChange:r,onFocus:o,onBlur:s,onPressEnter:i,onKeyDown:l,onKeyUp:c,prefixCls:u="rc-input",disabled:d,htmlSize:m,className:f,maxLength:p,suffix:y,showCount:b,count:x,type:v="text",classes:g,classNames:h,styles:$,onCompositionStart:C,onCompositionEnd:N,...S}=e,[E,w]=a.useState(!1),R=a.useRef(!1),P=a.useRef(!1),T=a.useRef(null),M=a.useRef(null),z=U=>{T.current&&Db(T.current,U)},{setValue:B,formatValue:F}=TT(e.defaultValue,e.value),L=PT(x,b),{isOutOfRange:j,dataCount:O}=NT({countConfig:L,value:F,maxLength:p}),A=RT({countConfig:L,getTarget:()=>T.current});a.useImperativeHandle(t,()=>{var U;return{focus:z,blur:()=>{var Q;(Q=T.current)==null||Q.blur()},setSelectionRange:(Q,Z,ne)=>{var oe;(oe=T.current)==null||oe.setSelectionRange(Q,Z,ne)},select:()=>{var Q;(Q=T.current)==null||Q.select()},input:T.current,nativeElement:((U=M.current)==null?void 0:U.nativeElement)||T.current}}),a.useEffect(()=>{P.current&&(P.current=!1),w(U=>U&&d?!1:U)},[d]);const k=(U,Q,Z)=>{const ne=A(Q,R.current);Z.source==="compositionEnd"&&Q===ne||(B(ne),T.current&&Ef(T.current,U,r,ne))},_=U=>{k(U,U.target.value,{source:"change"})},D=U=>{R.current=!1,k(U,U.currentTarget.value,{source:"compositionEnd"}),N==null||N(U)},V=U=>{i&&U.key==="Enter"&&!P.current&&!U.nativeEvent.isComposing&&(P.current=!0,i(U)),l==null||l(U)},W=U=>{U.key==="Enter"&&(P.current=!1),c==null||c(U)},K=U=>{w(!0),o==null||o(U)},q=U=>{P.current&&(P.current=!1),w(!1),s==null||s(U)},Y=U=>{B(""),z(),T.current&&Ef(T.current,U,r)},ee=j&&`${u}-out-of-range`,ie=()=>{const U=Dt(e,["prefixCls","onPressEnter","addonBefore","addonAfter","prefix","suffix","allowClear","defaultValue","showCount","count","classes","htmlSize","styles","classNames","onClear"]);return J.createElement("input",If({autoComplete:n},U,{onChange:_,onFocus:K,onBlur:q,onKeyDown:V,onKeyUp:W,className:H(u,{[`${u}-disabled`]:d},h==null?void 0:h.input),style:$==null?void 0:$.input,ref:T,size:m,type:v,onCompositionStart:Q=>{R.current=!0,C==null||C(Q)},onCompositionEnd:D}))},ae=()=>y||L.show?J.createElement(J.Fragment,null,L.show&&J.createElement("span",{className:H(`${u}-show-count-suffix`,{[`${u}-show-count-has-suffix`]:!!y},h==null?void 0:h.count),style:{...$==null?void 0:$.count}},O),y):null;return J.createElement(IT,If({},S,{prefixCls:u,className:H(f,ee),handleReset:Y,value:F,focused:E,triggerFocus:z,suffix:ae(),disabled:d,classes:g,classNames:h,styles:$,ref:M}),ie())}),RV=` + min-height:0 !important; + max-height:none !important; + height:0 !important; + visibility:hidden !important; + overflow:hidden !important; + position:absolute !important; + z-index:-1000 !important; + top:0 !important; + right:0 !important; + pointer-events: none !important; +`,TV=["letter-spacing","line-height","padding-top","padding-bottom","font-family","font-weight","font-size","font-variant","text-rendering","text-transform","width","text-indent","padding-left","padding-right","border-width","box-sizing","word-break","white-space"],mg={};let Ir;function MV(e,t=!1){const n=e.getAttribute("id")||e.getAttribute("data-reactid")||e.getAttribute("name");if(t&&mg[n])return mg[n];const r=window.getComputedStyle(e),o=r.getPropertyValue("box-sizing")||r.getPropertyValue("-moz-box-sizing")||r.getPropertyValue("-webkit-box-sizing"),s=parseFloat(r.getPropertyValue("padding-bottom"))+parseFloat(r.getPropertyValue("padding-top")),i=parseFloat(r.getPropertyValue("border-bottom-width"))+parseFloat(r.getPropertyValue("border-top-width")),c={sizingStyle:TV.map(u=>`${u}:${r.getPropertyValue(u)}`).join(";"),paddingSize:s,borderSize:i,boxSizing:o};return t&&n&&(mg[n]=c),c}function OV(e,t=!1,n=null,r=null){Ir||(Ir=document.createElement("textarea"),Ir.setAttribute("tab-index","-1"),Ir.setAttribute("aria-hidden","true"),Ir.setAttribute("name","hiddenTextarea"),document.body.appendChild(Ir)),e.getAttribute("wrap")?Ir.setAttribute("wrap",e.getAttribute("wrap")):Ir.removeAttribute("wrap");const{paddingSize:o,borderSize:s,boxSizing:i,sizingStyle:l}=MV(e,t);Ir.setAttribute("style",`${l};${RV}`),Ir.value=e.value||e.placeholder||"";let c,u,d,m=Ir.scrollHeight;if(i==="border-box"?m+=s:i==="content-box"&&(m-=o),n!==null||r!==null){Ir.value=" ";const p=Ir.scrollHeight-o;n!==null&&(c=p*n,i==="border-box"&&(c=c+o+s),m=Math.max(c,m)),r!==null&&(u=p*r,i==="border-box"&&(u=u+o+s),d=m>u?void 0:"hidden",m=Math.min(u,m))}const f={height:m,overflowY:d,resize:"none"};return c&&(f.minHeight=c),u&&(f.maxHeight=u),f}function _v(){return _v=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{prefixCls:n,defaultValue:r,value:o,autoSize:s,onResize:i,className:l,style:c,disabled:u,onChange:d,onInternalAutoSize:m,...f}=e,[p,y]=nn(r,o),b=p??"",x=B=>{y(B.target.value),d==null||d(B)},v=a.useRef(null);a.useImperativeHandle(t,()=>({textArea:v.current}));const[g,h]=a.useMemo(()=>s&&typeof s=="object"?[s.minRows,s.maxRows]:[],[s]),$=!!s,[C,N]=a.useState(hg),[S,E]=a.useState(),w=()=>{N(pg)};It(()=>{$&&w()},[o,g,h,$]),It(()=>{if(C===pg)N(gg);else if(C===gg){const B=OV(v.current,!1,g,h);N(hg),E(B)}},[C]);const R=a.useRef(void 0),P=()=>{R.current!==void 0&&Ct.cancel(R.current)},T=B=>{C===hg&&(i==null||i(B),s&&(P(),R.current=Ct(()=>{w()})))};a.useEffect(()=>P,[]);const z={...c,...$?S:null};return(C===pg||C===gg)&&(z.overflowY="hidden",z.overflowX="hidden"),a.createElement(ir,{onResize:T,disabled:!(s||i)},a.createElement("textarea",_v({},f,{ref:v,style:z,className:H(n,l,{[`${n}-disabled`]:u}),disabled:u,value:b,onChange:x})))});function zv(){return zv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const[P,T]=J.useState(!1),M=J.useRef(!1),[z,B]=J.useState(null),F=a.useRef(null),L=a.useRef(null),j=()=>{var le;return((le=L.current)==null?void 0:le.textArea)||null},{setValue:O,formatValue:A}=TT(e,t),k=PT(f,m),{isOutOfRange:_,dataCount:D}=NT({countConfig:k,value:A,maxLength:i}),V=RT({countConfig:k,getTarget:()=>{var le;return((le=L.current)==null?void 0:le.textArea)||null}}),W=()=>{var le;(le=j())==null||le.focus()};a.useImperativeHandle(R,()=>{var le;return{resizableTextArea:L.current,focus:W,blur:()=>{var re;(re=j())==null||re.blur()},nativeElement:((le=F.current)==null?void 0:le.nativeElement)||j()}}),a.useEffect(()=>{T(le=>!b&&le)},[b]);const K=(le,re)=>{const X=V(re,M.current);O(X),Ef(le.currentTarget,le,o,X)},q=le=>{M.current=!0,l==null||l(le)},Y=le=>{M.current=!1,K(le,le.currentTarget.value),c==null||c(le)},ee=le=>{K(le,le.target.value)},ie=le=>{le.key==="Enter"&&C&&!le.nativeEvent.isComposing&&C(le),E==null||E(le)},ae=le=>{T(!0),n==null||n(le)},U=le=>{T(!1),r==null||r(le)},Q=le=>{O(""),W();const re=j();re&&Ef(re,le,o)};let Z=u;k.show&&(Z=J.createElement(J.Fragment,null,Z,J.createElement("span",{className:H(`${d}-data-count`,v==null?void 0:v.count),style:g==null?void 0:g.count},D)));const ne=le=>{var re;h==null||h(le),(re=j())!=null&&re.style.height&&B(!0)},oe=!S&&!m&&!s;return J.createElement(IT,{ref:F,value:A,allowClear:s,handleReset:Q,suffix:Z,prefixCls:d,classNames:{...v,affixWrapper:H(v==null?void 0:v.affixWrapper,{[`${d}-show-count`]:m,[`${d}-textarea-allow-clear`]:s})},disabled:b,focused:P,className:H(p,_&&`${d}-out-of-range`),style:{...y,...z&&!oe?{height:"auto"}:{}},dataAttrs:typeof D=="string"?{affixWrapper:{"data-count":D}}:void 0,styles:g,hidden:x,readOnly:N,onClear:$},J.createElement(_V,zv({},w,{autoSize:S,maxLength:i,onKeyDown:ie,onChange:ee,onFocus:ae,onBlur:U,onCompositionStart:q,onCompositionEnd:Y,className:H(v==null?void 0:v.textarea),style:{resize:y==null?void 0:y.resize,...g==null?void 0:g.textarea},disabled:b,prefixCls:d,onResize:ne,ref:L,readOnly:N})))});function MT(e,t){const n=a.useRef([]),r=()=>{n.current.push(setTimeout(()=>{var o,s,i,l;(o=e.current)!=null&&o.input&&((s=e.current)==null?void 0:s.input.getAttribute("type"))==="password"&&((i=e.current)!=null&&i.input.hasAttribute("value"))&&((l=e.current)==null||l.input.removeAttribute("value"))}))};return a.useEffect(()=>(t&&r(),()=>n.current.forEach(o=>{o&&clearTimeout(o)})),[t]),r}function jV(e){return!!(e.prefix||e.suffix||e.allowClear||e.showCount)}const au=a.forwardRef((e,t)=>{const{prefixCls:n,bordered:r=!0,status:o,size:s,disabled:i,onBlur:l,onFocus:c,suffix:u,allowClear:d,addonAfter:m,addonBefore:f,className:p,style:y,styles:b,rootClassName:x,onChange:v,classNames:g,variant:h,...$}=e,{getPrefixCls:C,direction:N,allowClear:S,autoComplete:E,className:w,style:R,classNames:P,styles:T}=Pt("input"),M=C("input",n),z=a.useRef(null),B=on(M),[F,L]=tT(M,x);nT(M,B);const{compactSize:j,compactItemClassnames:O}=qs(M,N),A=Cn(ge=>s??j??ge),k=J.useContext(cr),_=i??k,D={...e,size:A,disabled:_},V=Mt(R),W=Mt(y),[K,q]=Ot([P,g],[T,V,b,W],{props:D}),{status:Y,hasFeedback:ee,feedbackIcon:ie}=a.useContext(Hn),ae=nu(Y,o),U=jV(e)||!!ee;a.useRef(U);const Q=MT(z,!0),Z=ge=>{Q(),l==null||l(ge)},ne=ge=>{Q(),c==null||c(ge)},oe=ge=>{Q(),v==null||v(ge)},le=(ee||u)&&J.createElement(J.Fragment,null,u,ee&&ie),re=EN({allowClear:d,contextAllowClear:S,componentName:"Input"}),[X,se]=tl("input",h,r);return J.createElement(NV,{ref:Tn(t,z),prefixCls:M,autoComplete:E,...$,disabled:_,onBlur:Z,onFocus:ne,style:q.root,styles:q,suffix:le,allowClear:re,className:H(p,x,L,B,O,w,K.root),onChange:oe,addonBefore:f&&J.createElement(Ri,{form:!0,space:!0},f),addonAfter:m&&J.createElement(Ri,{form:!0,space:!0},m),classNames:{...K,input:H({[`${M}-sm`]:A==="small",[`${M}-lg`]:A==="large",[`${M}-rtl`]:N==="rtl"},K.input,F),variant:H({[`${M}-${X}`]:se},qa(M,ae)),affixWrapper:H({[`${M}-affix-wrapper-sm`]:A==="small",[`${M}-affix-wrapper-lg`]:A==="large",[`${M}-affix-wrapper-rtl`]:N==="rtl"},F),wrapper:H({[`${M}-group-rtl`]:N==="rtl"},F),groupWrapper:H({[`${M}-group-wrapper-sm`]:A==="small",[`${M}-group-wrapper-lg`]:A==="large",[`${M}-group-wrapper-rtl`]:N==="rtl",[`${M}-group-wrapper-${X}`]:se},qa(`${M}-group-wrapper`,ae,ee),F)}})});function $w(e){return["small","middle","medium","large"].includes(e)}function Sw(e){return e?Rn(e):!1}const OT=J.createContext({latestIndex:0}),BV=OT.Provider,LV=e=>{const{className:t,prefix:n,index:r,children:o,separator:s,style:i,classNames:l,styles:c}=e,{latestIndex:u}=a.useContext(OT);return $n(o)?a.createElement(a.Fragment,null,a.createElement("div",{className:t,style:i},o),r{const{componentCls:t,antCls:n}=e;return{[t]:{display:"inline-flex","&-rtl":{direction:"rtl"},"&-vertical":{flexDirection:"column"},"&-align":{flexDirection:"column","&-center":{alignItems:"center"},"&-start":{alignItems:"flex-start"},"&-end":{alignItems:"flex-end"},"&-baseline":{alignItems:"baseline"}},[`${t}-item:empty`]:{display:"none"},[`${t}-item > ${n}-badge-not-a-wrapper:only-child`]:{display:"block"}}}},AV=e=>{const{componentCls:t}=e;return{[t]:{"&-gap-row-small":{rowGap:e.spaceGapSmallSize},"&-gap-row-medium, &-gap-row-middle":{rowGap:e.spaceGapMiddleSize},"&-gap-row-large":{rowGap:e.spaceGapLargeSize},"&-gap-col-small":{columnGap:e.spaceGapSmallSize},"&-gap-col-medium, &-gap-col-middle":{columnGap:e.spaceGapMiddleSize},"&-gap-col-large":{columnGap:e.spaceGapLargeSize}}}},DV=Tt("Space",e=>{const t=Rt(e,{spaceGapSmallSize:e.paddingXS,spaceGapMiddleSize:e.padding,spaceGapLargeSize:e.paddingLG});return[kV(t),AV(t)]},()=>({}),{resetStyle:!1}),FV=a.forwardRef((e,t)=>{const{getPrefixCls:n,direction:r,size:o,className:s,style:i,classNames:l,styles:c}=Pt("space"),{size:u=o??"small",align:d,className:m,rootClassName:f,children:p,direction:y,orientation:b,prefixCls:x,split:v,separator:g,style:h,vertical:$,wrap:C=!1,classNames:N,styles:S,...E}=e,[w,R]=Array.isArray(u)?u:[u,u],P=$w(R),T=$w(w),M=Sw(R),z=Sw(w),B=zn(p,{keepEmpty:!0}),[F,L]=Gc(b,$,y),j=d===void 0&&!L?"center":d,O=g??v,A=n("space",x),[k,_]=DV(A),D={...e,size:u,orientation:F,align:j},[V,W]=Ot([l,N],[c,S],{props:D}),K=H(A,s,k,`${A}-${F}`,{[`${A}-rtl`]:r==="rtl",[`${A}-align-${j}`]:j,[`${A}-gap-row-${R}`]:P,[`${A}-gap-col-${w}`]:T},m,f,_,V.root),q=H(`${A}-item`,V.item),Y=B.map((ae,U)=>{const Q=(ae==null?void 0:ae.key)||`${q}-${U}`;return a.createElement(LV,{prefix:A,classNames:V,styles:W,className:q,key:Q,index:U,separator:O,style:W.item},ae)}),ee=a.useMemo(()=>({latestIndex:B.reduce((U,Q,Z)=>$n(Q)?Z:U,0)}),[B]);if(B.length===0)return null;const ie={};return C&&(ie.flexWrap="wrap"),!T&&z&&(ie.columnGap=w),!P&&M&&(ie.rowGap=R),a.createElement("div",{ref:t,className:K,style:{...ie,...W.root,...i,...h},...E},a.createElement(BV,{value:ee},Y))}),Vt=FV;Vt.Compact=mx;Vt.Addon=wT;const _T=e=>{const{getPopupContainer:t,getPrefixCls:n,direction:r}=a.useContext(ct),{prefixCls:o,type:s="default",danger:i,disabled:l,loading:c,onClick:u,htmlType:d,children:m,className:f,menu:p,arrow:y,autoFocus:b,trigger:x,align:v,open:g,onOpenChange:h,placement:$,getPopupContainer:C,href:N,icon:S=a.createElement(Bm,null),title:E,buttonsRender:w=ie=>ie,mouseEnterDelay:R,mouseLeaveDelay:P,overlayClassName:T,overlayStyle:M,destroyOnHidden:z,destroyPopupOnHide:B,dropdownRender:F,popupRender:L,...j}=e,O=n("dropdown",o),A=`${O}-button`,_={menu:p,arrow:y,autoFocus:b,align:v,disabled:l,trigger:l?[]:x,onOpenChange:h,getPopupContainer:C||t,mouseEnterDelay:R,mouseLeaveDelay:P,classNames:{root:T},styles:{root:M},destroyOnHidden:z,popupRender:L||F},{compactSize:D,compactItemClassnames:V}=qs(O,r),W=H(A,V,f);"destroyPopupOnHide"in e&&(_.destroyPopupOnHide=B),"open"in e&&(_.open=g),"placement"in e?_.placement=$:_.placement=r==="rtl"?"bottomLeft":"bottomRight";const K=a.createElement(Xe,{type:s,danger:i,disabled:l,loading:c,onClick:u,htmlType:d,href:N,title:E},m),q=a.createElement(Xe,{type:s,danger:i,icon:S}),[Y,ee]=w([K,q]);return a.createElement(Vt.Compact,{className:W,size:D,block:!0,...j},Y,a.createElement(Lm,{..._},ee))};_T.__ANT_BUTTON=!0;const Jx=Lm;Jx.Button=_T;const HV=(e,t)=>$n(e)?dt(e)&&!a.isValidElement(e)?{...t,...e}:{...t,title:e}:null;function Pf(e){const[t,n]=a.useState(e);return a.useEffect(()=>{const r=setTimeout(()=>{n(e)},e.length?0:10);return()=>{clearTimeout(r)}},[e]),t}const VV=e=>{const{componentCls:t,motionDurationFast:n,motionEaseInOut:r}=e,o=`${t}-show-help`,s=`${t}-show-help-item`;return{[o]:{transition:`opacity ${n} ${r}`,"&-appear, &-enter":{opacity:0,"&-active":{opacity:1}},"&-leave":{opacity:1,"&-active":{opacity:0}},[s]:{overflow:"hidden",transition:`${["height","opacity","transform"].map(i=>`${i} ${n} ${r}`).join(", ")} !important`,[`&${s}-appear, &${s}-enter`]:{transform:"translateY(-5px)",opacity:0,"&-active":{transform:"translateY(0)",opacity:1}},[`&${s}-leave-active`]:{transform:"translateY(-5px)"}}}}},WV=e=>({legend:{display:"block",width:"100%",marginBottom:e.marginLG,padding:0,color:e.colorTextDescription,fontSize:e.fontSizeLG,lineHeight:"inherit",border:0,borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`},'input[type="search"]':{boxSizing:"border-box"},'input[type="radio"], input[type="checkbox"]':{lineHeight:"normal"},'input[type="file"]':{display:"block"},'input[type="range"]':{display:"block",width:"100%"},"select[multiple], select[size]":{height:"auto"},"input[type='file']:focus, input[type='radio']:focus, input[type='checkbox']:focus":{outline:0,boxShadow:`0 0 0 ${G(e.controlOutlineWidth)} ${e.controlOutline}`},output:{display:"block",paddingTop:15,color:e.colorText,fontSize:e.fontSize,lineHeight:e.lineHeight}}),Cw=(e,t)=>{const{formItemCls:n}=e;return{[n]:{[`${n}-label > label`]:{height:t},[`${n}-control-input`]:{minHeight:t}}}},KV=e=>{const{componentCls:t}=e;return{[t]:{...Ft(e),...WV(e),[`${t}-text`]:{display:"inline-block",paddingInlineEnd:e.paddingSM},"&-small":{...Cw(e,e.controlHeightSM)},"&-large":{...Cw(e,e.controlHeightLG)}}}},UV=e=>{const{formItemCls:t,iconCls:n,rootPrefixCls:r,antCls:o,labelRequiredMarkColor:s,labelColor:i,labelFontSize:l,labelHeight:c,labelColonMarginInlineStart:u,labelColonMarginInlineEnd:d,itemMarginBottom:m}=e,[f]=rn(o,"grid");return{[t]:{...Ft(e),marginBottom:m,verticalAlign:"top","&-with-help":{transition:"none"},[`&-hidden, + &-hidden${o}-row`]:{display:"none"},[`${t}-label`]:{flexGrow:0,overflow:"hidden",whiteSpace:"nowrap",textAlign:"end",verticalAlign:"middle","&-left":{textAlign:"start"},"&-wrap":{overflow:"unset",lineHeight:e.lineHeight,whiteSpace:"unset","> label":{verticalAlign:"middle",textWrap:"balance"}},"> label":{position:"relative",display:"inline-flex",alignItems:"center",maxWidth:"100%",height:c,color:i,fontSize:l,[`> ${n}`]:{fontSize:e.fontSize,verticalAlign:"top"},[`&${t}-required`]:{"&::before":{display:"inline-block",marginInlineEnd:e.marginXXS,color:s,fontSize:e.fontSize,fontFamily:"sans-serif",lineHeight:1,content:'"*"'},[`&${t}-required-mark-hidden, &${t}-required-mark-optional`]:{"&::before":{display:"none"}}},[`${t}-optional`]:{display:"inline-block",marginInlineStart:e.marginXXS,color:e.colorTextDescription,[`&${t}-required-mark-hidden`]:{display:"none"}},[`${t}-tooltip`]:{color:e.colorTextDescription,cursor:"help",writingMode:"horizontal-tb",marginInlineStart:e.marginXXS},"&::after":{content:'":"',position:"relative",marginBlock:0,marginInlineStart:u,marginInlineEnd:d},[`&${t}-no-colon::after`]:{content:'"\\a0"'}}},[`${t}-control`]:{[f("display")]:"flex",flexDirection:"column",flexGrow:1,[`&:first-child:not([class^="'${r}-col-'"]):not([class*="' ${r}-col-'"])`]:{width:"100%"},"&-input":{position:"relative",display:"flex",alignItems:"center",minHeight:e.controlHeight,"&-content":{flex:"auto",maxWidth:"100%",[`&:has(> ${o}-switch:only-child, > ${o}-rate:only-child)`]:{display:"flex",alignItems:"center"}}}},[t]:{"&-additional":{display:"flex",flexDirection:"column"},"&-explain, &-extra":{clear:"both",color:e.colorTextDescription,fontSize:e.fontSize,lineHeight:e.lineHeight},"&-explain-connected":{width:"100%"},"&-extra":{minHeight:e.controlHeightSM,transition:`color ${e.motionDurationMid} ${e.motionEaseOut}`},"&-explain":{"&-error":{color:e.colorError},"&-warning":{color:e.colorWarning}}},[`&-with-help ${t}-explain`]:{height:"auto",opacity:1},[`${t}-feedback-icon`]:{fontSize:e.fontSize,textAlign:"center",visibility:"visible",animationName:hx,animationDuration:e.motionDurationMid,animationTimingFunction:e.motionEaseOutBack,pointerEvents:"none","&-success":{color:e.colorSuccess},"&-error":{color:e.colorError},"&-warning":{color:e.colorWarning},"&-validating":{color:e.colorPrimary}}}}},ai=e=>({padding:e.verticalLabelPadding,margin:e.verticalLabelMargin,whiteSpace:"initial",textAlign:"start","> label":{margin:0,"&::after":{visibility:"hidden"}}}),qV=e=>{const{antCls:t,formItemCls:n}=e;return{[`${n}-horizontal`]:{[`${n}-label`]:{flexGrow:0},[`${n}-control`]:{flex:"1 1 0",minWidth:0},[`${n}-label[class$='-24'], ${n}-label[class*='-24 ']`]:{[`& + ${n}-control`]:{minWidth:"unset"}},[`${t}-col-24${n}-label, + ${t}-col-xl-24${n}-label`]:ai(e)}}},GV=e=>{const{componentCls:t,formItemCls:n,inlineItemMarginBottom:r}=e;return{[`${t}-inline`]:{display:"flex",flexWrap:"wrap",[`${n}-inline`]:{flex:"none",marginInlineEnd:e.margin,marginBottom:r,"&-row":{flexWrap:"nowrap"},[`> ${n}-label, + > ${n}-control`]:{display:"inline-block",verticalAlign:"top"},[`> ${n}-label`]:{flex:"none"},[`${t}-text`]:{display:"inline-block"},[`${n}-has-feedback`]:{display:"inline-block"}}}}},XV=e=>{const{componentCls:t,formItemCls:n,rootPrefixCls:r}=e;return{[`${n} ${n}-label`]:ai(e),[`${t}:not(${t}-inline)`]:{[n]:{flexWrap:"wrap",[`${n}-label, ${n}-control`]:{[`&:not([class*=" ${r}-col-xs"])`]:{flex:"0 0 100%",maxWidth:"100%"}}}}}},YV=e=>{const{componentCls:t,formItemCls:n,antCls:r,verticalLabelHeight:o}=e;return{[`${n}-vertical`]:{[`${n}-row`]:{flexDirection:"column"},[`${n}-label > label`]:{height:o},[`${n}-control`]:{width:"100%"},[`${n}-label, + ${r}-col-24${n}-label, + ${r}-col-xl-24${n}-label`]:ai(e)},[`@media (max-width: ${G(e.screenXSMax)})`]:[XV(e),{[t]:{[`${n}:not(${n}-horizontal)`]:{[`${r}-col-xs-24${n}-label`]:ai(e)}}}],[`@media (max-width: ${G(e.screenSMMax)})`]:{[t]:{[`${n}:not(${n}-horizontal)`]:{[`${r}-col-sm-24${n}-label`]:ai(e)}}},[`@media (max-width: ${G(e.screenMDMax)})`]:{[t]:{[`${n}:not(${n}-horizontal)`]:{[`${r}-col-md-24${n}-label`]:ai(e)}}},[`@media (max-width: ${G(e.screenLGMax)})`]:{[t]:{[`${n}:not(${n}-horizontal)`]:{[`${r}-col-lg-24${n}-label`]:ai(e)}}}}},QV=e=>({labelRequiredMarkColor:e.colorError,labelColor:e.colorTextHeading,labelFontSize:e.fontSize,labelHeight:e.controlHeight,verticalLabelHeight:e.labelHeight??"auto",labelColonMarginInlineStart:e.marginXXS/2,labelColonMarginInlineEnd:e.marginXS,itemMarginBottom:e.marginLG,verticalLabelPadding:`0 0 ${e.paddingXS}px`,verticalLabelMargin:0,inlineItemMarginBottom:0}),zT=(e,t)=>Rt(e,{formItemCls:`${e.componentCls}-item`,rootPrefixCls:t}),Zx=Tt("Form",(e,{rootPrefixCls:t})=>{const n=zT(e,t);return[KV(n),UV(n),VV(n),qV(n),GV(n),YV(n),gx(n),hx]},QV,{order:-1e3}),ww=[];function yg(e,t,n,r=0){return{key:typeof e=="string"?e:`${t}-${r}`,error:e,errorStatus:n}}const jT=({help:e,helpStatus:t,errors:n=ww,warnings:r=ww,className:o,fieldId:s,onVisibleChanged:i})=>{const{prefixCls:l}=a.useContext($x),{classNames:c,styles:u}=a.useContext(_o),d=`${l}-item-explain`,m=on(l),[f,p]=Zx(l,m),y=a.useMemo(()=>cf(l),[l]),b=Pf(n),x=Pf(r),v=bn(e),g=a.useMemo(()=>v?[yg(e,"help",t)]:[].concat($t(b.map((C,N)=>yg(C,"error","error",N))),$t(x.map((C,N)=>yg(C,"warning","warning",N)))),[e,t,v,b,x]),h=a.useMemo(()=>{const C={};return g.forEach(({key:N})=>{C[N]=(C[N]||0)+1}),g.map((N,S)=>({...N,key:C[N.key]>1?`${N.key}-fallback-${S}`:N.key}))},[g]),$={};return s&&($.id=`${s}_help`),a.createElement(fr,{motionDeadline:y.motionDeadline,motionName:`${l}-show-help`,visible:!!h.length,onVisibleChanged:i},C=>{const{className:N,style:S}=C;return a.createElement("div",{...$,className:H(d,N,c==null?void 0:c.help,p,m,o,f),style:{...u==null?void 0:u.help,...S}},a.createElement(KP,{keys:h,...cf(l),motionName:`${l}-show-help-item`,component:!1},E=>{const{key:w,error:R,errorStatus:P,className:T,style:M}=E;return a.createElement("div",{key:w,className:H(T,c==null?void 0:c.helpItem,{[`${d}-${P}`]:P}),style:{...u==null?void 0:u.helpItem,...M}},R)}))})},JV=(e,t)=>{const n=a.useContext(cr),{getPrefixCls:r,direction:o,requiredMark:s,colon:i,scrollToFirstError:l,className:c,style:u,styles:d,classNames:m,tooltip:f,labelAlign:p,labelWrap:y}=Pt("form"),{prefixCls:b,className:x,rootClassName:v,size:g,disabled:h=n,form:$,colon:C,labelAlign:N,labelWrap:S,labelCol:E,wrapperCol:w,layout:R="horizontal",scrollToFirstError:P,requiredMark:T,onFinishFailed:M,name:z,style:B,feedbackIcons:F,variant:L,classNames:j,styles:O,tooltip:A,...k}=e,_=Cn(g),D=a.useContext(XP),V=a.useMemo(()=>T!==void 0?T:s!==void 0?s:!0,[T,s]),W=C??i,K=N??p,q=S??y,Y={...f,...A},ee=r("form",b),ie=on(ee),[ae,U]=Zx(ee,ie),Q={...e,size:_,disabled:h,layout:R,colon:W,requiredMark:V,labelAlign:K,labelWrap:q},Z=Mt(u),ne=Mt(B),[oe,le]=Ot([m,j],[d,Z,O,ne],{props:Q}),re=H(ee,`${ee}-${R}`,{[`${ee}-hide-required-mark`]:V===!1,[`${ee}-rtl`]:o==="rtl",[`${ee}-large`]:_==="large",[`${ee}-small`]:_==="small"},U,ie,ae,c,x,v,oe.root),[X]=D4($),{__INTERNAL__:se}=X;se.name=z;const ge=a.useMemo(()=>({name:z,labelAlign:K,labelCol:E,labelWrap:q,wrapperCol:w,layout:R,colon:W,requiredMark:V,itemRef:se.itemRef,form:X,feedbackIcons:F,tooltip:Y,classNames:oe,styles:le}),[z,K,q,E,w,R,W,V,X,F,oe,le,Y]),de=a.useRef(null);a.useImperativeHandle(t,()=>{var be;return{...X,nativeElement:(be=de.current)==null?void 0:be.nativeElement}});const Se=(be,Ne)=>{if(be){let we={block:"nearest"};dt(be)&&(we={...we,...be}),X.scrollToField(Ne,we)}},ue=be=>{if(M==null||M(be),be.errorFields.length){const Ne=be.errorFields[0].name;if(P!==void 0){Se(P,Ne);return}l!==void 0&&Se(l,Ne)}};return a.createElement(uR.Provider,{value:L},a.createElement(ix,{disabled:h},a.createElement(Pi.Provider,{value:_},a.createElement(lR,{validateMessages:D},a.createElement(_o.Provider,{value:ge},a.createElement(cR,{status:!0},a.createElement(el,{id:z,...k,name:z,onFinishFailed:ue,form:X,ref:de,style:le==null?void 0:le.root,className:re})))))))},ZV=a.forwardRef(JV),eW=e=>{if(bt(e))return e;const t=zn(e);return t.length<=1?t[0]:t},BT=()=>{const{status:e,errors:t=[],warnings:n=[]}=a.useContext(Hn);return{status:e,errors:t,warnings:n}};BT.Context=Hn;function tW(e){const[t,n]=a.useState(e),r=a.useRef(null),o=a.useRef([]),s=a.useRef(!1);a.useEffect(()=>(s.current=!1,()=>{s.current=!0,Ct.cancel(r.current),r.current=null}),[]);function i(l){s.current||(r.current===null&&(o.current=[],r.current=Ct(()=>{r.current=null,n(c=>{let u=c;return o.current.forEach(d=>{u=d(u)}),u})})),o.current.push(l))}return[t,i]}const nW=()=>{const{itemRef:e}=a.useContext(_o),t=a.useRef({});return(r,o)=>{const s=o&&dt(o)&&Bo(o),i=r.join("_");return(t.current.name!==i||t.current.originRef!==s)&&(t.current.name=i,t.current.originRef=s,t.current.ref=Tn(e(r),s)),t.current.ref}},rW=e=>{const{formItemCls:t}=e;return{"@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)":{[`${t}-control`]:{display:"flex"}}}},oW=Ks(["Form","item-item"],(e,{rootPrefixCls:t})=>{const n=zT(e,t);return rW(n)}),sW=24,iW=e=>{const{prefixCls:t,status:n,labelCol:r,wrapperCol:o,children:s,errors:i,warnings:l,_internalItemRender:c,extra:u,help:d,fieldId:m,marginBottom:f,onErrorVisibleChanged:p,label:y}=e,b=`${t}-item`,x=a.useContext(_o),{classNames:v,styles:g}=x,h=a.useMemo(()=>{let F={...o||x.wrapperCol||{}};return y===null&&!r&&!o&&x.labelCol&&[void 0].concat($t(t4)).forEach(j=>{const O=j?[j]:[],A=Kn(x.labelCol,O),k=dt(A)?A:{},_=Kn(F,O),D=dt(_)?_:{};"span"in k&&!("offset"in D)&&k.span{const{labelCol:F,wrapperCol:L,...j}=x;return j},[x]),N=a.useRef(null),[S,E]=a.useState(0);It(()=>{u&&N.current?E(N.current.clientHeight):E(0)},[u]);const w=a.createElement("div",{className:`${b}-control-input`},a.createElement("div",{className:H(`${b}-control-input-content`,v==null?void 0:v.content),style:g==null?void 0:g.content},s)),R=a.useMemo(()=>({prefixCls:t,status:n}),[t,n]),P=f!==null||i.length||l.length?a.createElement($x.Provider,{value:R},a.createElement(jT,{fieldId:m,errors:i,warnings:l,help:d,helpStatus:n,className:`${b}-explain-connected`,onVisibleChanged:p})):null,T={};m&&(T.id=`${m}_extra`);const M=u?a.createElement("div",{...T,className:H(`${b}-extra`,v==null?void 0:v.extra),style:g==null?void 0:g.extra,ref:N},u):null,z=P||M?a.createElement("div",{className:`${b}-additional`,style:f?{minHeight:f+S}:{}},P,M):null,B=c&&c.mark==="pro_table_render"&&c.render?c.render(e,{input:w,errorList:P,extra:M}):a.createElement(a.Fragment,null,w,z);return a.createElement(_o.Provider,{value:C},a.createElement(si,{...h,className:$},B),a.createElement(oW,{prefixCls:t}))};var LT={};Object.defineProperty(LT,"__esModule",{value:!0});var aW={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M623.6 316.7C593.6 290.4 554 276 512 276s-81.6 14.5-111.6 40.7C369.2 344 352 380.7 352 420v7.6c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V420c0-44.1 43.1-80 96-80s96 35.9 96 80c0 31.1-22 59.6-56.1 72.7-21.2 8.1-39.2 22.3-52.1 40.9-13.1 19-19.9 41.8-19.9 64.9V620c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-22.7a48.3 48.3 0 0130.9-44.8c59-22.7 97.1-74.7 97.1-132.5.1-39.3-17.1-76-48.3-103.3zM472 732a40 40 0 1080 0 40 40 0 10-80 0z"}}]},name:"question-circle",theme:"outlined"},lW=LT.default=aW;function jv(){return jv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,jv({},e,{ref:t,icon:lW})),uW=a.forwardRef(cW),dW=({prefixCls:e,label:t,htmlFor:n,labelCol:r,labelAlign:o,colon:s,required:i,requiredMark:l,tooltip:c,vertical:u})=>{var B;const[d]=Ar("Form"),{labelAlign:m,labelCol:f,labelWrap:p,colon:y,classNames:b,styles:x,tooltip:v}=a.useContext(_o);if(!t)return null;const g=r||f||{},h=o||m,$=`${e}-item-label`,C=H($,h==="left"&&`${$}-left`,g.className,{[`${$}-wrap`]:!!p});let N=t;const S=s===!0||y!==!1&&s!==!1;S&&!u&&typeof t=="string"&&t.trim()&&(N=t.replace(/[:|:]\s*$/,""));const w=HV(c,v);if(w){const F=a.createElement(bo,{...w},a.createElement("span",{className:`${e}-item-tooltip`,onClick:L=>{L.preventDefault()},tabIndex:-1},w.icon||w.children||a.createElement(uW,null)));N=a.createElement(a.Fragment,null,N,F)}const R=l==="optional",P=bt(l),T=l===!1;P?N=l(N,{required:!!i}):R&&!i&&(N=a.createElement(a.Fragment,null,N,a.createElement("span",{className:`${e}-item-optional`},(d==null?void 0:d.optional)||((B=Zr.Form)==null?void 0:B.optional))));let M;T?M="hidden":(R||P)&&(M="optional");const z=H(b==null?void 0:b.label,{[`${e}-item-required`]:i,[`${e}-item-required-mark-${M}`]:M,[`${e}-item-no-colon`]:!S});return a.createElement(si,{...g,className:C},a.createElement("label",{htmlFor:n,className:z,style:x==null?void 0:x.label,title:typeof t=="string"?t:void 0},N))},fW={success:Uc,warning:Li,error:Bi,validating:ki};function kT({children:e,errors:t,warnings:n,hasFeedback:r,validateStatus:o,prefixCls:s,meta:i,noStyle:l,name:c}){const u=`${s}-item`,{feedbackIcons:d}=a.useContext(_o),m=A4(t,n,i,null,!!r,o),{isFormItemInput:f,status:p,hasFeedback:y,feedbackIcon:b,name:x}=a.useContext(Hn),v=a.useMemo(()=>{var $;let g;if(r){const C=r!==!0&&r.icons||d,N=m&&(($=C==null?void 0:C({status:m,errors:t,warnings:n}))==null?void 0:$[m]),S=m?fW[m]:null;g=N!==!1&&S?a.createElement("span",{className:H(`${u}-feedback-icon`,`${u}-feedback-icon-${m}`)},N||a.createElement(S,null)):null}const h={status:m||"",errors:t,warnings:n,hasFeedback:!!r,feedbackIcon:g,isFormItemInput:!0,name:c};return l&&(h.status=(m??p)||"",h.isFormItemInput=f,h.hasFeedback=!!(r??y),h.feedbackIcon=r!==void 0?h.feedbackIcon:b,h.name=c??x),h},[m,r,l,f,p,u,y]);return a.createElement(Hn.Provider,{value:v},e)}function mW(e){const{prefixCls:t,className:n,rootClassName:r,style:o,help:s,errors:i,warnings:l,validateStatus:c,meta:u,hasFeedback:d,hidden:m,children:f,fieldId:p,required:y,isRequired:b,onSubItemMetaChange:x,layout:v,name:g,...h}=e,$=`${t}-item`,{requiredMark:C,layout:N}=a.useContext(_o),S=v||N,E=S==="vertical",w=a.useRef(null),R=Pf(i),P=Pf(l),T=bn(s),M=!!(T||i.length||l.length),z=!!w.current&&Vc(w.current),[B,F]=a.useState(null);It(()=>{if(M&&w.current){const k=getComputedStyle(w.current);F(Number.parseInt(k.marginBottom,10))}},[M,z]);const L=k=>{k||F(null)},O=((k=!1)=>{const _=k?R:u.errors,D=k?P:u.warnings;return A4(_,D,u,"",!!d,c)})(),A=H($,n,r,{[`${$}-with-help`]:T||R.length||P.length,[`${$}-has-feedback`]:O&&d,[`${$}-has-success`]:O==="success",[`${$}-has-warning`]:O==="warning",[`${$}-has-error`]:O==="error",[`${$}-is-validating`]:O==="validating",[`${$}-hidden`]:m,[`${$}-${S}`]:S});return a.createElement("div",{className:A,style:o,ref:w},a.createElement(Nv,{className:`${$}-row`,...Dt(h,["_internalItemRender","colon","dependencies","extra","fieldKey","getValueFromEvent","getValueProps","htmlFor","id","initialValue","isListField","label","labelAlign","labelCol","labelWrap","messageVariables","name","normalize","noStyle","preserve","requiredMark","rules","shouldUpdate","trigger","tooltip","validateFirst","validateTrigger","valuePropName","wrapperCol","validateDebounce"])},a.createElement(dW,{htmlFor:p,...e,requiredMark:C,required:y??b,prefixCls:t,vertical:E}),a.createElement(iW,{...e,...u,errors:R,warnings:P,prefixCls:t,status:O,help:s,marginBottom:B,onErrorVisibleChanged:L},a.createElement(aR.Provider,{value:x},a.createElement(kT,{prefixCls:t,meta:u,errors:u.errors,warnings:u.warnings,hasFeedback:d,validateStatus:O,name:g},f)))),!!B&&a.createElement("div",{className:`${$}-margin-offset`,style:{marginBottom:-B}}))}const pW="__SPLIT__";function gW(e,t){const n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every(o=>{const s=e[o],i=t[o];return s===i||bt(s)||bt(i)})}const hW=a.memo(e=>e.children,(e,t)=>gW(e.control,t.control)&&e.update===t.update&&e.childProps.length===t.childProps.length&&e.childProps.every((n,r)=>n===t.childProps[r]));function Ew(){return{errors:[],warnings:[],touched:!1,validating:!1,name:[],validated:!1}}function yW(e){const{name:t,noStyle:n,className:r,dependencies:o,prefixCls:s,shouldUpdate:i,rules:l,children:c,required:u,label:d,messageVariables:m,trigger:f="onChange",validateTrigger:p,hidden:y,help:b,layout:x}=e,{getPrefixCls:v}=a.useContext(ct),{name:g}=a.useContext(_o),h=eW(c),$=bt(h),C=a.useContext(aR),{validateTrigger:N}=a.useContext(Ni),S=bn(p)?p:N,E=bn(t),w=v("form",s),R=on(w),[P,T]=Zx(w,R);yo();const M=a.useContext(Rc),z=a.useRef(null),[B,F]=tW({}),[L,j]=_b(()=>Ew()),O=K=>{const q=M==null?void 0:M.getKey(K.name);if(j(K.destroy?Ew():K,!0),n&&b!==!1&&C){let Y=K.name;if(K.destroy)Y=z.current||Y;else if(q!==void 0){const[ee,ie]=q;Y=[ee].concat($t(ie)),z.current=Y}C(K,Y)}},A=(K,q)=>{F(Y=>{const ee={...Y},ae=[].concat($t(K.name.slice(0,-1)),$t(q)).join(pW);return K.destroy?delete ee[ae]:ee[ae]=K,ee})},[k,_]=a.useMemo(()=>{const K=$t(L.errors),q=$t(L.warnings);return Object.values(B).forEach(Y=>{K.push.apply(K,$t(Y.errors||[])),q.push.apply(q,$t(Y.warnings||[]))}),[K,q]},[B,L.errors,L.warnings]),D=nW();function V(K,q,Y){return n&&!y?a.createElement(kT,{prefixCls:w,hasFeedback:e.hasFeedback,validateStatus:e.validateStatus,meta:L,errors:k,warnings:_,noStyle:!0,name:t},K):a.createElement(mW,{key:"row",...e,className:H(r,T,R,P),prefixCls:w,fieldId:q,isRequired:Y,errors:k,warnings:_,meta:L,onSubItemMetaChange:A,layout:x,name:t},K)}if(!E&&!$&&!o)return V(h);let W={};return typeof d=="string"?W.label=d:t&&(W.label=String(t)),m&&(W={...W,...m}),a.createElement(bx,{...e,messageVariables:W,trigger:f,validateTrigger:S,onMetaChange:O},(K,q,Y)=>{const ee=Xl(t).length&&q?q.name:[],ie=k4(ee,g),ae=u!==void 0?u:l==null?void 0:l.some(Z=>{if(dt(Z)&&Z.required&&!Z.warningOnly)return!0;if(bt(Z)){const ne=Z(Y);return(ne==null?void 0:ne.required)&&!(ne!=null&&ne.warningOnly)}return!1}),U={...K};let Q=null;if(Array.isArray(h)&&E)Q=h;else if(!($&&(!(i||o)||E))){if(!(o&&!$&&!E))if(a.isValidElement(h)){const Z={...h.props,...U};if(Z.id||(Z.id=ie),b||k.length>0||_.length>0||e.extra){const le=[];(b||k.length>0)&&le.push(`${ie}_help`),e.extra&&le.push(`${ie}_extra`),Z["aria-describedby"]=le.join(" ")}k.length>0&&(Z["aria-invalid"]="true"),ae&&(Z["aria-required"]="true"),is(h)&&(Z.ref=D(ee,h)),new Set([].concat($t(Xl(f)),$t(Xl(S)))).forEach(le=>{Z[le]=(...re)=>{var X,se,ge;(X=U[le])==null||X.call(U,...re),(ge=(se=h.props)[le])==null||ge.call(se,...re)}});const oe=[Z["aria-required"],Z["aria-invalid"],Z["aria-describedby"]];Q=a.createElement(hW,{control:U,update:h,childProps:oe},Fn(h,Z))}else $&&(i||o)&&!E?Q=h(Y):Q=h}return V(Q,ie,ae)})}const AT=yW;AT.useStatus=BT;const vW=({prefixCls:e,children:t,...n})=>{const{getPrefixCls:r}=a.useContext(ct),o=r("form",e),s=a.useMemo(()=>({prefixCls:o,status:"error"}),[o]);return a.createElement(oR,{...n},(i,l,c)=>a.createElement($x.Provider,{value:s},t(i.map(u=>({...u,fieldKey:u.key})),l,{errors:c.errors,warnings:c.warnings})))};function bW(){const{form:e}=a.useContext(_o);return e}const We=ZV;We.Item=AT;We.List=vW;We.ErrorList=jT;We.useForm=D4;We.useFormInstance=bW;We.useWatch=iR;We.Provider=lR;const xW=e=>{const{getPrefixCls:t,direction:n}=a.useContext(ct),{prefixCls:r,className:o}=e,s=t("input-group",r),i=t("input"),[l,c]=nT(i),u=H(s,c,{[`${s}-lg`]:e.size==="large",[`${s}-sm`]:e.size==="small",[`${s}-compact`]:e.compact,[`${s}-rtl`]:n==="rtl"},l,o),d=a.useContext(Hn),m=a.useMemo(()=>({...d,isFormItemInput:!1}),[d]);return a.createElement(Hn.Provider,{value:m},a.createElement(Vt.Compact,{className:u,style:e.style,onMouseEnter:e.onMouseEnter,onMouseLeave:e.onMouseLeave,onFocus:e.onFocus,onBlur:e.onBlur},e.children))},$W=e=>{const{componentCls:t,paddingXS:n}=e;return{[t]:{display:"inline-flex",alignItems:"center",flexWrap:"nowrap",columnGap:n,[`${t}-input-wrapper`]:{position:"relative",[`${t}-mask-icon`]:{position:"absolute",zIndex:"1",top:"50%",right:"50%",transform:"translate(50%, -50%)",pointerEvents:"none"},[`${t}-mask-input`]:{color:"transparent",caretColor:e.colorText,"&::selection":{color:"transparent"}},[`${t}-mask-input[type=number]::-webkit-inner-spin-button`]:{"-webkit-appearance":"none",margin:0},[`${t}-mask-input[type=number]`]:{"-moz-appearance":"textfield"}},"&-rtl":{direction:"rtl"},[`${t}-input`]:{textAlign:"center",paddingInline:e.paddingXXS},[`&${t}-sm ${t}-input`]:{paddingInline:e.calc(e.paddingXXS).div(2).equal()},[`&${t}-lg ${t}-input`]:{paddingInline:e.paddingXS}}}},SW=Tt(["Input","OTP"],e=>{const t=Rt(e,Hi(e));return $W(t)},Vi),CW=a.forwardRef((e,t)=>{const{className:n,value:r,onChange:o,onActiveChange:s,index:i,mask:l,onFocus:c,...u}=e,{getPrefixCls:d}=a.useContext(ct),m=d("otp"),f=typeof l=="string"?l:r,p=a.useRef(null);a.useImperativeHandle(t,()=>p.current);const y=g=>{o(i,g.target.value)},b=()=>{Ct(()=>{var h;const g=(h=p.current)==null?void 0:h.input;document.activeElement===g&&g&&g.select()})},x=g=>{c==null||c(g),b()},v=g=>{const{key:h,ctrlKey:$,metaKey:C}=g;h==="ArrowLeft"?s(i-1):h==="ArrowRight"?s(i+1):h==="z"&&($||C)?g.preventDefault():h==="Backspace"&&!r&&s(i-1),b()};return a.createElement("span",{className:`${m}-input-wrapper`,role:"presentation"},l&&r!==""&&r!==void 0&&a.createElement("span",{className:`${m}-mask-icon`,"aria-hidden":"true"},f),a.createElement(au,{"aria-label":`OTP Input ${i+1}`,type:l===!0?"password":"text",...u,ref:p,value:r,onInput:y,onFocus:x,onKeyDown:v,onMouseDown:b,onMouseUp:b,className:H(n,{[`${m}-mask-input`]:l})}))});function Ku(e){return(e||"").split("")}const wW=e=>{const{index:t,prefixCls:n,separator:r,className:o,style:s}=e,i=bt(r)?r(t):r;return i?a.createElement("span",{className:H(`${n}-separator`,o),style:s},i):null},EW=a.forwardRef((e,t)=>{const{prefixCls:n,length:r=6,size:o,defaultValue:s,value:i,onChange:l,formatter:c,separator:u,variant:d,disabled:m,status:f,autoFocus:p,mask:y,type:b,autoComplete:x,onInput:v,onFocus:g,inputMode:h,classNames:$,styles:C,className:N,style:S,...E}=e,{classNames:w,styles:R,getPrefixCls:P,direction:T,style:M,className:z}=Pt("otp"),B=P("otp",n),F={...e,length:r},L=Mt(M),j=Mt(S),[O,A]=Ot([w,$],[R,L,C,j],{props:F}),k=Nn(E,{aria:!0,data:!0,attr:!0}),[_,D]=SW(B),V=Cn(X=>o??X),W=a.useContext(Hn),K=nu(W.status,f),q=a.useMemo(()=>({...W,status:K,hasFeedback:!1,feedbackIcon:null}),[W,K]),Y=a.useRef(null),ee=a.useRef({});a.useImperativeHandle(t,()=>({focus:()=>{var X;(X=ee.current[0])==null||X.focus()},blur:()=>{var X;for(let se=0;sec?c(X):X,[ae,U]=a.useState(()=>Ku(ie(s||"")));a.useEffect(()=>{i!==void 0&&U(Ku(i))},[i]);const Q=vt(X=>{U(X),v&&v(X),l&&X.length===r&&X.every(se=>se)&&X.some((se,ge)=>ae[ge]!==se)&&l(X.join(""))}),Z=vt((X,se)=>{let ge=$t(ae);for(let Se=0;Se=0&&!ge[Se];Se-=1)ge.pop();const de=ie(ge.map(Se=>Se||" ").join(""));return ge=Ku(de).map((Se,ue)=>Se===" "&&!ge[ue]?ge[ue]:Se),ge}),ne=(X,se)=>{var Se;const ge=Z(X,se),de=Math.min(X+se.length,r-1);de!==X&&ge[X]!==void 0&&((Se=ee.current[de])==null||Se.focus()),Q(ge)},oe=X=>{var se;(se=ee.current[X])==null||se.focus()},le=(X,se)=>{var ge,de,Se;for(let ue=0;ue{const ge=`otp-${se}`,de=ae[se]||"";return a.createElement(a.Fragment,{key:ge},a.createElement(CW,{ref:Se=>{ee.current[se]=Se},index:se,size:V,htmlSize:1,className:H(O.input,`${B}-input`),style:A.input,onChange:ne,value:de,onActiveChange:oe,autoFocus:se===0&&p,onFocus:Se=>le(Se,se),...re}),sea.createElement(gt,Bv({},e,{ref:t,icon:PW})),RW=a.forwardRef(NW);var FT={};Object.defineProperty(FT,"__esModule",{value:!0});var TW={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"}}]},name:"eye",theme:"outlined"},MW=FT.default=TW;function Lv(){return Lv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Lv({},e,{ref:t,icon:MW})),_W=a.forwardRef(OW),zW=e=>e?a.createElement(_W,null):a.createElement(RW,null),jW={click:"onClick",hover:"onMouseOver"},BW=a.forwardRef((e,t)=>{const{disabled:n,action:r="click",visibilityToggle:o=!0,iconRender:s,prefixCls:i,inputPrefixCls:l,suffix:c,className:u,style:d,classNames:m,styles:f,...p}=e,{getPrefixCls:y,className:b,style:x,classNames:v,styles:g,iconRender:h}=Pt("inputPassword"),[$]=Ar("global"),C=a.useContext(cr),N=n??C,S={...e,disabled:N},[E,w]=Ot([v,m],[g,f],{props:S}),R=dt(o)&&o.visible!==void 0,[P,T]=a.useState(()=>R?o.visible:!1),M=a.useRef(null);a.useEffect(()=>{R&&T(o.visible)},[R,o]);const z=MT(M),B=()=>{var D;if(N)return;P&&z();const _=!P;T(_),dt(o)&&((D=o.onVisibleChange)==null||D.call(o,_))},F=_=>{const D=jW[r]||"",W=(s||h||zW)(P),K=dt(o)?o.tabIndex:void 0;return a.createElement("span",{key:"passwordIcon",role:"button",tabIndex:N?-1:K??0,className:`${_}-icon`,"aria-disabled":N,"aria-pressed":P,"aria-label":P?$.hide:$.show,onMouseDown:q=>{q.preventDefault()},onMouseUp:q=>{q.preventDefault()},onKeyDown:q=>{(q.key==="Enter"||q.key===" ")&&(q.preventDefault(),B())},[D]:B},W)},L=y("input",l),j=y("input-password",i),O=o&&F(j),A=H(j,b,u,{[`${j}-${e.size}`]:!!e.size}),k={...p,type:P?"text":"password",prefixCls:L,suffix:a.createElement(a.Fragment,null,O,c),disabled:N,className:A,style:{...x,...d},classNames:E,styles:w};return a.createElement(au,{ref:Tn(t,M),...k})}),LW=e=>{const{componentCls:t,antCls:n,calc:r,max:o}=e,s=`${t}-btn`,[i,l]=rn(n,"input-search"),c=e.inputFontSizeSM??e.fontSize,u=o(e.controlHeightSM,r(c).mul(e.lineHeight).add(r(e.paddingBlockSM).mul(2)).add(r(e.lineWidth).mul(2)).equal());return{[t]:{[i("btn-height")]:G(e.controlHeight),width:"100%",[s]:{height:l("btn-height"),"&:focus-visible":{zIndex:5},[`&${n}-btn-icon-only`]:{width:l("btn-height")},"&-filled":{background:e.colorFillTertiary,"&:not(:disabled)":{"&:hover":{background:e.colorFillSecondary},"&:active":{background:e.colorFill}}}},[`&${t}-large`]:{[i("btn-height")]:G(e.controlHeightLG)},[`&${t}-small`]:{[i("btn-height")]:G(e.controlHeightSM)},[`&${t}-small ${s}`]:{minHeight:u,[`&${e.antCls}-btn-icon-only`]:{minWidth:u}}}}},kW=Tt(["Input","Search"],e=>{const t=Rt(e,Hi(e));return LW(t)},Vi),AW=a.forwardRef((e,t)=>{const{prefixCls:n,inputPrefixCls:r,className:o,size:s,style:i,enterButton:l=!1,searchIcon:c,addonAfter:u,loading:d,disabled:m,onSearch:f,onChange:p,onCompositionStart:y,onCompositionEnd:b,variant:x,onPressEnter:v,classNames:g,styles:h,hidden:$,...C}=e,{direction:N,getPrefixCls:S,className:E,style:w,classNames:R,styles:P,searchIcon:T}=Pt("inputSearch"),M={...e,enterButton:l},[z,B]=Ot([R,g],[P,h],{props:M},{button:{_default:"root"}}),F=a.useRef(!1),L=S("input-search",n),j=S("input",r),[O,A]=kW(L),{compactSize:k}=qs(L,N),_=Cn(X=>s??k??X),D=a.useRef(null),V=X=>{X!=null&&X.target&&X.type==="click"&&f&&f(X.target.value,X,{source:"clear"}),p==null||p(X)},W=X=>{var se;document.activeElement===((se=D.current)==null?void 0:se.input)&&X.preventDefault()},K=X=>{var se,ge;f&&f((ge=(se=D.current)==null?void 0:se.input)==null?void 0:ge.value,X,{source:"input"})},q=X=>{F.current||d||(v==null||v(X),K(X))},Y=typeof l=="boolean"?Ur(c,T,a.createElement(Tx,null)):null,ee=`${L}-btn`,ie=H(ee,{[`${ee}-${x}`]:x});let ae;const U=l||{},Q=U.type&&U.type.__ANT_BUTTON===!0;if(Q||U.type==="button"){const X=U.props;ae=Fn(U,{onMouseDown:W,onClick:se=>{var ge,de;(de=(ge=U==null?void 0:U.props)==null?void 0:ge.onClick)==null||de.call(ge,se),K(se)},key:"enterButton",...Q?{className:H(ie,X.className),size:_}:{}})}else ae=a.createElement(Xe,{classNames:z.button,styles:B.button,className:ie,color:l?"primary":"default",size:_,disabled:m,key:"enterButton",onMouseDown:W,onClick:K,loading:d,icon:Y,variant:x==="borderless"||x==="filled"||x==="underlined"?"text":l?"solid":void 0},l);u&&(ae=[ae,Fn(u,{key:"addonAfter"})]);const Z=H(L,A,{[`${L}-rtl`]:N==="rtl",[`${L}-${_}`]:!!_,[`${L}-with-button`]:!!l},o,E,O,z.root),ne=X=>{F.current=!0,y==null||y(X)},oe=X=>{F.current=!1,b==null||b(X)},le=Nn(C,{data:!0}),re=Dt({...C,classNames:Dt(z,["button","root"]),styles:Dt(B,["button","root"]),prefixCls:j,type:"search",size:_,variant:x,onPressEnter:q,onCompositionStart:ne,onCompositionEnd:oe,onChange:V,disabled:m},Object.keys(le));return a.createElement(mx,{className:Z,style:{...B.root,...w,...i},...le,hidden:$},a.createElement(au,{ref:Tn(D,t),...re}),ae)}),DW=e=>{const{componentCls:t,paddingLG:n}=e,r=`${t}-textarea`;return{[`textarea${t}`]:{maxWidth:"100%",height:"auto",minHeight:e.controlHeight,lineHeight:e.lineHeight,verticalAlign:"bottom",transition:`all ${e.motionDurationSlow}`,resize:"vertical",[`&${t}-mouse-active`]:{transition:`all ${e.motionDurationSlow}, height 0s, width 0s`}},[`${t}-textarea-affix-wrapper-resize-dirty`]:{width:"auto"},[r]:{position:"relative","&-show-count":{[`${t}-data-count`]:{position:"absolute",bottom:e.calc(e.fontSize).mul(e.lineHeight).mul(-1).equal(),insetInlineEnd:0,color:e.colorTextDescription,whiteSpace:"nowrap",pointerEvents:"none"}},[` + &-allow-clear > ${t}, + &-affix-wrapper${r}-has-feedback ${t} + `]:{paddingInlineEnd:n},[`&-affix-wrapper${t}-affix-wrapper`]:{padding:0,[`> textarea${t}`]:{fontSize:"inherit",border:"none",outline:"none",background:"transparent",minHeight:e.calc(e.controlHeight).sub(e.calc(e.lineWidth).mul(2)).equal(),"&:focus":{boxShadow:"none !important"}},[`${t}-suffix`]:{margin:0,"> *:not(:last-child)":{marginInline:0},[`${t}-clear-icon`]:{position:"absolute",insetInlineEnd:e.paddingInline,insetBlockStart:e.paddingXS},[`${r}-suffix`]:{position:"absolute",top:0,insetInlineEnd:e.paddingInline,bottom:0,zIndex:1,display:"inline-flex",alignItems:"center",margin:"auto",pointerEvents:"none"}}},[`&-affix-wrapper${t}-affix-wrapper-rtl`]:{[`${t}-suffix`]:{[`${t}-data-count`]:{direction:"ltr",insetInlineStart:0}}},[`&-affix-wrapper${t}-affix-wrapper-sm`]:{[`${t}-suffix`]:{[`${t}-clear-icon`]:{insetInlineEnd:e.paddingInlineSM}}}}}},FW=Tt(["Input","TextArea"],e=>{const t=Rt(e,Hi(e));return DW(t)},Vi,{resetFont:!1}),HT=a.forwardRef((e,t)=>{var le;const{prefixCls:n,bordered:r=!0,size:o,disabled:s,status:i,allowClear:l,classNames:c,rootClassName:u,className:d,style:m,styles:f,variant:p,showCount:y,onMouseDown:b,onResize:x,...v}=e,{getPrefixCls:g,direction:h,allowClear:$,autoComplete:C,className:N,style:S,classNames:E,styles:w}=Pt("textArea"),R=a.useContext(cr),P=s??R,{status:T,hasFeedback:M,feedbackIcon:z}=a.useContext(Hn),B=nu(T,i),F=Mt(S),L=Mt(m),[j,O]=Ot([E,c],[w,F,f,L],{props:e}),A=a.useRef(null);a.useImperativeHandle(t,()=>{var re,X;return{resizableTextArea:(re=A.current)==null?void 0:re.resizableTextArea,focus:se=>{var ge,de;Db((de=(ge=A.current)==null?void 0:ge.resizableTextArea)==null?void 0:de.textArea,se)},blur:()=>{var se;return(se=A.current)==null?void 0:se.blur()},nativeElement:((X=A.current)==null?void 0:X.nativeElement)||null}});const k=g("input",n),_=on(k),[D,V]=tT(k,u);FW(k,_);const{compactSize:W,compactItemClassnames:K}=qs(k,h),q=Cn(re=>o??W??re),[Y,ee]=tl("textArea",p,r),ie=EN({allowClear:l,contextAllowClear:$,componentName:"TextArea"}),[ae,U]=a.useState(!1),[Q,Z]=a.useState(!1),ne=re=>{U(!0),b==null||b(re);const X=()=>{U(!1),document.removeEventListener("mouseup",X)};document.addEventListener("mouseup",X)},oe=re=>{var X,se;if(x==null||x(re),ae&&bt(getComputedStyle)){const ge=(se=(X=A.current)==null?void 0:X.nativeElement)==null?void 0:se.querySelector("textarea");ge&&getComputedStyle(ge).resize==="both"&&Z(!0)}};return a.createElement(zV,{autoComplete:C,...v,style:O.root,styles:O,disabled:P,allowClear:ie,className:H(V,_,d,u,K,N,j.root,{[`${k}-textarea-affix-wrapper-resize-dirty`]:Q}),classNames:{...j,textarea:H({[`${k}-sm`]:q==="small",[`${k}-lg`]:q==="large"},D,j.textarea,ae&&`${k}-mouse-active`),variant:H({[`${k}-${Y}`]:ee},qa(k,B)),affixWrapper:H(`${k}-textarea-affix-wrapper`,{[`${k}-affix-wrapper-rtl`]:h==="rtl",[`${k}-affix-wrapper-sm`]:q==="small",[`${k}-affix-wrapper-lg`]:q==="large",[`${k}-textarea-show-count`]:y||((le=e.count)==null?void 0:le.show)},D)},prefixCls:k,suffix:M&&a.createElement("span",{className:`${k}-textarea-suffix`},z),showCount:y,ref:A,onResize:oe,onMouseDown:ne})}),Lt=au;Lt.Group=xW;Lt.Search=AW;Lt.TextArea=HT;Lt.Password=BW;Lt.OTP=EW;function HW(e,t,n){return typeof n=="boolean"?n:e.length?!0:zn(t).some(o=>o.type===O4)}const Fm=({suffixCls:e,tagName:t,displayName:n})=>r=>a.forwardRef((s,i)=>a.createElement(r,{ref:i,suffixCls:e,tagName:t,...s})),e$=a.forwardRef((e,t)=>{const{prefixCls:n,suffixCls:r,className:o,tagName:s,...i}=e,{getPrefixCls:l}=a.useContext(ct),c=l("layout",n),[u,d]=M4(c),m=r?`${c}-${r}`:c;return a.createElement(s,{className:H(n||m,o,u,d),ref:t,...i})}),VW=a.forwardRef((e,t)=>{const{direction:n}=a.useContext(ct),[r,o]=a.useState([]),{prefixCls:s,className:i,rootClassName:l,children:c,hasSider:u,tagName:d,style:m,...f}=e,p=Dt(f,["suffixCls"]),{getPrefixCls:y,className:b,style:x}=Pt("layout"),v=y("layout",s),g=HW(r,c,u),[h,$]=M4(v),C=H(v,{[`${v}-has-sider`]:g,[`${v}-rtl`]:n==="rtl"},b,i,l,h,$),N=a.useMemo(()=>({siderHook:{addSider:S=>{o(E=>[].concat($t(E),[S]))},removeSider:S=>{o(E=>E.filter(w=>w!==S))}}}),[]);return a.createElement(N4.Provider,{value:N},a.createElement(d,{ref:t,className:C,style:{...x,...m},...p},c))}),WW=Fm({tagName:"div",displayName:"Layout"})(VW),KW=Fm({suffixCls:"header",tagName:"header",displayName:"Header"})(e$),UW=Fm({suffixCls:"footer",tagName:"footer",displayName:"Footer"})(e$),qW=Fm({suffixCls:"content",tagName:"main",displayName:"Content"})(e$),Ds=WW;Ds.Header=KW;Ds.Footer=UW;Ds.Content=qW;Ds.Sider=O4;Ds._InternalSiderContext=jm;var VT={};Object.defineProperty(VT,"__esModule",{value:!0});var GW={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M272.9 512l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L186.8 492.3a31.99 31.99 0 000 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H532c6.7 0 10.4-7.7 6.3-12.9L272.9 512zm304 0l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L490.8 492.3a31.99 31.99 0 000 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H836c6.7 0 10.4-7.7 6.3-12.9L576.9 512z"}}]},name:"double-left",theme:"outlined"},XW=VT.default=GW;function kv(){return kv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,kv({},e,{ref:t,icon:XW})),Iw=a.forwardRef(YW);var WT={};Object.defineProperty(WT,"__esModule",{value:!0});var QW={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M533.2 492.3L277.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H188c-6.7 0-10.4 7.7-6.3 12.9L447.1 512 181.7 851.1A7.98 7.98 0 00188 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5zm304 0L581.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H492c-6.7 0-10.4 7.7-6.3 12.9L751.1 512 485.7 851.1A7.98 7.98 0 00492 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z"}}]},name:"double-right",theme:"outlined"},JW=WT.default=QW;function Av(){return Av=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Av({},e,{ref:t,icon:JW})),Pw=a.forwardRef(ZW),eK={items_per_page:"条/页",jump_to:"跳至",jump_to_confirm:"确定",page:"页",prev_page:"上一页",next_page:"下一页",prev_5:"向前 5 页",next_5:"向后 5 页",prev_3:"向前 3 页",next_3:"向后 3 页",page_size:"页码"},tK=[10,20,50,100],nK=e=>{const{pageSizeOptions:t=tK,locale:n,changeSize:r,pageSize:o,goButton:s,quickGo:i,rootPrefixCls:l,disabled:c,buildOptionText:u,showSizeChanger:d,sizeChangerRender:m}=e,[f,p]=J.useState(""),y=J.useMemo(()=>!f||Number.isNaN(f)?void 0:Number(f),[f]),b=typeof u=="function"?u:E=>`${E} ${n.items_per_page}`,x=E=>{const w=E.target.value;/^\d*$/.test(w)&&p(w)},v=E=>{s||f===""||(p(""),!(E.relatedTarget&&(E.relatedTarget.className.includes(`${l}-item-link`)||E.relatedTarget.className.includes(`${l}-item`)))&&(i==null||i(y)))},g=E=>{f!==""&&(E.keyCode===nt.ENTER||E.type==="click")&&(p(""),i==null||i(y))},h=()=>t.some(E=>E.toString()===o.toString())?t:t.concat([o]).sort((E,w)=>{const R=Number.isNaN(Number(E))?0:Number(E),P=Number.isNaN(Number(w))?0:Number(w);return R-P}),$=`${l}-options`;if(!d&&!i)return null;let C=null,N=null,S=null;return d&&m&&(C=m({disabled:c,size:o,onSizeChange:E=>{r==null||r(Number(E))},"aria-label":n.page_size,className:`${$}-size-changer`,options:h().map(E=>({label:b(E),value:E}))})),i&&(s&&(S=typeof s=="boolean"?J.createElement("button",{type:"button",onClick:g,onKeyUp:g,disabled:c,className:`${$}-quick-jumper-button`},n.jump_to_confirm):J.createElement("span",{onClick:g,onKeyUp:g},s)),N=J.createElement("div",{className:`${$}-quick-jumper`},n.jump_to,J.createElement("input",{disabled:c,type:"text",value:f,onChange:x,onKeyUp:g,onBlur:v,"aria-label":n.page}),n.page,S)),J.createElement("li",{className:$},C,N)},Cl=e=>{const{rootPrefixCls:t,page:n,active:r,className:o,style:s,showTitle:i,onClick:l,onKeyPress:c,itemRender:u}=e,d=`${t}-item`,m=H(d,`${d}-${n}`,{[`${d}-active`]:r,[`${d}-disabled`]:!n},o),f=()=>{l(n)},p=b=>{c(b,l,n)},y=u(n,"page",J.createElement("a",{rel:"nofollow"},n));return y?J.createElement("li",{title:i?String(n):null,className:m,style:s,onClick:f,onKeyDown:p,tabIndex:0},y):null};function hs(){return hs=Object.assign?Object.assign.bind():function(e){for(var t=1;tn;function Nw(){}function Rw(e){const t=Number(e);return typeof t=="number"&&!Number.isNaN(t)&&isFinite(t)&&Math.floor(t)===t}function Js(e,t,n){const r=typeof e>"u"?t:e;return Math.floor((n-1)/r)+1}const oK=e=>{const{prefixCls:t="rc-pagination",selectPrefixCls:n="rc-select",className:r,classNames:o,styles:s,current:i,defaultCurrent:l=1,total:c=0,pageSize:u,defaultPageSize:d=10,onChange:m=Nw,hideOnSinglePage:f,align:p,showPrevNextJumpers:y=!0,showQuickJumper:b,showLessItems:x,showTitle:v=!0,onShowSizeChange:g=Nw,locale:h=eK,style:$,totalBoundaryShowSizeChanger:C=50,disabled:N,simple:S,showTotal:E,showSizeChanger:w=c>C,sizeChangerRender:R,pageSizeOptions:P,itemRender:T=rK,jumpPrevIcon:M,jumpNextIcon:z,prevIcon:B,nextIcon:F}=e,L=J.useRef(null),[j,O]=nn(d,u),[A,k]=nn(l,i),_=Math.max(1,Math.min(A,Js(void 0,j,c))),[D,V]=J.useState(_);a.useEffect(()=>{V(_)},[_]);const W=Math.max(1,_-(x?3:5)),K=Math.min(Js(void 0,j,c),_+(x?3:5));function q(ce,Pe){let pe=ce||J.createElement("button",{type:"button","aria-label":Pe,className:`${t}-item-link`});return typeof ce=="function"&&(pe=J.createElement(ce,e)),pe}function Y(ce){const Pe=ce.target.value,pe=Js(void 0,j,c);let $e;return Pe===""?$e=Pe:Number.isNaN(Number(Pe))?$e=D:Pe>=pe?$e=pe:$e=Number(Pe),$e}function ee(ce){return Rw(ce)&&ce!==_&&Rw(c)&&c>0}const ie=c>j?b:!1;function ae(ce){(ce.keyCode===nt.UP||ce.keyCode===nt.DOWN)&&ce.preventDefault()}function U(ce){const Pe=Y(ce);switch(Pe!==D&&V(Pe),ce.keyCode){case nt.ENTER:ne(Pe);break;case nt.UP:ne(Pe-1);break;case nt.DOWN:ne(Pe+1);break}}function Q(ce){ne(Y(ce))}function Z(ce){const Pe=Js(ce,j,c),pe=_>Pe&&Pe!==0?Pe:_;O(ce),V(pe),g==null||g(_,ce),k(pe),m==null||m(pe,ce)}function ne(ce){if(ee(ce)&&!N){const Pe=Js(void 0,j,c);let pe=ce;return ce>Pe?pe=Pe:ce<1&&(pe=1),pe!==D&&V(pe),k(pe),m==null||m(pe,j),pe}return _}const oe=_>1,le=_c?c:_*j]));let Me=null;const xe=Js(void 0,j,c);if(f&&c<=j)return null;const Ee=[],Ve={rootPrefixCls:t,onClick:ne,onKeyPress:de,showTitle:v,itemRender:T,page:-1,className:o==null?void 0:o.item,style:s==null?void 0:s.item},qe=_-1>0?_-1:0,me=_+1=Fe*2&&_!==3,te=!!Me&&xe-_>=Fe*2&&_!==xe-2;!x&&Be&&Ie!==xe&&(_e+=1),!x&&te&&_e!==1&&(Ie-=1);for(let ye=_e;ye<=Ie;ye+=1)Ee.push(J.createElement(Cl,hs({},Ve,{key:ye,page:ye,active:_===ye})));if(Be&&(Ee[0]=J.cloneElement(Ee[0],{className:H(`${t}-item-after-jump-prev`,Ee[0].props.className)}),Ee.unshift(ke)),te){const ye=Ee[Ee.length-1];Ee[Ee.length-1]=J.cloneElement(ye,{className:H(`${t}-item-before-jump-next`,ye.props.className)}),Ee.push(Me)}_e!==1&&Ee.unshift(J.createElement(Cl,hs({},Ve,{key:1,page:1}))),Ie!==xe&&Ee.push(J.createElement(Cl,hs({},Ve,{key:xe,page:xe})))}let et=we(qe);if(et){const ce=!oe||!xe;et=J.createElement("li",{title:v?h.prev_page:null,onClick:re,tabIndex:ce?null:0,onKeyDown:Se,className:H(`${t}-prev`,o==null?void 0:o.item,{[`${t}-disabled`]:ce}),style:s==null?void 0:s.item,"aria-disabled":ce},et)}let ve=ze(me);if(ve){let ce,Pe;S?(ce=!le,Pe=oe?0:null):(ce=!le||!xe,Pe=ce?null:0),ve=J.createElement("li",{title:v?h.next_page:null,onClick:X,tabIndex:Pe,onKeyDown:ue,className:H(`${t}-next`,o==null?void 0:o.item,{[`${t}-disabled`]:ce}),style:s==null?void 0:s.item,"aria-disabled":ce},ve)}const je=H(t,r,{[`${t}-start`]:p==="start",[`${t}-center`]:p==="center",[`${t}-end`]:p==="end",[`${t}-simple`]:S,[`${t}-disabled`]:N});return J.createElement("ul",hs({className:je,style:$,ref:L},Oe),Ce,et,S?Ge:Ee,ve,J.createElement(nK,{locale:h,rootPrefixCls:t,disabled:N,selectPrefixCls:n,changeSize:Z,pageSize:j,pageSizeOptions:P,quickGo:ie?ne:null,goButton:Ue,showSizeChanger:w,sizeChangerRender:R}))},sK=e=>{const{componentCls:t}=e;return{[`${t}-disabled`]:{"&, &:hover":{cursor:"not-allowed",[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed"}},"&:focus-visible":{cursor:"not-allowed",[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed"}}},[`&${t}-disabled`]:{cursor:"not-allowed",[`${t}-item`]:{cursor:"not-allowed",backgroundColor:"transparent","&:hover, &:active":{backgroundColor:"transparent"},a:{color:e.colorTextDisabled,backgroundColor:"transparent",border:"none",cursor:"not-allowed"},"&-active":{borderColor:e.colorBorder,backgroundColor:e.itemActiveBgDisabled,"&:hover, &:active":{backgroundColor:e.itemActiveBgDisabled},a:{color:e.itemActiveColorDisabled}}},[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed","&:hover, &:active":{backgroundColor:"transparent"},[`${t}-simple&`]:{backgroundColor:"transparent","&:hover, &:active":{backgroundColor:"transparent"}}},[`${t}-simple-pager`]:{color:e.colorTextDisabled},[`${t}-jump-prev, ${t}-jump-next`]:{[`${t}-item-link-icon`]:{opacity:0},[`${t}-item-ellipsis`]:{opacity:1}}}}},iK=e=>{const{componentCls:t}=e;return{[`&${t}-small ${t}-options`]:{marginInlineStart:e.paginationMiniOptionsMarginInlineStart,"&-quick-jumper":{input:{...Kx(e),width:e.paginationMiniQuickJumperInputWidth}}}}},aK=e=>{const{componentCls:t}=e;return{[`&${t}-large ${t}-options`]:{"&-quick-jumper":{input:{...Wx(e)}}}}},lK=e=>{const{componentCls:t,antCls:n}=e,[,r]=rn(n,"pagination");return{[`&${t}-simple`]:{[`${t}-prev, ${t}-next`]:{height:r("item-size-actual"),lineHeight:r("item-size-actual"),verticalAlign:"top",[`${t}-item-link`]:{height:r("item-size-actual"),backgroundColor:"transparent",border:0,"&:hover":{backgroundColor:e.colorBgTextHover},"&:active":{backgroundColor:e.colorBgTextActive},"&::after":{height:r("item-size-actual"),lineHeight:r("item-size-actual")}}},[`${t}-simple-pager`]:{display:"inline-flex",alignItems:"center",height:r("item-size-actual"),marginInlineEnd:r("item-spacing-actual"),input:{boxSizing:"border-box",height:"100%",width:e.quickJumperInputWidth,padding:`0 ${G(e.paginationItemPaddingInline)}`,textAlign:"center",backgroundColor:e.itemInputBg,border:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadius,outline:"none",transition:`border-color ${e.motionDurationMid}`,color:"inherit","&:hover":{borderColor:e.colorPrimary},"&:focus":{borderColor:e.colorPrimaryHover,boxShadow:`${G(e.inputOutlineOffset)} 0 ${G(e.controlOutlineWidth)} ${e.controlOutline}`},"&[disabled]":{color:e.colorTextDisabled,backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,cursor:"not-allowed"}}},[`&${t}-disabled`]:{[`${t}-prev, ${t}-next`]:{[`${t}-item-link`]:{"&:hover, &:active":{backgroundColor:"transparent"}}}},[`&${t}-small`]:{[`${t}-simple-pager`]:{input:{width:e.paginationMiniQuickJumperInputWidth}}}}}},cK=e=>{const{componentCls:t}=e,n=`${t}-options-quick-jumper input, ${t}-simple-pager input`;return{[`&${t}-filled`]:{[n]:{background:e.colorFillTertiary,borderColor:"transparent","&:hover":{background:e.colorFillSecondary},"&:focus":{borderColor:e.activeBorderColor,outline:0,backgroundColor:e.activeBg},"&[disabled]":{...su(e)}}},[`&${t}-borderless`]:{[n]:{background:"transparent",border:"none","&:focus":{outline:"none",boxShadow:"none"},"&[disabled]":{color:e.colorTextDisabled,cursor:"not-allowed"}}},[`&${t}-underlined`]:{[n]:{background:e.colorBgContainer,borderWidth:`${G(e.lineWidth)} 0`,borderStyle:`${e.lineType} none`,borderColor:`transparent transparent ${e.colorBorder} transparent`,borderRadius:0,"&:hover":{borderColor:`transparent transparent ${e.hoverBorderColor} transparent`,backgroundColor:e.hoverBg},"&:focus":{borderColor:`transparent transparent ${e.activeBorderColor} transparent`,outline:0,backgroundColor:e.activeBg},"&[disabled]":{color:e.colorTextDisabled,boxShadow:"none",cursor:"not-allowed"}}}}},uK=e=>{const{componentCls:t,iconCls:n,sizeLG:r,antCls:o}=e,[,s]=rn(o,"pagination");return{[`${t}-jump-prev, ${t}-jump-next`]:{outline:0,[`${t}-item-container`]:{position:"relative",[`${t}-item-link-icon`]:{color:e.colorPrimary,fontSize:e.fontSizeSM,opacity:0,transition:`all ${e.motionDurationMid}`,"&-svg":{top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,margin:"auto"}},[`${t}-item-ellipsis`]:{position:"absolute",inset:0,display:"inline-flex",justifyContent:"center",alignItems:"center",margin:"auto",color:e.colorTextDisabled,textAlign:"center",opacity:1,transition:`all ${e.motionDurationMid}`,[`${n}-ellipsis > svg`]:{width:r,height:r}}},"&:hover":{[`${t}-item-link-icon`]:{opacity:1},[`${t}-item-ellipsis`]:{opacity:0}}},[` + ${t}-prev, + ${t}-jump-prev, + ${t}-jump-next + `]:{marginInlineEnd:s("item-spacing-actual")},[` + ${t}-prev, + ${t}-next, + ${t}-jump-prev, + ${t}-jump-next + `]:{display:"inline-block",minWidth:s("item-size-actual"),height:s("item-size-actual"),color:e.colorText,fontFamily:e.fontFamily,lineHeight:s("item-size-actual"),textAlign:"center",verticalAlign:"middle",listStyle:"none",borderRadius:e.borderRadius,cursor:"pointer",transition:`all ${e.motionDurationMid}`},[`${t}-prev, ${t}-next`]:{outline:0,button:{color:e.colorText,cursor:"pointer",userSelect:"none"},[`${t}-item-link`]:{display:"block",width:"100%",height:"100%",padding:0,fontSize:e.fontSizeSM,textAlign:"center",backgroundColor:"transparent",border:`${G(e.lineWidth)} ${e.lineType} transparent`,borderRadius:e.borderRadius,outline:"none",transition:`all ${e.motionDurationMid}`},[`&:hover ${t}-item-link`]:{backgroundColor:e.colorBgTextHover},[`&:active ${t}-item-link`]:{backgroundColor:e.colorBgTextActive},[`&${t}-disabled:hover`]:{[`${t}-item-link`]:{backgroundColor:"transparent"}}},[`${t}-slash`]:{marginInlineEnd:e.paginationSlashMarginInlineEnd,marginInlineStart:e.paginationSlashMarginInlineStart},[`${t}-options`]:{display:"inline-block",marginInlineStart:e.margin,verticalAlign:"middle",[`&-size-changer, &-size-changer${t}-options-size-changer-select`]:{width:"auto"},"&-quick-jumper":{display:"inline-block",height:s("item-size-actual"),marginInlineStart:e.marginXS,lineHeight:s("item-size-actual"),verticalAlign:"baseline",input:{...km(e),...Vx(e,{borderColor:e.colorBorder,hoverBorderColor:e.colorPrimaryHover,activeBorderColor:e.colorPrimary,activeShadow:e.activeShadow}),"&[disabled]":{...su(e)},width:e.quickJumperInputWidth,height:s("item-size-actual"),boxSizing:"border-box",margin:0,marginInlineStart:s("item-spacing-actual"),marginInlineEnd:s("item-spacing-actual")}}}}},dK=e=>{const{componentCls:t,antCls:n}=e,[,r]=rn(n,"pagination");return{[`${t}-item`]:{display:"inline-block",minWidth:r("item-size-actual"),height:r("item-size-actual"),marginInlineEnd:r("item-spacing-actual"),fontFamily:e.fontFamily,lineHeight:G(e.calc(r("item-size-actual")).sub(2).equal()),textAlign:"center",verticalAlign:"middle",listStyle:"none",backgroundColor:e.itemBg,border:`${G(e.lineWidth)} ${e.lineType} transparent`,borderRadius:e.borderRadius,outline:0,cursor:"pointer",userSelect:"none",a:{display:"block",padding:`0 ${G(e.paginationItemPaddingInline)}`,color:e.colorText,"&:hover":{textDecoration:"none"}},[`&:not(${t}-item-active)`]:{"&:hover":{transition:`all ${e.motionDurationMid}`,backgroundColor:e.colorBgTextHover},"&:active":{backgroundColor:e.colorBgTextActive}},"&-active":{fontWeight:e.fontWeightStrong,backgroundColor:e.itemActiveBg,borderColor:e.colorPrimary,a:{color:e.itemActiveColor},"&:hover":{borderColor:e.colorPrimaryHover},"&:hover a":{color:e.itemActiveColorHover}}}}},fK=e=>{const{componentCls:t,antCls:n}=e,[r,o]=rn(n,"pagination");return{[t]:{[r("item-size-actual")]:G(e.itemSize),[r("item-spacing-actual")]:G(e.marginXS),"&-small":{[r("item-size-actual")]:G(e.itemSizeSM),[r("item-spacing-actual")]:G(e.marginXXS)},"&-large":{[r("item-size-actual")]:G(e.itemSizeLG),[r("item-spacing-actual")]:G(e.marginSM)},...Ft(e),display:"flex",alignItems:"center","&-start":{justifyContent:"start"},"&-center":{justifyContent:"center"},"&-end":{justifyContent:"end"},"ul, ol":{margin:0,padding:0,listStyle:"none"},"&::after":{display:"block",clear:"both",height:0,overflow:"hidden",visibility:"hidden",content:'""'},[`${t}-total-text`]:{display:"inline-block",height:o("item-size-actual"),marginInlineEnd:o("item-spacing-actual"),lineHeight:G(e.calc(o("item-size-actual")).sub(2).equal()),verticalAlign:"middle"},...dK(e),...uK(e),...lK(e),...cK(e),...iK(e),...aK(e),...sK(e),[`@media only screen and (max-width: ${e.screenLG}px)`]:{[`${t}-item`]:{"&-after-jump-prev, &-before-jump-next":{display:"none"}}},[`@media only screen and (max-width: ${e.screenSM}px)`]:{[`${t}-options`]:{display:"none"}}},[`&${e.componentCls}-rtl`]:{direction:"rtl"}}},mK=e=>{const{componentCls:t}=e;return{[`${t}:not(${t}-disabled)`]:{[`${t}-item`]:{...Br(e)},[`${t}-jump-prev, ${t}-jump-next`]:{"&:focus-visible":{[`${t}-item-link-icon`]:{opacity:1},[`${t}-item-ellipsis`]:{opacity:0},...jr(e)}},[`${t}-prev, ${t}-next`]:{[`&:focus-visible ${t}-item-link`]:jr(e)}}}},KT=e=>({itemBg:e.colorBgContainer,itemSize:e.controlHeight,itemSizeSM:e.controlHeightSM,itemSizeLG:e.controlHeightLG,itemActiveBg:e.colorBgContainer,itemActiveColor:e.colorPrimary,itemActiveColorHover:e.colorPrimaryHover,itemLinkBg:e.colorBgContainer,itemActiveColorDisabled:e.colorTextDisabled,itemActiveBgDisabled:e.controlItemBgActiveDisabled,itemInputBg:e.colorBgContainer,miniOptionsSizeChangerTop:0,...Vi(e)}),UT=e=>Rt(e,{inputOutlineOffset:0,quickJumperInputWidth:e.calc(e.controlHeightLG).mul(1.25).equal(),paginationMiniOptionsMarginInlineStart:e.calc(e.marginXXS).div(2).equal(),paginationMiniQuickJumperInputWidth:e.calc(e.controlHeightLG).mul(1.1).equal(),paginationItemPaddingInline:e.calc(e.marginXXS).mul(1.5).equal(),paginationEllipsisLetterSpacing:e.calc(e.marginXXS).div(2).equal(),paginationSlashMarginInlineStart:e.marginSM,paginationSlashMarginInlineEnd:e.marginSM,paginationEllipsisTextIndent:"0.13em"},Hi(e)),pK=Tt("Pagination",e=>{const t=UT(e);return[fK(t),mK(t)]},KT),gK=e=>{const{componentCls:t}=e;return{[`${t}${t}-bordered${t}-disabled`]:{"&, &:hover":{[`${t}-item-link`]:{borderColor:e.colorBorder}},"&:focus-visible":{[`${t}-item-link`]:{borderColor:e.colorBorder}},[`${t}-item, ${t}-item-link`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,[`&:hover:not(${t}-item-active)`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,a:{color:e.colorTextDisabled}},[`&${t}-item-active`]:{backgroundColor:e.itemActiveBgDisabled}},[`${t}-prev, ${t}-next`]:{"&:hover button":{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,color:e.colorTextDisabled},[`${t}-item-link`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder}}},[`${t}${t}-bordered`]:{[`${t}-prev, ${t}-next`]:{"&:hover button":{borderColor:e.colorPrimaryHover,backgroundColor:e.itemBg},[`${t}-item-link`]:{backgroundColor:e.itemLinkBg,borderColor:e.colorBorder},[`&:hover ${t}-item-link`]:{borderColor:e.colorPrimary,backgroundColor:e.itemBg,color:e.colorPrimary},[`&${t}-disabled`]:{[`${t}-item-link`]:{borderColor:e.colorBorder,color:e.colorTextDisabled}}},[`${t}-item`]:{backgroundColor:e.itemBg,border:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,[`&:hover:not(${t}-item-active)`]:{borderColor:e.colorPrimary,backgroundColor:e.itemBg,a:{color:e.colorPrimary}},"&-active":{borderColor:e.colorPrimary}}}}},hK=Ks(["Pagination","bordered"],e=>{const t=UT(e);return gK(t)},KT);function Tw(e){return a.useMemo(()=>typeof e=="boolean"?[e,{}]:dt(e)?[!0,e]:[void 0,void 0],[e])}const yK=e=>{const{align:t,prefixCls:n,selectPrefixCls:r,className:o,rootClassName:s,style:i,size:l,locale:c,responsive:u,showSizeChanger:d,selectComponentClass:m,pageSizeOptions:f,styles:p,classNames:y,...b}=e,{xs:x}=Mm(u),[,v]=Yn(),{getPrefixCls:g,direction:h,showSizeChanger:$,className:C,style:N,classNames:S,styles:E,totalBoundaryShowSizeChanger:w}=Pt("pagination"),R=g("pagination",n),[P,T]=pK(R),M=Cn(l),z=M==="small"||!!(x&&!M&&u),[B,F]=tl("input"),L={...e,size:M},j=Mt(N),O=Mt(i),[A,k]=Ot([S,y],[E,j,p,O],{props:L}),[_]=Ar("Pagination",YP),D={..._,...c},[V,W]=Tw(d),[K,q]=Tw($),Y=V??K,ee=W??q,ie=m||dn,ae=a.useMemo(()=>f?f.map(Number):void 0,[f]),U=le=>{var we;const{disabled:re,size:X,onSizeChange:se,"aria-label":ge,className:de,options:Se}=le,{className:ue,onChange:be}=ee||{},Ne=(we=Se.find(ze=>String(ze.value)===String(X)))==null?void 0:we.value;return a.createElement(ie,{disabled:re,showSearch:!0,popupMatchSelectWidth:!1,getPopupContainer:ze=>ze.parentNode,"aria-label":ge,options:Se,...ee,value:Ne,onChange:(ze,he)=>{se==null||se(ze),be==null||be(ze,he)},size:M,className:H(`${R}-options-size-changer-select`,de,ue)})},Q=a.useMemo(()=>{const le=a.createElement("span",{className:`${R}-item-ellipsis`},a.createElement(Bm,null)),re=a.createElement("button",{className:`${R}-item-link`,type:"button",tabIndex:-1},h==="rtl"?a.createElement(Nc,null):a.createElement(Tc,null)),X=a.createElement("button",{className:`${R}-item-link`,type:"button",tabIndex:-1},h==="rtl"?a.createElement(Tc,null):a.createElement(Nc,null)),se=a.createElement("a",{className:`${R}-item-link`},a.createElement("div",{className:`${R}-item-container`},h==="rtl"?a.createElement(Pw,{className:`${R}-item-link-icon`}):a.createElement(Iw,{className:`${R}-item-link-icon`}),le)),ge=a.createElement("a",{className:`${R}-item-link`},a.createElement("div",{className:`${R}-item-container`},h==="rtl"?a.createElement(Iw,{className:`${R}-item-link-icon`}):a.createElement(Pw,{className:`${R}-item-link-icon`}),le));return{prevIcon:re,nextIcon:X,jumpPrevIcon:se,jumpNextIcon:ge}},[h,R]),Z=g("select",r),ne=H({[`${R}-${t}`]:!!t,[`${R}-${M}`]:M,[`${R}-${B}`]:F&&B!=="outlined",[`${R}-mini`]:z,[`${R}-rtl`]:h==="rtl",[`${R}-bordered`]:v.wireframe},C,o,s,A.root,P,T),oe={...k.root};return a.createElement(a.Fragment,null,v.wireframe&&a.createElement(hK,{prefixCls:R}),a.createElement(oK,{...Q,...b,styles:k,classNames:A,style:oe,prefixCls:R,selectPrefixCls:Z,className:ne,locale:D,pageSizeOptions:ae,showSizeChanger:Y,totalBoundaryShowSizeChanger:b.totalBoundaryShowSizeChanger??w,sizeChangerRender:U}))},Nf=100,qT=Nf/5,GT=Nf/2-qT/2,vg=GT*2*Math.PI,Mw=50,Ow=e=>{const{dotClassName:t,style:n,hasCircleCls:r}=e;return a.createElement("circle",{className:H(`${t}-circle`,{[`${t}-circle-bg`]:r}),r:GT,cx:Mw,cy:Mw,strokeWidth:qT,style:n})},vK=({percent:e,prefixCls:t})=>{const n=`${t}-dot`,r=`${n}-holder`,o=`${r}-hidden`,[s,i]=a.useState(!1);It(()=>{e!==0&&i(!0)},[e]);const l=Math.max(Math.min(e,100),0);if(!s)return null;const c={strokeDashoffset:`${vg/4}`,strokeDasharray:`${vg*l/100} ${vg*(100-l)/100}`};return a.createElement("span",{className:H(r,`${n}-progress`,{[o]:l<=0})},a.createElement("svg",{viewBox:`0 0 ${Nf} ${Nf}`,role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":l},a.createElement(Ow,{dotClassName:n,hasCircleCls:!0}),a.createElement(Ow,{dotClassName:n,style:c})))};function bK(e){const{prefixCls:t,percent:n=0,className:r,style:o}=e,s=`${t}-dot`,i=`${s}-holder`,l=`${i}-hidden`;return a.createElement(a.Fragment,null,a.createElement("span",{className:H(i,r,n>0&&l),style:o},a.createElement("span",{className:H(s,`${t}-dot-spin`)},[1,2,3,4].map(c=>a.createElement("i",{className:`${t}-dot-item`,key:c})))),a.createElement(vK,{prefixCls:t,percent:n}))}function xK(e){const{prefixCls:t,indicator:n,percent:r,className:o,style:s}=e,i=`${t}-dot`;return n&&a.isValidElement(n)?Fn(n,l=>({className:H(l.className,i,o),style:{...l.style,...s},percent:r})):a.createElement(bK,{prefixCls:t,percent:r,className:o,style:s})}const $K=new Ht("antSpinMove",{to:{opacity:1}}),SK=new Ht("antRotate",{to:{transform:"rotate(405deg)"}}),CK=e=>{const{componentCls:t}=e,n=`${t}-section`;return{[t]:{...Ft(e),position:"relative","&-rtl":{direction:"rtl"},[`&${n}, ${n}`]:{display:"flex",alignItems:"center",flexDirection:"column",gap:e.paddingSM,color:e.colorPrimary},[`&${n}`]:{display:"inline-flex"},[n]:{position:"absolute",top:"50%",left:{_skip_check_:!0,value:"50%"},transform:"translate(-50%, -50%)",zIndex:1},[`${t}-description`]:{fontSize:e.fontSize,lineHeight:1},[`${t}-container`]:{position:"relative",transition:`opacity ${e.motionDurationSlow}`,"&::after":{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,zIndex:10,width:"100%",height:"100%",background:e.colorBgContainer,opacity:0,transition:`all ${e.motionDurationSlow}`,content:'""',pointerEvents:"none"}},"&-spinning":{[`${t}-description`]:{textShadow:`0 0px 5px ${e.colorBgContainer}`},[`${t}-container`]:{clear:"both",opacity:.5,userSelect:"none",pointerEvents:"none","&::after":{opacity:.4,pointerEvents:"auto"}}},"&-fullscreen":{position:"fixed",inset:0,backgroundColor:e.colorBgMask,zIndex:e.zIndexPopupBase,opacity:0,pointerEvents:"none",transition:`all ${e.motionDurationMid}`,[`&${t}-spinning`]:{opacity:1,pointerEvents:"auto"},[n]:{color:e.colorWhite,[`${t}-description`]:{color:e.colorTextLightSolid}}}}}},wK=e=>{const{componentCls:t,antCls:n,motionDurationSlow:r}=e,[o,s]=rn(n,"spin");return{[t]:{[o("dot-holder-size")]:e.dotSize,[o("dot-item-size")]:`calc((${s("dot-holder-size")} - ${e.marginXXS} / 2) / 2)`,[`${t}-dot`]:{"&-holder":{width:"1em",height:"1em",fontSize:s("dot-holder-size"),display:"inline-block",transition:["transform","opacity"].map(i=>`${i} ${r} ease`).join(", "),transformOrigin:"50% 50%",lineHeight:1,"&-hidden":{transform:"scale(0.3)",opacity:0}},position:"relative",display:"inline-block",fontSize:s("dot-holder-size"),width:"1em",height:"1em","&-spin":{transform:"rotate(45deg)",animationName:SK,animationDuration:"1.2s",animationIterationCount:"infinite",animationTimingFunction:"linear"},"&-item":{position:"absolute",display:"block",width:s("dot-item-size"),height:s("dot-item-size"),background:"currentColor",borderRadius:"100%",transform:"scale(0.75)",transformOrigin:"50% 50%",opacity:.3,animationName:$K,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear",animationDirection:"alternate","&:nth-child(1)":{top:0,insetInlineStart:0,animationDelay:"0s"},"&:nth-child(2)":{top:0,insetInlineEnd:0,animationDelay:"0.4s"},"&:nth-child(3)":{insetInlineEnd:0,bottom:0,animationDelay:"0.8s"},"&:nth-child(4)":{bottom:0,insetInlineStart:0,animationDelay:"1.2s"}},"&-progress":{position:"absolute",left:"50%",top:0,transform:"translateX(-50%)"},"&-circle":{strokeLinecap:"round",transition:["stroke-dashoffset","stroke-dasharray","stroke","stroke-width","opacity"].map(i=>`${i} ${r} ease`).join(","),fillOpacity:0,stroke:"currentcolor"},"&-circle-bg":{stroke:e.colorFillSecondary}}}}},EK=e=>{const{componentCls:t}=e,[n]=rn(e.antCls,"spin");return{[t]:{"&-sm":{[n("dot-holder-size")]:e.dotSizeSM},"&-lg":{[n("dot-holder-size")]:e.dotSizeLG}}}},IK=e=>{const{controlHeightLG:t,controlHeight:n}=e;return{contentHeight:400,dotSize:t/2,dotSizeSM:t*.35,dotSizeLG:n}},PK=Tt("Spin",e=>{const t=Rt(e,{spinDotDefault:e.colorTextDescription});return[CK(t),wK(t),EK(t)]},IK),NK=200,_w=[[30,.05],[70,.03],[96,.01]];function RK(e,t){const[n,r]=a.useState(0),o=a.useRef(null),s=t==="auto";return a.useEffect(()=>(s&&e&&(r(0),o.current=setInterval(()=>{r(i=>{const l=100-i;for(let c=0;c<_w.length;c+=1){const[u,d]=_w[c];if(i<=u)return i+l*d}return i})},NK)),()=>{o.current&&(clearInterval(o.current),o.current=null)}),[s,e]),s?n:t}let XT;function TK(e,t){return!!e&&!!t&&!Number.isNaN(Number(t))}const YT=e=>{const{prefixCls:t,spinning:n=!0,delay:r=0,className:o,rootClassName:s,size:i,tip:l,description:c,wrapperClassName:u,style:d,children:m,fullscreen:f=!1,indicator:p,percent:y,classNames:b,styles:x,...v}=e,{getPrefixCls:g,direction:h,indicator:$,className:C,style:N,classNames:S,styles:E}=Pt("spin"),w=g("spin",t),[R,P]=PK(w),[T,M]=a.useState(()=>n&&!TK(n,r)),z=RK(T,y);a.useEffect(()=>{if(n){const V=m9(r,()=>{M(!0)});return V(),()=>{var W;(W=V==null?void 0:V.cancel)==null||W.call(V)}}M(!1)},[r,n]);const B=Cn(V=>i??V),F=c??l,L={...e,size:B,spinning:T,tip:F,description:F,fullscreen:f,children:m,percent:z},[j,O]=Ot([S,b],[E,x],{props:L}),A=p??$??XT,k=typeof m<"u",_=k||f,D=a.createElement(a.Fragment,null,a.createElement(xK,{className:H(j.indicator),style:O.indicator,prefixCls:w,indicator:A,percent:z}),F&&a.createElement("div",{className:H(`${w}-description`,j.tip,j.description),style:{...O.tip,...O.description}},F));return a.createElement("div",{className:H(w,{[`${w}-sm`]:B==="small",[`${w}-lg`]:B==="large",[`${w}-spinning`]:T,[`${w}-rtl`]:h==="rtl",[`${w}-fullscreen`]:f},s,j.root,f&&j.mask,_?u:[`${w}-section`,j.section],C,o,R,P),style:{...O.root,..._?{}:O.section,...f?O.mask:{},...N,...d},"aria-live":"polite","aria-busy":T,...v},T&&(_?a.createElement("div",{className:H(`${w}-section`,j.section),style:O.section},D):D),k&&a.createElement("div",{className:H(`${w}-container`,j.container),style:O.container},m))};YT.setDefaultIndicator=e=>{XT=e};const QT=(e,t={})=>!bn(e)&&(t!=null&&t.skipEmpty)?[]:Array.isArray(e)?e:[e],MK=e=>{const{items:t,classNames:n,style:r}=e,{getPrefixCls:o}=Pt("message"),s=o("message"),i=on(s),[l,c]=ux(s,i),u=`${s}-notice`,d=t.map(m=>{const{content:f,duration:p,key:y,type:b}=m,x=b?`${u}-icon-${b}`:void 0;return{key:y,duration:p,icon:dx(b),title:f,className:`${u}-${b}`,classNames:{wrapper:`${s}-${b}`,icon:x}}});return a.createElement(xN,{prefixCls:s,placement:"top",configList:d,className:H(l,c,i),classNames:{...n,wrapper:n==null?void 0:n.wrapper,title:n==null?void 0:n.title},style:r,stack:!1})};let Tr=null,gi=e=>e(),jc=[],Bc={};function zw(){const{getContainer:e,duration:t,rtl:n,maxCount:r,top:o,stack:s}=Bc,i=(e==null?void 0:e())||document.body;return{getContainer:()=>i,duration:t,rtl:n,maxCount:r,top:o,stack:s}}const OK=J.forwardRef((e,t)=>{const{messageConfig:n,sync:r}=e,{getPrefixCls:o}=a.useContext(ct),s=Bc.prefixCls||o("message"),i=a.useContext(Wy),[l,c]=kN({...n,prefixCls:s,...i.message});return J.useImperativeHandle(t,()=>{const u={...l};return Object.keys(u).forEach(d=>{u[d]=(...m)=>(r(),l[d].apply(l,m))}),{instance:u,sync:r}}),c}),_K=J.forwardRef((e,t)=>{const[n,r]=J.useState(zw),o=()=>{r(zw)};J.useEffect(o,[]);const s=gN(),i=s.getRootPrefixCls(),l=s.getIconPrefixCls(),c=s.getTheme(),u=J.createElement(OK,{ref:t,sync:o,messageConfig:n});return J.createElement(to,{prefixCls:i,iconPrefixCls:l,theme:c},s.holderRender?s.holderRender(u):u)}),Hm=()=>{if(!Tr){const e=document.createDocumentFragment(),t={fragment:e};Tr=t,gi(()=>{Wb(J.createElement(_K,{ref:n=>{const{instance:r,sync:o}=n||{};Promise.resolve().then(()=>{!t.instance&&r&&(t.instance=r,t.sync=o,Hm())})}}),e)});return}Tr.instance&&(jc.forEach(e=>{const{type:t,skipped:n}=e;if(!n)switch(t){case"open":{gi(()=>{const r=Tr.instance.open({...Bc,...e.config});r==null||r.then(e.resolve),e.setCloseFn(r)});break}case"destroy":gi(()=>{Tr==null||Tr.instance.destroy(e.key)});break;default:gi(()=>{var r;const o=(r=Tr.instance)[t].apply(r,$t(e.args));o==null||o.then(e.resolve),e.setCloseFn(o)})}}),jc=[])};function zK(e){Bc={...Bc,...e},gi(()=>{var t;(t=Tr==null?void 0:Tr.sync)==null||t.call(Tr)})}function jK(e){const t=fx(n=>{let r;const o={type:"open",config:e,resolve:n,setCloseFn:s=>{r=s}};return jc.push(o),()=>{r?gi(()=>{r()}):o.skipped=!0}});return Hm(),t}function BK(e,t){const n=fx(r=>{let o;const s={type:e,args:t,resolve:r,setCloseFn:i=>{o=i}};return jc.push(s),()=>{o?gi(()=>{o()}):s.skipped=!0}});return Hm(),n}const LK=e=>{jc.push({type:"destroy",key:e}),Hm()},kK=["success","info","warning","error","loading"],AK={open:jK,destroy:LK,config:zK,useMessage:AN,_InternalPanelDoNotUseOrYouWillBeFired:MB,_InternalListDoNotUseOrYouWillBeFired:MK},JT=AK;kK.forEach(e=>{JT[e]=(...t)=>BK(e,t)});const DK=e=>{const{prefixCls:t,className:n,closeIcon:r,closable:o,type:s,title:i,children:l,footer:c,classNames:u,styles:d,...m}=e,{getPrefixCls:f}=a.useContext(ct),{className:p,style:y,classNames:b,styles:x}=Pt("modal"),v=f(),g=t||f("modal"),h=on(v),[$,C]=vR(g,h),[N,S]=Ot([b,u],[x,d],{props:e}),E=`${g}-confirm`;let w={};return s?w={closable:o??!1,title:"",footer:"",children:a.createElement(xR,{...e,prefixCls:g,confirmPrefixCls:E,rootPrefixCls:v,content:l})}:w={closable:o??!0,title:i,footer:c!==null&&a.createElement(pR,{...e}),children:l},a.createElement(ZN,{prefixCls:g,className:H($,`${g}-pure-panel`,s&&E,s&&`${E}-${s}`,n,p,C,h,N.root),style:{...y,...S.root},...m,closeIcon:mR(g,r),closable:o,classNames:N,styles:S,...w})},FK=OR(DK);function ZT(e){return eu(wR(e))}const Sn=bR;Sn.useModal=RR;Sn.info=function(t){return eu(ER(t))};Sn.success=function(t){return eu(IR(t))};Sn.error=function(t){return eu(PR(t))};Sn.warning=ZT;Sn.warn=ZT;Sn.confirm=function(t){return eu(NR(t))};Sn.destroyAll=function(){for(;mi.length;){const t=mi.pop();t&&t()}};Sn.config=TA;Sn._InternalPanelDoNotUseOrYouWillBeFired=FK;const HK=e=>{const{componentCls:t,iconCls:n,antCls:r,zIndexPopup:o,colorText:s,colorWarning:i,marginXXS:l,marginXS:c,fontSize:u,fontWeightStrong:d,colorTextHeading:m}=e;return{[t]:{zIndex:o,[`&${r}-popover`]:{fontSize:u},[`${t}-message`]:{marginBottom:c,display:"flex",flexWrap:"nowrap",alignItems:"start",[`> ${t}-message-icon`]:{color:i},[`> ${t}-message-icon ${n}`]:{fontSize:u,lineHeight:1,marginInlineEnd:c},[`${t}-title`]:{fontWeight:d,color:m,"&:only-child":{fontWeight:"normal"}},[`${t}-description`]:{marginTop:l,color:s}},[`${t}-buttons`]:{textAlign:"end",whiteSpace:"nowrap",button:{marginInlineStart:c}}}}},VK=e=>{const{zIndexPopupBase:t}=e;return{zIndexPopup:t+60}},eM=Tt("Popconfirm",HK,VK,{resetStyle:!1}),tM=e=>{const{prefixCls:t,okButtonProps:n,cancelButtonProps:r,title:o,description:s,cancelText:i,okText:l,okType:c="primary",icon:u=a.createElement(Li,null),showCancel:d=!0,close:m,onConfirm:f,onCancel:p,onPopupClick:y,classNames:b,styles:x}=e,{getPrefixCls:v}=a.useContext(ct),[g]=Ar("Popconfirm",Zr.Popconfirm),h=Ga(o),$=Ga(s);return a.createElement("div",{className:`${t}-inner-content`,onClick:y},a.createElement("div",{className:`${t}-message`},u&&a.createElement("span",{className:H(`${t}-message-icon`,b==null?void 0:b.icon),style:x==null?void 0:x.icon},u),a.createElement("div",{className:`${t}-message-text`},$n(h)&&a.createElement("div",{className:H(`${t}-title`,b==null?void 0:b.title),style:x==null?void 0:x.title},h),$n($)&&a.createElement("div",{className:H(`${t}-description`,b==null?void 0:b.content),style:x==null?void 0:x.content},$))),a.createElement("div",{className:`${t}-buttons`},d&&a.createElement(Xe,{onClick:p,size:"small",...r},i||(g==null?void 0:g.cancelText)),a.createElement(vx,{buttonProps:{size:"small",...px(c),...n},actionFn:f,close:m,prefixCls:v("btn"),quitOnNullishReturnValue:!0,emitEvent:!0},l||(g==null?void 0:g.okText))))},WK=e=>{const{prefixCls:t,placement:n,className:r,style:o,...s}=e,{getPrefixCls:i}=a.useContext(ct),l=i("popconfirm",t);return eM(l),a.createElement(d4,{placement:n,className:H(l,r),style:o,content:a.createElement(tM,{prefixCls:l,...s})})},KK=a.forwardRef((e,t)=>{const{prefixCls:n,placement:r="top",trigger:o,okType:s="primary",icon:i=a.createElement(Li,null),children:l,overlayClassName:c,onOpenChange:u,overlayStyle:d,styles:m,arrow:f,classNames:p,...y}=e,{getPrefixCls:b,className:x,style:v,classNames:g,styles:h,arrow:$,trigger:C}=Pt("popconfirm"),[N,S]=nn(e.defaultOpen??!1,e.open),E=jx(f,$),w=o||C||"click",R=A=>{S(A),u==null||u(A)},P=()=>{R(!1)},T=A=>{var k;return(k=e.onConfirm)==null?void 0:k.call(void 0,A)},M=A=>{var k;R(!1),(k=e.onCancel)==null||k.call(void 0,A)},z=A=>{const{disabled:k=!1}=e;k||R(A)},B=b("popconfirm",n),F={...e,placement:r,trigger:w,okType:s,overlayStyle:d,styles:m,classNames:p},[L,j]=Ot([g,p],[h,m],{props:F}),O=H(B,x,c,L.root);return eM(B),a.createElement(Bx,{arrow:E,...Dt(y,["title"]),trigger:w,placement:r,onOpenChange:z,open:N,ref:t,classNames:{root:O,container:L.container,arrow:L.arrow},styles:{root:{...v,...j.root,...d},container:j.container,arrow:j.arrow},content:a.createElement(tM,{okType:s,icon:i,...e,prefixCls:B,close:P,onConfirm:T,onCancel:M,classNames:L,styles:j}),"data-popover-inject":!0},l)}),lu=KK;lu._InternalPanelDoNotUseOrYouWillBeFired=WK;var nM={};Object.defineProperty(nM,"__esModule",{value:!0});var UK={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z"}}]},name:"reload",theme:"outlined"},qK=nM.default=UK;function Dv(){return Dv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Dv({},e,{ref:t,icon:qK})),Gs=a.forwardRef(GK),Uo={},cu="rc-table-internal-hook";function t$(e){const t=a.createContext(void 0);return{Context:t,Provider:({value:r,children:o})=>{const s=a.useRef(r);s.current=r;const[i]=a.useState(()=>({getValue:()=>s.current,listeners:new Set}));return It(()=>{ss.unstable_batchedUpdates(()=>{i.listeners.forEach(l=>{l(r)})})},[r]),a.createElement(t.Provider,{value:i},o)},defaultValue:e}}function Bn(e,t){const n=vt(typeof t=="function"?t:c=>{if(t===void 0)return c;if(!Array.isArray(t))return c[t];const u={};return t.forEach(d=>{u[d]=c[d]}),u}),r=a.useContext(e==null?void 0:e.Context),{listeners:o,getValue:s}=r||{},i=a.useRef();i.current=n(r?s():e==null?void 0:e.defaultValue);const[,l]=a.useState({});return It(()=>{if(!r)return;function c(u){const d=n(u);ho(i.current,d,!0)||l({})}return o.add(c),()=>{o.delete(c)}},[r]),i.current}function ec(){return ec=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const d=i?{ref:u}:{},m=a.useRef(0),f=a.useRef(c);return t()!==null?a.createElement(o,ec({},c,d)):((!s||s(f.current,c))&&(m.current+=1),f.current=c,a.createElement(e.Provider,{value:m.current},a.createElement(o,ec({},c,d))))};return i?a.forwardRef(l):l}function r(o,s){const i=is(o),l=(c,u)=>{const d=i?{ref:u}:{};return t(),a.createElement(o,ec({},c,d))};return a.memo(i?a.forwardRef(l):l,s)}return{makeImmutable:n,responseImmutable:r,useImmutableMark:t}}const{makeImmutable:rM,responseImmutable:ol,useImmutableMark:YK}=XK(),Gn=t$(),oM=a.createContext({renderWithProps:!1}),QK="RC_TABLE_KEY";function JK(e){return e==null?[]:Array.isArray(e)?e:[e]}function Vm(e){const t=[],n={};return e.forEach(r=>{const{key:o,dataIndex:s}=r||{};let i=o||JK(s).join("-")||QK;for(;n[i];)i=`${i}_next`;n[i]=!0,t.push(i)}),t}function Fv(e){return e!=null}function ZK(e){return typeof e=="number"&&!Number.isNaN(e)}function eU(e){return e&&typeof e=="object"&&!Array.isArray(e)&&!a.isValidElement(e)}function tU(e,t,n,r,o,s){const i=a.useContext(oM),l=YK();return _i(()=>{if(Fv(r))return[r];const u=t==null||t===""?[]:Array.isArray(t)?t:[t],d=Kn(e,u);let m=d,f;if(o){const p=o(d,e,n);eU(p)?(m=p.children,f=p.props,i.renderWithProps=!0):m=p}return[m,f]},[l,e,r,t,o,n],(u,d)=>{if(s){const[,m]=u,[,f]=d;return s(f,m)}return i.renderWithProps?!0:!ho(u,d,!0)})}function nU(e,t,n,r){const o=e+t-1;return e<=r&&o>=n}function rU(e,t){return Bn(Gn,n=>[nU(e,t||1,n.hoverStartRow,n.hoverEndRow),n.onHover])}function Hv(){return Hv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var s,i;let r;const o=e===!0?{showTitle:!0}:e;return o&&(o.showTitle||t==="header")&&(typeof n=="string"||typeof n=="number"?r=n.toString():a.isValidElement(n)&&typeof((s=n.props)==null?void 0:s.children)=="string"&&(r=(i=n.props)==null?void 0:i.children)),r},sU=e=>{const{component:t,children:n,ellipsis:r,scope:o,prefixCls:s,className:i,style:l,align:c,record:u,render:d,dataIndex:m,renderIndex:f,shouldCellUpdate:p,index:y,rowType:b,colSpan:x,rowSpan:v,fixStart:g,fixEnd:h,fixedStartShadow:$,fixedEndShadow:C,offsetFixedStartShadow:N,offsetFixedEndShadow:S,zIndex:E,zIndexReverse:w,appendNode:R,additionalProps:P={},isSticky:T}=e,M=`${s}-cell`,{allColumnsFixedLeft:z,rowHoverable:B}=Bn(Gn,["allColumnsFixedLeft","rowHoverable"]),[F,L]=tU(u,m,f,n,d,p),j={},O=typeof g=="number"&&!z,A=typeof h=="number"&&!z,[k,_]=Bn(Gn,({scrollInfo:Z})=>{if(!O&&!A)return[!1,!1];const[ne,oe]=Z,le=(O&&$&&ne)-N>=1,re=(A&&C&&oe-ne)-S>1;return[le,re]});O&&(j.insetInlineStart=g,j["--z-offset"]=E,j["--z-offset-reverse"]=w),A&&(j.insetInlineEnd=h,j["--z-offset"]=E,j["--z-offset-reverse"]=w);const D=(L==null?void 0:L.colSpan)??P.colSpan??x??1,V=(L==null?void 0:L.rowSpan)??P.rowSpan??v??1,[W,K]=rU(y,V),q=vt(Z=>{var ne;u&&K(y,y+V-1),(ne=P==null?void 0:P.onMouseEnter)==null||ne.call(P,Z)}),Y=vt(Z=>{var ne;u&&K(-1,-1),(ne=P==null?void 0:P.onMouseLeave)==null||ne.call(P,Z)});if(D===0||V===0)return null;const ee=P.title??oU({rowType:b,ellipsis:r,children:F}),ie=H(M,i,{[`${M}-fix`]:O||A,[`${M}-fix-start`]:O,[`${M}-fix-end`]:A,[`${M}-fix-start-shadow`]:$,[`${M}-fix-start-shadow-show`]:$&&k,[`${M}-fix-end-shadow`]:C,[`${M}-fix-end-shadow-show`]:C&&_,[`${M}-ellipsis`]:r,[`${M}-with-append`]:R,[`${M}-fix-sticky`]:(O||A)&&T,[`${M}-row-hover`]:!L&&W},P.className,L==null?void 0:L.className),ae={};c&&(ae.textAlign=c);const U={...L==null?void 0:L.style,...j,...ae,...P.style,...l};let Q=F;return typeof Q=="object"&&!Array.isArray(Q)&&!a.isValidElement(Q)&&(Q=null),r&&($||C)&&(Q=a.createElement("span",{className:`${M}-content`},Q)),a.createElement(t,Hv({},L,P,{className:ie,style:U,title:ee,scope:o,onMouseEnter:B?q:void 0,onMouseLeave:B?Y:void 0,colSpan:D!==1?D:null,rowSpan:V!==1?V:null}),R,Q)},sl=a.memo(sU);function Uu(e){return e.fixed==="start"}function qu(e){return e.fixed==="end"}function n$(e,t,n,r){const o=n[e]||{},s=n[t]||{};let i=null,l=null;Uu(o)&&Uu(s)?i=r.start[e]:qu(s)&&qu(o)&&(l=r.end[t]);let c=!1,u=!1,d=0,m=0;i!==null&&(c=!n[t+1]||!Uu(n[t+1]),d=n.length*2-e,m=n.length+e),l!==null&&(u=!n[e-1]||!qu(n[e-1]),d=t,m=n.length-t);let f=0,p=0;if(c)for(let y=0;yt;y-=1)qu(n[y])||(p+=r.widths[y]||0);return{fixStart:i,fixEnd:l,fixedStartShadow:c,fixedEndShadow:u,offsetFixedStartShadow:f,offsetFixedEndShadow:p,isSticky:r.isSticky,zIndex:d,zIndexReverse:m}}const sM=a.createContext({});function Vv(){return Vv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{className:t,index:n,children:r,colSpan:o=1,rowSpan:s,align:i}=e,{prefixCls:l}=Bn(Gn,["prefixCls"]),{scrollColumnIndex:c,stickyOffsets:u,flattenColumns:d}=a.useContext(sM),f=n+o-1+1===c?o+1:o,p=a.useMemo(()=>n$(n,n+f-1,d,u),[n,f,d,u]);return a.createElement(sl,Vv({className:t,index:n,component:"td",prefixCls:l,record:null,dataIndex:null,align:i,colSpan:f,rowSpan:s,render:()=>r},p))},aU=e=>{const{children:t,...n}=e;return a.createElement("tr",n,t)},Wm=e=>{const{children:t}=e;return t};Wm.Row=aU;Wm.Cell=iU;const lU=e=>{const{children:t,stickyOffsets:n,flattenColumns:r}=e,o=Bn(Gn,"prefixCls"),s=r.length-1,i=r[s],l=a.useMemo(()=>({stickyOffsets:n,flattenColumns:r,scrollColumnIndex:i!=null&&i.scrollbar?s:null}),[i,r,s,n]);return a.createElement(sM.Provider,{value:l},a.createElement("tfoot",{className:`${o}-summary`},t))},Gu=ol(lU),iM=Wm;function cU(e){return null}function uU(e){return null}function aM(e,t,n,r,o,s,i){const l=s(t,i);e.push({record:t,indent:n,index:i,rowKey:l});const c=o==null?void 0:o.has(l);if(t&&Array.isArray(t[r])&&c)for(let u=0;u{if(n!=null&&n.size){const s=[];for(let i=0;i<(e==null?void 0:e.length);i+=1){const l=e[i];aM(s,l,0,t,n,r,i)}return s}return e==null?void 0:e.map((s,i)=>({record:s,indent:0,index:i,rowKey:r(s,i)}))},[e,t,n,r])}function cM(e,t,n,r){const o=Bn(Gn,["prefixCls","fixedInfoList","flattenColumns","expandableType","expandRowByClick","onTriggerExpand","rowClassName","expandedRowClassName","indentSize","expandIcon","expandedRowRender","expandIconColumnIndex","expandedKeys","childrenColumnName","rowExpandable","onRow"]),{flattenColumns:s,expandableType:i,expandedKeys:l,childrenColumnName:c,onTriggerExpand:u,rowExpandable:d,onRow:m,expandRowByClick:f,rowClassName:p}=o,y=i==="nest",b=i==="row"&&(!d||d(e)),x=b||y,v=l&&l.has(t),g=c&&e&&e[c],h=vt(u),$=m==null?void 0:m(e,n),C=$==null?void 0:$.onClick,N=(w,...R)=>{f&&x&&u(e,w),C==null||C(w,...R)};let S;typeof p=="string"?S=p:typeof p=="function"&&(S=p(e,n,r));const E=Vm(s);return{...o,columnsKey:E,nestExpandable:y,expanded:v,hasNestChildren:g,record:e,onTriggerExpand:h,rowSupportExpand:b,expandable:x,rowProps:{...$,className:H(S,$==null?void 0:$.className),onClick:N}}}const uM=e=>{const{prefixCls:t,children:n,component:r,cellComponent:o,className:s,expanded:i,colSpan:l,isEmpty:c,stickyOffset:u=0}=e,{scrollbarSize:d,fixHeader:m,fixColumn:f,componentWidth:p,horizonScroll:y}=Bn(Gn,["scrollbarSize","fixHeader","fixColumn","componentWidth","horizonScroll"]);let b=n;return(c?y&&p:f)&&(b=a.createElement("div",{style:{width:p-u-(m&&!c?d:0),position:"sticky",left:u,overflow:"hidden"},className:`${t}-expanded-row-fixed`},b)),a.createElement(r,{className:s,style:{display:i?null:"none"}},a.createElement(sl,{component:o,prefixCls:t,colSpan:l},b))};function dU({prefixCls:e,record:t,onExpand:n,expanded:r,expandable:o}){const s=`${e}-row-expand-icon`;if(!o)return a.createElement("span",{className:H(s,`${e}-row-spaced`)});const i=l=>{n(t,l),l.stopPropagation()};return a.createElement("span",{className:H(s,{[`${e}-row-expanded`]:r,[`${e}-row-collapsed`]:!r}),onClick:i})}function fU(e,t,n){const r=[];function o(s){(s||[]).forEach((i,l)=>{r.push(t(i,l)),o(i[n])})}return o(e),r}function dM(e,t,n,r){return typeof e=="string"?e:typeof e=="function"?e(t,n,r):""}function Rf(){return Rf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{className:t,style:n,classNames:r,styles:o,record:s,index:i,renderIndex:l,rowKey:c,rowKeys:u,indent:d=0,rowComponent:m,cellComponent:f,scopeCellComponent:p,expandedRowInfo:y}=e,b=cM(s,c,i,d),{prefixCls:x,flattenColumns:v,expandedRowClassName:g,expandedRowRender:h,rowProps:$,expanded:C,rowSupportExpand:N}=b,S=a.useRef(!1);S.current||(S.current=C);const E=dM(g,s,i,d),w=a.createElement(m,Rf({},$,{"data-row-key":c,className:H(t,`${x}-row`,`${x}-row-level-${d}`,$==null?void 0:$.className,r.row,{[E]:d>=1}),style:{...n,...$==null?void 0:$.style,...o.row}}),v.map((P,T)=>{const{render:M,dataIndex:z,className:B}=P,{key:F,fixedInfo:L,appendCellNode:j,additionalCellProps:O}=fM(b,P,T,d,i,u,y==null?void 0:y.offset);return a.createElement(sl,Rf({className:H(B,r.cell),style:o.cell,ellipsis:P.ellipsis,align:P.align,scope:P.rowScope,component:P.rowScope?p:f,prefixCls:x,key:F,record:s,index:i,renderIndex:l,dataIndex:z,render:M,shouldCellUpdate:P.shouldCellUpdate},L,{appendNode:j,additionalProps:O}))}));let R;if(N&&(S.current||C)){const P=h(s,i,d+1,C);R=a.createElement(uM,{expanded:C,className:H(`${x}-expanded-row`,`${x}-expanded-row-level-${d+1}`,E),prefixCls:x,component:m,cellComponent:f,colSpan:y?y.colSpan:v.length,isEmpty:!1,stickyOffset:y==null?void 0:y.sticky},P)}return a.createElement(a.Fragment,null,w,R)},pU=ol(mU),gU=e=>{const{columnKey:t,onColumnResize:n,title:r}=e,o=a.useRef(null);return It(()=>{o.current&&n(t,o.current.offsetWidth)},[]),a.createElement(ir,{data:t},a.createElement("td",{ref:o,style:{paddingTop:0,paddingBottom:0,borderTop:0,borderBottom:0,height:0}},a.createElement("div",{style:{height:0,overflow:"hidden",fontWeight:"bold"}},r||" ")))},hU=({prefixCls:e,columnsKey:t,onColumnResize:n,columns:r})=>{const o=a.useRef(null),{measureRowRender:s}=Bn(Gn,["measureRowRender"]),i=a.createElement("tr",{"aria-hidden":"true",className:`${e}-measure-row`,style:{height:0},ref:o},a.createElement(ir.Collection,{onBatchResize:l=>{Vc(o.current)&&l.forEach(({data:c,size:u})=>{n(c,u.offsetWidth)})}},t.map(l=>{const c=r.find(m=>m.key===l),u=c==null?void 0:c.title,d=a.isValidElement(u)?a.cloneElement(u,{ref:null}):u;return a.createElement(gU,{key:l,columnKey:l,onColumnResize:n,title:d})})));return typeof s=="function"?s(i):i},yU=e=>{const{data:t,measureColumnWidth:n}=e,{prefixCls:r,getComponent:o,onColumnResize:s,flattenColumns:i,getRowKey:l,expandedKeys:c,childrenColumnName:u,emptyNode:d,classNames:m,styles:f,expandedRowOffset:p=0,colWidths:y}=Bn(Gn,["prefixCls","getComponent","onColumnResize","flattenColumns","getRowKey","expandedKeys","childrenColumnName","emptyNode","classNames","styles","expandedRowOffset","fixedInfoList","colWidths"]),{body:b={}}=m||{},{body:x={}}=f||{},v=lM(t,u,c,l),g=a.useMemo(()=>v.map(P=>P.rowKey),[v]),h=a.useRef({renderWithProps:!1}),$=a.useMemo(()=>{const P=i.length-p;let T=0;for(let M=0;M{const{record:M,indent:z,index:B,rowKey:F}=P;return a.createElement(pU,{classNames:b,styles:x,key:F,rowKey:F,rowKeys:g,record:M,index:T,renderIndex:B,rowComponent:N,cellComponent:S,scopeCellComponent:E,indent:z,expandedRowInfo:$})}):w=a.createElement(uM,{expanded:!0,className:`${r}-placeholder`,prefixCls:r,component:N,cellComponent:S,colSpan:i.length,isEmpty:!0},d);const R=Vm(i);return a.createElement(oM.Provider,{value:h.current},a.createElement(C,{style:x.wrapper,className:H(`${r}-tbody`,b.wrapper)},n&&a.createElement(hU,{prefixCls:r,columnsKey:R,onColumnResize:s,columns:i}),w))},vU=ol(yU),tc="RC_TABLE_INTERNAL_COL_DEFINE";function bU(e){const{expandable:t,...n}=e;let r;return"expandable"in e?r={...n,...t}:r=n,r.showExpandColumn===!1&&(r.expandIconColumnIndex=-1),r}function Wv(){return Wv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{colWidths:t,columns:n,columCount:r}=e,{tableLayout:o}=Bn(Gn,["tableLayout"]),s=[],i=r||n.length;let l=!1;for(let c=i-1;c>=0;c-=1){const u=t[c],d=n&&n[c];let m,f;if(d&&(m=d[tc],o==="auto"&&(f=d.minWidth)),u||f||m||l){const{columnType:p,...y}=m||{};s.unshift(a.createElement("col",Wv({key:c,style:{width:u,minWidth:f}},y))),l=!0}}return s.length>0?a.createElement("colgroup",null,s):null};function xU(e,t){return a.useMemo(()=>{const n=[];for(let r=0;r{const{className:n,style:r,noData:o,columns:s,flattenColumns:i,colWidths:l,colGroup:c,columCount:u,stickyOffsets:d,direction:m,fixHeader:f,stickyTopOffset:p,stickyBottomOffset:y,stickyClassName:b,scrollX:x,tableLayout:v="fixed",onScroll:g,maxContentScroll:h,children:$,...C}=e,{prefixCls:N,scrollbarSize:S,isSticky:E,getComponent:w}=Bn(Gn,["prefixCls","scrollbarSize","isSticky","getComponent"]),R=w(["header","table"],"table"),P=E&&!f?0:S,T=a.useRef(null),M=a.useCallback(k=>{Rh(t,k),Rh(T,k)},[]);a.useEffect(()=>{function k(D){const{currentTarget:V,deltaX:W}=D;if(W){const{scrollLeft:K,scrollWidth:q,clientWidth:Y}=V,ee=q-Y;let ie=K+W;m==="rtl"?(ie=Math.max(-ee,ie),ie=Math.min(0,ie)):(ie=Math.min(ee,ie),ie=Math.max(0,ie)),g({currentTarget:V,scrollLeft:ie}),D.preventDefault()}}const _=T.current;return _==null||_.addEventListener("wheel",k,{passive:!1}),()=>{_==null||_.removeEventListener("wheel",k)}},[]);const z=i[i.length-1],B={fixed:z?z.fixed:null,scrollbar:!0,onHeaderCell:()=>({className:`${N}-cell-scrollbar`})},F=a.useMemo(()=>P?[...s,B]:s,[P,s]),L=a.useMemo(()=>P?[...i,B]:i,[P,i]),j=a.useMemo(()=>{const{start:k,end:_}=d;return{...d,start:k,end:[..._.map(D=>D+P),0],isSticky:E}},[P,d,E]),O=xU(l,u),A=a.useMemo(()=>{const k=!O||!O.length||O.every(_=>!_);return o||k},[o,O]);return a.createElement("div",{style:{overflow:"hidden",...E?{top:p,bottom:y}:{},...r},ref:M,className:H(n,{[b]:!!b})},a.createElement(R,{style:{tableLayout:v,minWidth:"100%",width:x}},A?c:a.createElement(mM,{colWidths:[...O,P],columCount:u+1,columns:L}),$({...C,stickyOffsets:j,columns:F,flattenColumns:L})))}),jw=a.memo($U);function Tf(){return Tf=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{cells:t,stickyOffsets:n,flattenColumns:r,rowComponent:o,cellComponent:s,onHeaderRow:i,index:l,classNames:c,styles:u}=e,{prefixCls:d}=Bn(Gn,["prefixCls"]);let m;i&&(m=i(t.map(p=>p.column),l));const f=Vm(t.map(p=>p.column));return a.createElement(o,Tf({},m,{className:c.row,style:u.row}),t.map((p,y)=>{var C;const{column:b,colStart:x,colEnd:v,colSpan:g}=p,h=n$(x,v,r,n),$=((C=b==null?void 0:b.onHeaderCell)==null?void 0:C.call(b,b))||{};return a.createElement(sl,Tf({},p,{scope:b.title?g>1?"colgroup":"col":null,ellipsis:b.ellipsis,align:b.align,component:s,prefixCls:d,key:f[y]},h,{additionalProps:$,rowType:"header"}))}))};function CU(e,t,n){const r=[];function o(i,l,c=0){r[c]=r[c]||[];let u=l;return i.filter(Boolean).map(m=>{const f={key:m.key,className:H(m.className,t.cell)||"",style:n.cell,children:m.title,column:m,colStart:u};let p=1;const y=m.children;return y&&y.length>0&&(p=o(y,u,c+1).reduce((b,x)=>b+x,0),f.hasSubColumns=!0),"colSpan"in m&&({colSpan:p}=m),"rowSpan"in m&&(f.rowSpan=m.rowSpan),f.colSpan=p,f.colEnd=f.colStart+p-1,r[c].push(f),u+=p,p})}o(e,0);const s=r.length;for(let i=0;i{!("rowSpan"in l)&&!l.hasSubColumns&&(l.rowSpan=s-i)});return r}const wU=e=>{const{stickyOffsets:t,columns:n,flattenColumns:r,onHeaderRow:o}=e,{prefixCls:s,getComponent:i,classNames:l,styles:c}=Bn(Gn,["prefixCls","getComponent","classNames","styles"]),{header:u={}}=l||{},{header:d={}}=c||{},m=a.useMemo(()=>CU(n,u,d),[n,u,d]),f=i(["header","wrapper"],"thead"),p=i(["header","row"],"tr"),y=i(["header","cell"],"th");return a.createElement(f,{className:H(`${s}-thead`,u.wrapper),style:d.wrapper},m.map((b,x)=>a.createElement(SU,{classNames:u,styles:d,key:x,flattenColumns:r,cells:b,stickyOffsets:t,rowComponent:p,cellComponent:y,onHeaderRow:o,index:x})))},Bw=ol(wU);function Lw(e,t=""){return typeof t=="number"?t:t.endsWith("%")?e*parseFloat(t)/100:null}function EU(e,t,n){return a.useMemo(()=>{if(t&&t>0){let r=0,o=0;e.forEach(m=>{const f=Lw(t,m.width);f?r+=f:o+=1});const s=Math.max(t,n);let i=Math.max(s-r,o),l=o;const c=i/o;let u=0;const d=e.map(m=>{const f={...m},p=Lw(t,f.width);if(p)f.width=p;else{const y=Math.floor(c);f.width=l===1?i:y,i-=y,l-=1}return u+=f.width,f});if(u{const y=Math.floor(f.width*m);f.width=p===d.length-1?i:y,i-=y})}return[d,Math.max(u,s)]}return[e,t]},[e,t,n])}function r$(e){return zn(e).filter(t=>a.isValidElement(t)).map(t=>{const{key:n,props:r}=t,{children:o,...s}=r,i={key:n,...s};return o&&(i.children=r$(o)),i})}function pM(e){return e.filter(t=>t&&typeof t=="object"&&!t.hidden).map(t=>{const n=t.children;return n&&n.length>0?{...t,children:pM(n)}:t})}function gM(e,t="key"){return e.filter(n=>n&&typeof n=="object").reduce((n,r,o)=>{const{fixed:s}=r,i=s===!0||s==="left"?"start":s==="right"?"end":s,l=`${t}-${o}`,c=r.children;return c&&c.length>0?[...n,...gM(c,l).map(u=>({...u,fixed:u.fixed??i}))]:[...n,{key:l,...r,fixed:i}]},[])}function IU({prefixCls:e,columns:t,children:n,expandable:r,expandedKeys:o,columnTitle:s,getRowKey:i,onTriggerExpand:l,expandIcon:c,rowExpandable:u,expandIconColumnIndex:d,expandedRowOffset:m=0,direction:f,expandRowByClick:p,columnWidth:y,fixed:b,scrollWidth:x,clientWidth:v},g){const h=a.useMemo(()=>{const w=t||r$(n)||[];return pM(w.slice())},[t,n]),$=a.useMemo(()=>{if(r){let w=h.slice();if(!w.includes(Uo)){const z=d||0,B=z===0&&(b==="right"||b==="end")?h.length:z;B>=0&&w.splice(B,0,Uo)}const R=w.indexOf(Uo);w=w.filter((z,B)=>z!==Uo||B===R);const P=h[R];let T;b?T=b:T=P?P.fixed:null;const M={[tc]:{className:`${e}-expand-icon-col`,columnType:"EXPAND_COLUMN"},title:s,fixed:T,className:`${e}-row-expand-icon-cell`,width:y,render:(z,B,F)=>{const L=i(B,F),j=o.has(L),O=u?u(B):!0,A=c({prefixCls:e,expanded:j,expandable:O,record:B,onExpand:l});return p?a.createElement("span",{onClick:k=>k.stopPropagation()},A):A}};return w.map((z,B)=>{const F=z===Uo?M:z;return Bw!==Uo)},[r,h,i,o,c,f,m]),C=a.useMemo(()=>{let w=$;return g&&(w=g(w)),w.length||(w=[{render:()=>null}]),w},[g,$,f]),N=a.useMemo(()=>gM(C),[C,f,x]),[S,E]=EU(N,x,v);return[C,S,E]}function PU(e,t,n){const r=bU(e),{expandIcon:o,expandedRowKeys:s,defaultExpandedRowKeys:i,defaultExpandAllRows:l,expandedRowRender:c,onExpand:u,onExpandedRowsChange:d,childrenColumnName:m}=r,f=o||dU,p=m||"children",y=a.useMemo(()=>c?"row":e.expandable&&e.internalHooks===cu&&e.expandable.__PARENT_RENDER_ICON__||t.some(h=>h&&typeof h=="object"&&h[p])?"nest":!1,[!!c,t]),[b,x]=a.useState(()=>i||(l?fU(t,n,p):[])),v=a.useMemo(()=>new Set(s||b||[]),[s,b]),g=a.useCallback(h=>{const $=n(h,t.indexOf(h));let C;const N=v.has($);N?(v.delete($),C=[...v]):C=[...v,$],x(C),u&&u(!N,h),d&&d(C)},[n,v,t,u,d]);return[r,y,v,f,p,g]}function NU(e,t){const n=a.useMemo(()=>e.map((r,o)=>n$(o,o,e,t)),[e,t]);return _i(()=>n,[n],(r,o)=>!ho(r,o))}function RU(e){const t=a.useRef(e),[,n]=a.useState({}),r=a.useRef(null),o=a.useRef([]);function s(i){o.current.push(i);const l=Promise.resolve();r.current=l,l.then(()=>{if(r.current===l){const c=o.current,u=t.current;o.current=[],c.forEach(d=>{t.current=d(t.current)}),r.current=null,u!==t.current&&n({})}})}return a.useEffect(()=>()=>{r.current=null},[]),[t.current,s]}function TU(e){const t=a.useRef(null),n=a.useRef(null);function r(){clearTimeout(n.current)}function o(i){t.current=i,r(),n.current=setTimeout(()=>{t.current=null,n.current=void 0},100)}function s(){return t.current}return a.useEffect(()=>r,[]),[o,s]}function MU(){const[e,t]=a.useState(-1),[n,r]=a.useState(-1),o=a.useCallback((s,i)=>{t(s),r(i)},[]);return[e,n,o]}const kw=lr()?window:null;function OU(e,t){const{offsetHeader:n=0,offsetSummary:r=0,offsetScroll:o=0,getContainer:s=()=>kw}=typeof e=="object"?e:{},i=s()||kw,l=!!e;return a.useMemo(()=>({isSticky:l,stickyClassName:l?`${t}-sticky-holder`:"",offsetHeader:n,offsetSummary:r,offsetScroll:o,container:i}),[l,o,n,r,t,i])}function _U(e,t){return a.useMemo(()=>{const r=t.length,o=(l,c,u)=>{const d=[];let m=0;for(let f=l;f!==c;f+=u)d.push(m),t[f].fixed&&(m+=e[f]||0);return d},s=o(0,r,1),i=o(r-1,-1,-1).reverse();return{start:s,end:i,widths:e}},[e,t])}const Aw=e=>{const{children:t,className:n,style:r}=e;return a.createElement("div",{className:n,style:r},t)};function Dw(e){const n=go(e).getBoundingClientRect(),r=document.documentElement;return{left:n.left+(window.pageXOffset||r.scrollLeft)-(r.clientLeft||document.body.clientLeft||0),top:n.top+(window.pageYOffset||r.scrollTop)-(r.clientTop||document.body.clientTop||0)}}const Fw="mouseup",Hw="mousemove",sa="scroll",Vw="resize",zU=(e,t)=>{var S,E;const{scrollBodyRef:n,onScroll:r,offsetScroll:o,container:s,direction:i}=e,l=Bn(Gn,"prefixCls"),c=((S=n.current)==null?void 0:S.scrollWidth)||0,u=((E=n.current)==null?void 0:E.clientWidth)||0,d=c&&u*(u/c),m=a.useRef(null),[f,p]=RU({scrollLeft:0,isHiddenScrollBar:!0}),y=a.useRef({delta:0,x:0}),[b,x]=a.useState(!1),v=a.useRef(null);a.useEffect(()=>()=>{Ct.cancel(v.current)},[]);const g=()=>{x(!1)},h=w=>{w.persist(),y.current.delta=w.pageX-f.scrollLeft,y.current.x=0,x(!0),w.preventDefault()},$=w=>{const{buttons:R}=w||(window==null?void 0:window.event);if(!b||R===0){b&&x(!1);return}let P=y.current.x+w.pageX-y.current.x-y.current.delta;const T=i==="rtl";P=Math.max(T?d-u:0,Math.min(T?0:u-d,P)),(!T||Math.abs(P)+Math.abs(d){Ct.cancel(v.current),v.current=Ct(()=>{if(!n.current)return;const w=Dw(n.current).top,R=w+n.current.offsetHeight,P=s===window?document.documentElement.scrollTop+window.innerHeight:Dw(s).top+s.clientHeight;R-LS()<=P||w>=P-o?p(T=>({...T,isHiddenScrollBar:!0})):p(T=>({...T,isHiddenScrollBar:!1}))})},N=w=>{p(R=>({...R,scrollLeft:w/c*u||0}))};return a.useImperativeHandle(t,()=>({setScrollLeft:N,checkScrollBarVisible:C})),a.useEffect(()=>(document.body.addEventListener(Fw,g,!1),document.body.addEventListener(Hw,$,!1),C(),()=>{document.body.removeEventListener(Fw,g),document.body.removeEventListener(Hw,$)}),[d,b]),a.useEffect(()=>{if(n.current){const w=[];let R=go(n.current);for(;R;)w.push(R),R=R.parentElement;return w.forEach(P=>{P.addEventListener(sa,C,!1)}),window.addEventListener(Vw,C,!1),window.addEventListener(sa,C,!1),s.addEventListener(sa,C,!1),()=>{w.forEach(P=>{P.removeEventListener(sa,C)}),window.removeEventListener(Vw,C),window.removeEventListener(sa,C),s.removeEventListener(sa,C)}}},[s]),a.useEffect(()=>{f.isHiddenScrollBar||p(w=>{const R=n.current;return R?{...w,scrollLeft:R.scrollLeft/R.scrollWidth*R.clientWidth}:w})},[f.isHiddenScrollBar]),c<=u||!d||f.isHiddenScrollBar?null:a.createElement("div",{style:{height:LS(),width:u,bottom:o},className:`${l}-sticky-scroll`},a.createElement("div",{onMouseDown:h,ref:m,className:H(`${l}-sticky-scroll-bar`,{[`${l}-sticky-scroll-bar-active`]:b}),style:{width:`${d}px`,transform:`translate3d(${f.scrollLeft}px, 0, 0)`}}))},jU=a.forwardRef(zU);function ys(){return ys=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var Wt,en;const n={rowKey:"key",prefixCls:hM,emptyText:kU,...e},{prefixCls:r,className:o,rowClassName:s,style:i,classNames:l,styles:c,data:u,rowKey:d,scroll:m,tableLayout:f,direction:p,title:y,footer:b,summary:x,caption:v,id:g,showHeader:h,components:$,emptyText:C,onRow:N,onHeaderRow:S,measureRowRender:E,onScroll:w,internalHooks:R,transformColumns:P,internalRefs:T,tailor:M,getContainerWidth:z,sticky:B,rowHoverable:F=!0}=n,L=u||BU,j=!!L.length,O=R===cu,A=a.useCallback((tt,wt)=>Kn($,tt)||wt,[$]),k=a.useMemo(()=>typeof d=="function"?d:tt=>tt&&tt[d],[d]),_=A(["body"]),[D,V,W]=MU(),[K,q,Y,ee,ie,ae]=PU(n,L,k),U=m==null?void 0:m.x,[Q,Z]=a.useState(0),[ne,oe,le]=IU({...n,...K,expandable:!!K.expandedRowRender,columnTitle:K.columnTitle,expandedKeys:Y,getRowKey:k,onTriggerExpand:ae,expandIcon:ee,expandIconColumnIndex:K.expandIconColumnIndex,direction:p,scrollWidth:O&&M&&typeof U=="number"?U:null,clientWidth:Q},O?P:null),re=le??U,X=a.useMemo(()=>({columns:ne,flattenColumns:oe}),[ne,oe]),se=a.useRef(null),ge=a.useRef(null),de=a.useRef(null),Se=a.useRef(null);a.useImperativeHandle(t,()=>({nativeElement:se.current,scrollTo:tt=>{var wt,Ye;if(de.current instanceof HTMLElement){const{index:Et,top:Kt,key:pr,offset:Dr,align:ko="nearest"}=tt;if(ZK(Kt))(wt=de.current)==null||wt.scrollTo({top:Kt});else{const Ao=pr??k(L[Et]),Do=de.current.querySelector(`[data-row-key="${Ao}"]`);if(Do&&(Do.scrollIntoView({block:ko}),Dr)){const Qe=de.current;Qe.scrollTo({top:Qe.scrollTop+Dr})}}}else(Ye=de.current)!=null&&Ye.scrollTo&&de.current.scrollTo(tt)}}));const ue=a.useRef(null),[be,Ne]=a.useState(!1),[we,ze]=a.useState(!1),[he,ke]=a.useState(new Map),Ce=Vm(oe).map(tt=>he.get(tt)),Me=a.useMemo(()=>Ce,[Ce.join("_")]),xe=_U(Me,oe),Ee=m&&Fv(m.y),Ve=m&&Fv(re)||!!K.fixed,qe=Ve&&oe.some(({fixed:tt})=>tt),me=a.useRef(null),{isSticky:Re,offsetHeader:Te,offsetSummary:Ue,offsetScroll:Ge,stickyClassName:Fe,container:et}=OU(B,r),ve=a.useMemo(()=>x==null?void 0:x(L),[x,L]),je=(Ee||Re)&&a.isValidElement(ve)&&ve.type===Wm&&ve.props.fixed;let ce,Pe,pe;Ee&&(Pe={overflowY:j?"scroll":"auto",maxHeight:m.y}),Ve&&(ce={overflowX:"auto"},Ee||(Pe={overflowY:"hidden"}),pe={width:re===!0?"auto":re,minWidth:"100%"});const $e=a.useCallback((tt,wt)=>{ke(Ye=>{if(Ye.get(tt)!==wt){const Et=new Map(Ye);return Et.set(tt,wt),Et}return Ye})},[]),[_e,Ie]=TU(),[Be]=a.useState(()=>new WeakMap);function te(tt,wt){if(!wt)return;if(typeof wt=="function"){wt(tt);return}const Ye=Be.get(wt);if(Ye&&clearTimeout(Ye),wt.scrollLeft!==tt){wt.scrollLeft=tt;const Et=setTimeout(()=>{wt.scrollLeft!==tt&&(wt.scrollLeft=tt)},0);Be.set(wt,Et)}}const[ye,Ae]=a.useState([0,0]),Je=vt(({currentTarget:tt,scrollLeft:wt})=>{var pr;const Ye=typeof wt=="number"?wt:tt.scrollLeft,Et=tt||LU;(!Ie()||Ie()===Et)&&(_e(Et),te(Ye,ge.current),te(Ye,de.current),te(Ye,ue.current),te(Ye,(pr=me.current)==null?void 0:pr.setScrollLeft));const Kt=tt||ge.current;if(Kt){const Dr=O&&M&&typeof re=="number"?re:Kt.scrollWidth,ko=Kt.clientWidth,Ao=Math.abs(Ye);if(Ae(Do=>{const Qe=[Ao,Dr-ko];return ho(Do,Qe)?Do:Qe}),Dr===ko){Ne(!1),ze(!1);return}Ne(Ao>0),ze(Ao{Je(tt),w==null||w(tt)}),ht=()=>{var tt;Ve&&de.current?Je({currentTarget:go(de.current),scrollLeft:(tt=de.current)==null?void 0:tt.scrollLeft}):(Ne(!1),ze(!1))},Nt=tt=>{var Ye,Et;(Ye=me.current)==null||Ye.checkScrollBarVisible();let wt=tt??((Et=se.current)==null?void 0:Et.offsetWidth)??0;O&&z&&se.current&&(wt=z(se.current,wt)||wt),wt!==Q&&(ht(),Z(wt))};It(()=>{Ve&&Nt()},[Ve]);const yt=a.useRef(!1);a.useEffect(()=>{yt.current&&ht()},[Ve,u,ne.length]),a.useEffect(()=>{yt.current=!0},[]);const[at,Ze]=a.useState(0);It(()=>{(!M||!O)&&(de.current instanceof Element?Ze(zh(de.current).width):Ze(zh(Se.current).width))},[]),a.useEffect(()=>{O&&T&&(T.body.current=de.current)});const De=a.useCallback(tt=>a.createElement(a.Fragment,null,a.createElement(Bw,tt),je==="top"&&a.createElement(Gu,tt,ve)),[je,ve]),Le=a.useCallback(tt=>a.createElement(Gu,tt,ve),[ve]),Ke=A(["table"],"table"),lt=a.useMemo(()=>f||(qe?re==="max-content"?"auto":"fixed":Ee||Re||oe.some(({ellipsis:tt})=>tt)?"fixed":"auto"),[Ee,qe,oe,f,Re]);let _t;const ft={colWidths:Me,columCount:oe.length,stickyOffsets:xe,onHeaderRow:S,fixHeader:Ee,scroll:m},xt=a.useMemo(()=>j?null:typeof C=="function"?C():C,[j,C]),jt=a.createElement(vU,{data:L,measureColumnWidth:Ee||Ve||Re}),pt=a.createElement(mM,{colWidths:oe.map(({width:tt})=>tt),columns:oe}),qt=v!=null?a.createElement("caption",{className:`${r}-caption`},v):void 0,cn=Nn(n,{data:!0}),hn=Nn(n,{aria:!0});if(Ee||Re){let tt;typeof _=="function"?(tt=_(L,{scrollbarSize:at,ref:de,onScroll:Je}),ft.colWidths=oe.map(({width:Ye},Et)=>{const Kt=Et===oe.length-1?Ye-at:Ye;return typeof Kt=="number"&&!Number.isNaN(Kt)?Kt:0})):tt=a.createElement("div",{style:{...ce,...Pe},onScroll:St,ref:de,className:`${r}-body`},a.createElement(Ke,ys({style:{...pe,tableLayout:lt}},hn),qt,pt,jt,!je&&ve&&a.createElement(Gu,{stickyOffsets:xe,flattenColumns:oe},ve)));const wt={noData:!L.length,maxContentScroll:Ve&&re==="max-content",...ft,...X,direction:p,stickyClassName:Fe,scrollX:re,tableLayout:lt,onScroll:Je};_t=a.createElement(a.Fragment,null,h!==!1&&a.createElement(jw,ys({},wt,{stickyTopOffset:Te,className:`${r}-header`,ref:ge,colGroup:pt}),De),tt,je&&je!=="top"&&a.createElement(jw,ys({},wt,{stickyBottomOffset:Ue,className:`${r}-summary`,ref:ue,colGroup:pt}),Le),Re&&de.current&&de.current instanceof Element&&a.createElement(jU,{ref:me,offsetScroll:Ge,scrollBodyRef:de,onScroll:Je,container:et,direction:p}))}else _t=a.createElement("div",{style:{...ce,...Pe,...c==null?void 0:c.content},className:H(`${r}-content`,l==null?void 0:l.content),onScroll:Je,ref:de},a.createElement(Ke,ys({style:{...pe,tableLayout:lt}},hn),qt,pt,h!==!1&&a.createElement(Bw,ys({},ft,X)),jt,ve&&a.createElement(Gu,{stickyOffsets:xe,flattenColumns:oe},ve)));const Cr={...i};Re&&(Cr["--columns-count"]=oe.length);let mr=a.createElement("div",ys({className:H(r,o,{[`${r}-rtl`]:p==="rtl",[`${r}-fix-start-shadow`]:Ve,[`${r}-fix-end-shadow`]:Ve,[`${r}-fix-start-shadow-show`]:Ve&&be,[`${r}-fix-end-shadow-show`]:Ve&&we,[`${r}-layout-fixed`]:f==="fixed",[`${r}-fixed-header`]:Ee,[`${r}-fixed-column`]:qe,[`${r}-scroll-horizontal`]:Ve,[`${r}-has-fix-start`]:(Wt=oe[0])==null?void 0:Wt.fixed,[`${r}-has-fix-end`]:((en=oe[oe.length-1])==null?void 0:en.fixed)==="end"}),style:Cr,id:g,ref:se},cn),y&&a.createElement(Aw,{className:H(`${r}-title`,l==null?void 0:l.title),style:c==null?void 0:c.title},y(L)),a.createElement("div",{ref:Se,className:H(`${r}-container`,l==null?void 0:l.section),style:c==null?void 0:c.section},_t),b&&a.createElement(Aw,{className:H(`${r}-footer`,l==null?void 0:l.footer),style:c==null?void 0:c.footer},b(L)));Ve&&(mr=a.createElement(ir,{onResize:({offsetWidth:tt})=>Nt(tt)},mr));const wr=NU(oe,xe),mt=a.useMemo(()=>({scrollX:re,scrollInfo:ye,classNames:l,styles:c,prefixCls:r,getComponent:A,scrollbarSize:at,direction:p,fixedInfoList:wr,isSticky:Re,componentWidth:Q,fixHeader:Ee,fixColumn:qe,horizonScroll:Ve,tableLayout:lt,rowClassName:s,expandedRowClassName:K.expandedRowClassName,expandIcon:ee,expandableType:q,expandRowByClick:K.expandRowByClick,expandedRowRender:K.expandedRowRender,expandedRowOffset:K.expandedRowOffset,onTriggerExpand:ae,expandIconColumnIndex:K.expandIconColumnIndex,indentSize:K.indentSize,allColumnsFixedLeft:oe.every(tt=>tt.fixed==="start"),emptyNode:xt,columns:ne,flattenColumns:oe,onColumnResize:$e,colWidths:Me,hoverStartRow:D,hoverEndRow:V,onHover:W,rowExpandable:K.rowExpandable,onRow:N,getRowKey:k,expandedKeys:Y,childrenColumnName:ie,rowHoverable:F,measureRowRender:E}),[re,ye,l,c,r,A,at,p,wr,Re,Q,Ee,qe,Ve,lt,s,K.expandedRowClassName,ee,q,K.expandRowByClick,K.expandedRowRender,K.expandedRowOffset,ae,K.expandIconColumnIndex,K.indentSize,xt,ne,oe,$e,Me,D,V,W,K.rowExpandable,N,k,Y,ie,F,E]);return a.createElement(Gn.Provider,{value:mt},mr)},DU=a.forwardRef(AU),yM=e=>rM(DU,e),il=yM();il.EXPAND_COLUMN=Uo;il.INTERNAL_HOOKS=cu;il.Column=cU;il.ColumnGroup=uU;il.Summary=iM;const o$=t$(null),vM=t$(null);function Kv(){return Kv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{rowInfo:t,column:n,colIndex:r,indent:o,index:s,component:i,renderIndex:l,record:c,style:u,className:d,inverse:m,getHeight:f}=e,{render:p,dataIndex:y,className:b,width:x}=n,{columnsOffset:v}=Bn(vM,["columnsOffset"]),{key:g,fixedInfo:h,appendCellNode:$,additionalCellProps:C}=fM(t,n,r,o,s),{style:N,colSpan:S=1,rowSpan:E=1}=C,w=r-1,R=FU(w,S,v),P=S>1?x-R:0,T={...N,...u,flex:`0 0 ${R}px`,width:`${R}px`,marginRight:P,pointerEvents:"auto"},M=a.useMemo(()=>m?E<=1:S===0||E===0||E>1,[E,S,m]);M?T.visibility="hidden":m&&(T.height=f==null?void 0:f(E));const z=M?()=>null:p,B={};return(E===0||S===0)&&(B.rowSpan=1,B.colSpan=1),a.createElement(sl,Kv({className:H(b,d),ellipsis:n.ellipsis,align:n.align,scope:n.rowScope,component:i,prefixCls:t.prefixCls,key:g,record:c,index:s,renderIndex:l,dataIndex:y,render:z,shouldCellUpdate:n.shouldCellUpdate},h,{appendNode:$,additionalProps:{...C,style:T,...B}}))};function Uv(){return Uv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{var L,j;const{data:n,index:r,className:o,rowKey:s,style:i,extra:l,getHeight:c,...u}=e,{record:d,indent:m,index:f}=n,{scrollX:p,flattenColumns:y,prefixCls:b,fixColumn:x,componentWidth:v,classNames:g,styles:h}=Bn(Gn,["prefixCls","flattenColumns","fixColumn","componentWidth","scrollX","classNames","styles"]),{getComponent:$}=Bn(o$,["getComponent"]),C=cM(d,s,r,m),N=$(["body","row"],"div"),S=$(["body","cell"],"div"),{rowSupportExpand:E,expanded:w,rowProps:R,expandedRowRender:P,expandedRowClassName:T}=C,M=dM(T,d,r,m);let z;if(E&&w){const O=P(d,r,m+1,w);let A={};x&&(A={style:{"--virtual-width":`${v}px`}});const k=`${b}-expanded-row-cell`;z=a.createElement(N,{className:H(`${b}-expanded-row`,`${b}-expanded-row-level-${m+1}`,M)},a.createElement(sl,{component:S,prefixCls:b,className:H(k,{[`${k}-fixed`]:x}),additionalProps:A},O))}const B={...i,width:p};l&&(B.position="absolute",B.pointerEvents="none");const F=a.createElement(N,Uv({},R,u,{"data-row-key":s,ref:E?null:t,className:H(o,`${b}-row`,R==null?void 0:R.className,(L=g==null?void 0:g.body)==null?void 0:L.row,{[M]:m>=1,[`${b}-row-extra`]:l}),style:{...B,...R==null?void 0:R.style,...(j=h==null?void 0:h.body)==null?void 0:j.row}}),y.map((O,A)=>{var k,_;return a.createElement(HU,{key:A,className:(k=g==null?void 0:g.body)==null?void 0:k.cell,style:(_=h==null?void 0:h.body)==null?void 0:_.cell,component:S,rowInfo:C,column:O,colIndex:A,indent:m,index:r,renderIndex:f,record:d,inverse:l,getHeight:c})}));return E?a.createElement("div",{ref:t},F,z):F}),Ww=ol(VU),WU={start:"top",end:"bottom",nearest:"auto"},KU=a.forwardRef((e,t)=>{const{data:n,onScroll:r}=e,{flattenColumns:o,onColumnResize:s,getRowKey:i,expandedKeys:l,prefixCls:c,childrenColumnName:u,scrollX:d,direction:m}=Bn(Gn,["flattenColumns","onColumnResize","getRowKey","prefixCls","expandedKeys","childrenColumnName","scrollX","direction"]),{sticky:f,scrollY:p,listItemHeight:y,getComponent:b,onScroll:x}=Bn(o$),v=a.useRef(null),g=lM(n,u,l,i),h=a.useMemo(()=>{let P=0;return o.map(({width:T,minWidth:M,key:z})=>{const B=Math.max(T||0,M||0);return P+=B,[z,B,P]})},[o]),$=a.useMemo(()=>h.map(P=>P[2]),[h]);a.useEffect(()=>{h.forEach(([P,T])=>{s(P,T)})},[h]),a.useImperativeHandle(t,()=>{var T;const P={scrollTo:M=>{var j;const{align:z,offset:B,...F}=M,L=WU[z]??(B?"top":"auto");(j=v.current)==null||j.scrollTo({...F,offset:B,align:L})},nativeElement:(T=v.current)==null?void 0:T.nativeElement};return Object.defineProperty(P,"scrollLeft",{get:()=>{var M;return((M=v.current)==null?void 0:M.getScrollInfo().x)||0},set:M=>{var z;(z=v.current)==null||z.scrollTo({left:M})}}),Object.defineProperty(P,"scrollTop",{get:()=>{var M;return((M=v.current)==null?void 0:M.getScrollInfo().y)||0},set:M=>{var z;(z=v.current)==null||z.scrollTo({top:M})}}),P});const C=(P,T)=>{var B;const M=(B=g[T])==null?void 0:B.record,{onCell:z}=P;if(z){const F=z(M,T);return(F==null?void 0:F.rowSpan)??1}return 1},N=P=>{const{start:T,end:M,getSize:z,offsetY:B}=P;if(M<0)return null;let F=o.filter(_=>C(_,T)===0),L=T;for(let _=T;_>=0;_-=1)if(F=F.filter(D=>C(D,_)===0),!F.length){L=_;break}let j=o.filter(_=>C(_,M)!==1),O=M;for(let _=M;_C(D,_)!==1),!j.length){O=Math.max(_-1,M);break}const A=[];for(let _=L;_<=O;_+=1)g[_]&&o.some(V=>C(V,_)>1)&&A.push(_);return A.map(_=>{const D=g[_],V=i(D.record,_),W=q=>{const Y=_+q-1,ee=g[Y];if(!ee||!ee.record){const U=Math.min(Y,g.length-1),Q=g[U],Z=i(Q.record,U),ne=z(V,Z);return ne.bottom-ne.top}const ie=i(ee.record,Y),ae=z(V,ie);return ae.bottom-ae.top},K=z(V);return a.createElement(Ww,{key:_,data:D,rowKey:V,index:_,style:{top:-B+K.top},extra:!0,getHeight:W})})},S=a.useMemo(()=>({columnsOffset:$}),[$]),E=`${c}-tbody`,w=b(["body","wrapper"]),R={};return f&&(R.position="sticky",R.bottom=0,typeof f=="object"&&f.offsetScroll&&(R.bottom=f.offsetScroll)),a.createElement(vM.Provider,{value:S},a.createElement(Tm,{fullHeight:!1,ref:v,prefixCls:`${E}-virtual`,styles:{horizontalScrollBar:R},className:E,height:p,itemHeight:y||24,data:g,itemKey:P=>i(P.record),component:w,scrollWidth:d,direction:m,onVirtualScroll:({x:P})=>{var T;r({currentTarget:(T=v.current)==null?void 0:T.nativeElement,scrollLeft:P})},onScroll:x,extraRender:N},(P,T,M)=>{const z=i(P.record,T);return a.createElement(Ww,{data:P,rowKey:z,index:T,style:M.style})}))}),UU=ol(KU);function qv(){return qv=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{ref:n,onScroll:r}=t;return a.createElement(UU,{ref:n,data:e,onScroll:r})},GU=(e,t)=>{const{data:n,columns:r,scroll:o,sticky:s,prefixCls:i=hM,className:l,listItemHeight:c,components:u,onScroll:d}=e;let{x:m,y:f}=o||{};typeof m!="number"&&(m=1),typeof f!="number"&&(f=500);const p=vt((x,v)=>Kn(u,x)||v),y=vt(d),b=a.useMemo(()=>({sticky:s,scrollY:f,listItemHeight:c,getComponent:p,onScroll:y}),[s,f,c,p,y]);return a.createElement(o$.Provider,{value:b},a.createElement(il,qv({},e,{className:H(l,`${i}-virtual`),scroll:{...o,x:m},components:{...u,body:n!=null&&n.length?qU:void 0},columns:r,internalHooks:cu,tailor:!0,ref:t})))},XU=a.forwardRef(GU),bM=e=>rM(XU,e);bM();const YU=e=>null,QU=e=>null,Wo={},Gv="SELECT_ALL",Xv="SELECT_INVERT",Yv="SELECT_NONE",bg=[],xM=(e,t,n=[])=>((t||[]).forEach(r=>{n.push(r),dt(r)&&e in r&&xM(e,r[e],n)}),n),JU=(e,t)=>{const{preserveSelectedRowKeys:n,selectedRowKeys:r,defaultSelectedRowKeys:o,getCheckboxProps:s,getTitleCheckboxProps:i,onChange:l,onSelect:c,onSelectAll:u,onSelectInvert:d,onSelectNone:m,onSelectMultiple:f,columnWidth:p,type:y,selections:b,fixed:x,renderCell:v,hideSelectAll:g,checkStrictly:h=!0}=t||{},{prefixCls:$,data:C,pageData:N,getRecordByKey:S,getRowKey:E,expandType:w,childrenColumnName:R,locale:P,getPopupContainer:T}=e,M=yo(),[z,B]=Jj(Q=>Q),[F,L]=nn(o||bg,r),j=F??bg,O=a.useRef(new Map),A=a.useCallback(Q=>{if(n){const Z=new Map;Q.forEach(ne=>{let oe=S(ne);!oe&&O.current.has(ne)&&(oe=O.current.get(ne)),Z.set(ne,oe)}),O.current=Z}},[S,n]);a.useEffect(()=>{A(j)},[j,A]);const k=a.useMemo(()=>xM(R,N),[R,N]),{keyEntities:_}=a.useMemo(()=>{if(h)return{keyEntities:null};let Q=C;if(n){const Z=new Set(k.map(E)),ne=Array.from(O.current).reduce((oe,[le,re])=>Z.has(le)?oe:oe.concat(re),[]);Q=[].concat($t(Q),$t(ne))}return Xx(Q,{externalGetKey:E,childrenPropName:R})},[C,E,h,R,n,k]),D=a.useMemo(()=>{const Q=new Map;return k.forEach((Z,ne)=>{const oe=E(Z,ne),le=(s?s(Z):null)||{};Q.set(oe,le)}),Q},[k,E,s]),V=a.useCallback(Q=>{const Z=E(Q);let ne;return D.has(Z)?ne=D.get(E(Q)):ne=s?s(Q):void 0,!!(ne!=null&&ne.disabled)},[D,E]),[W,K]=a.useMemo(()=>{if(h)return[j,[]];const{checkedKeys:Q,halfCheckedKeys:Z}=Ta(j,!0,_,V);return[Q||[],Z]},[j,h,_,V]),q=a.useMemo(()=>{const Q=y==="radio"?W.slice(0,1):W;return new Set(Q)},[W,y]),Y=a.useMemo(()=>y==="radio"?new Set:new Set(K),[K,y]);a.useEffect(()=>{t||L(bg)},[!!t]);const ee=a.useCallback((Q,Z)=>{let ne,oe;A(Q),n?(ne=Q,oe=Q.map(le=>O.current.get(le))):(ne=[],oe=[],Q.forEach(le=>{const re=S(le);re!==void 0&&(ne.push(le),oe.push(re))})),L(ne),l==null||l(ne,oe,{type:Z})},[L,S,l,n]),ie=a.useCallback((Q,Z,ne,oe)=>{if(c){const le=ne.map(S);c(S(Q),Z,le,oe)}ee(ne,"single")},[c,S,ee]),ae=a.useMemo(()=>!b||g?null:(b===!0?[Gv,Xv,Yv]:b).map(Z=>{let ne;return Z===Gv?ne={key:"all",text:P.selectionAll,onSelect(){ee(C.reduce((oe,le,re)=>{const X=E(le,re),se=D.get(X);return(!(se!=null&&se.disabled)||q.has(X))&&oe.push(X),oe},[]),"all")}}:Z===Xv?ne={key:"invert",text:P.selectInvert,onSelect(){const oe=new Set(q);N.forEach((re,X)=>{const se=E(re,X),ge=D.get(se);ge!=null&&ge.disabled||(oe.has(se)?oe.delete(se):oe.add(se))});const le=Array.from(oe);d&&(M.deprecated(!1,"onSelectInvert","onChange"),d(le)),ee(le,"invert")}}:Z===Yv?ne={key:"none",text:P.selectNone,onSelect(){m==null||m(),ee(Array.from(q).filter(oe=>{const le=D.get(oe);return le==null?void 0:le.disabled}),"none")}}:ne=Z,{...ne,onSelect:oe=>{var le;(le=ne.onSelect)==null||le.call(ne,oe),B(null)}}}),[b,g,P.selectionAll,P.selectInvert,P.selectNone,D,q,C,N,E,d,ee]);return[a.useCallback(Q=>{var Oe;if(!t)return Q.filter(Ce=>Ce!==Wo);let Z=$t(Q);const ne=new Set(q),oe=k.reduce((Ce,Me,xe)=>{const Ee=E(Me,xe);return D.get(Ee).disabled||Ce.push(Ee),Ce},[]),le=oe.every(Ce=>ne.has(Ce)),re=oe.some(Ce=>ne.has(Ce)),X=()=>{const Ce=[];le?oe.forEach(xe=>{ne.delete(xe),Ce.push(xe)}):oe.forEach(xe=>{ne.has(xe)||(ne.add(xe),Ce.push(xe))});const Me=Array.from(ne);u==null||u(!le,Me.map(S),Ce.map(S)),ee(Me,"all"),B(null)};let se,ge;if(y!=="radio"){let Ce;if(ae){const Te={getPopupContainer:T,items:ae.map((Ue,Ge)=>{const{key:Fe,text:et,onSelect:ve}=Ue;return{key:Fe??Ge,onClick:()=>{ve==null||ve(oe)},label:et}})};Ce=a.createElement("div",{className:`${$}-selection-extra`},a.createElement(Jx,{menu:Te,getPopupContainer:T},a.createElement("span",null,a.createElement(Rx,null))))}const Me=k.reduce((Te,Ue,Ge)=>{const Fe=E(Ue,Ge),et=D.get(Fe)||{},ve={checked:ne.has(Fe),...et};return ve.disabled&&Te.push(ve),Te},[]),xe=!!Me.length&&Me.length===k.length,Ee=xe&&Me.every(({checked:Te})=>Te),Ve=xe&&Me.some(({checked:Te})=>Te),qe=(i==null?void 0:i())||{},{onChange:me,disabled:Re}=qe;ge=a.createElement(as,{"aria-label":Ce?"Custom selection":"Select all",...qe,checked:xe?Ee:!!k.length&&le,indeterminate:xe?!Ee&&Ve:!le&&re,onChange:Te=>{X(),me==null||me(Te)},disabled:Re??(k.length===0||xe),skipGroup:!0}),se=!g&&a.createElement("div",{className:`${$}-selection`},ge,Ce)}let de;y==="radio"?de=(Ce,Me,xe)=>{const Ee=E(Me,xe),Ve=ne.has(Ee),qe=D.get(Ee),me=`Select row ${xe+1}`;return{node:a.createElement(ou,{"aria-label":me,...qe,checked:Ve,onClick:Re=>{var Te;Re.stopPropagation(),(Te=qe==null?void 0:qe.onClick)==null||Te.call(qe,Re)},onChange:Re=>{var Te;ne.has(Ee)||ie(Ee,!0,[Ee],Re.nativeEvent),(Te=qe==null?void 0:qe.onChange)==null||Te.call(qe,Re)}}),checked:Ve}}:de=(Ce,Me,xe)=>{const Ee=E(Me,xe),Ve=ne.has(Ee),qe=Y.has(Ee),me=D.get(Ee);let Re;w==="nest"?Re=qe:Re=(me==null?void 0:me.indeterminate)??qe;const Te=Ve?`Row ${xe+1} selected`:`Select row ${xe+1}`;return{node:a.createElement(as,{"aria-label":Te,...me,indeterminate:Re,checked:Ve,skipGroup:!0,onClick:Ue=>{var Ge;Ue.stopPropagation(),(Ge=me==null?void 0:me.onClick)==null||Ge.call(me,Ue)},onChange:Ue=>{var je;const{nativeEvent:Ge}=Ue,{shiftKey:Fe}=Ge,et=oe.indexOf(Ee),ve=q.size>0&&oe.some(ce=>q.has(ce));if(Fe&&h&&ve){const ce=z(et,oe,ne),Pe=Array.from(ne);f==null||f(!Ve,Pe.map(S),ce.map(S)),ee(Pe,"multiple")}else{const ce=W;if(h){const Pe=Ve?wo(ce,Ee):Fo(ce,Ee);ie(Ee,!Ve,Pe,Ge)}else{const Pe=Ta([].concat($t(ce),[Ee]),!0,_,V),{checkedKeys:pe,halfCheckedKeys:$e}=Pe;let _e=pe;if(Ve){const Ie=new Set(pe);Ie.delete(Ee),_e=Ta(Array.from(Ie),{halfCheckedKeys:$e},_,V).checkedKeys}ie(Ee,!Ve,_e,Ge)}}B(Ve?null:et),(je=me==null?void 0:me.onChange)==null||je.call(me,Ue)}}),checked:Ve}};const Se=(Ce,Me,xe)=>{const{node:Ee,checked:Ve}=de(Ce,Me,xe);return v?v(Ve,Me,xe,Ee):Ee};if(!Z.includes(Wo))if(Z.findIndex(Ce=>{var Me;return((Me=Ce[tc])==null?void 0:Me.columnType)==="EXPAND_COLUMN"})===0){const[Ce,...Me]=Z;Z=[Ce,Wo].concat($t(Me))}else Z=[Wo].concat($t(Z));const ue=Z.indexOf(Wo);Z=Z.filter((Ce,Me)=>Ce!==Wo||Me===ue);const be=Z[ue-1],Ne=Z[ue+1];let we=x;we===void 0&&((Ne==null?void 0:Ne.fixed)!==void 0?we=Ne.fixed:(be==null?void 0:be.fixed)!==void 0&&(we=be.fixed)),we&&be&&((Oe=be[tc])==null?void 0:Oe.columnType)==="EXPAND_COLUMN"&&be.fixed===void 0&&(be.fixed=we);const ze=H(`${$}-selection-col`,{[`${$}-selection-col-with-dropdown`]:b&&y==="checkbox"}),he=()=>t!=null&&t.columnTitle?bt(t.columnTitle)?t.columnTitle(ge):t.columnTitle:se,ke={fixed:we,width:p,className:`${$}-selection-column`,title:he(),render:Se,onCell:t.onCell,align:t.align,[tc]:{className:ze}};return Z.map(Ce=>Ce===Wo?ke:Ce)},[E,k,t,W,q,Y,p,ae,w,D,f,ie,V]),q]};function ZU(e){return t=>{const{prefixCls:n,onExpand:r,record:o,expanded:s,expandable:i}=t,l=`${n}-row-expand-icon`;return a.createElement("button",{type:"button",onClick:c=>{r(o,c),c.stopPropagation()},className:H(l,{[`${l}-spaced`]:!i,[`${l}-expanded`]:i&&s,[`${l}-collapsed`]:i&&!s}),"aria-label":s?e.collapse:e.expand,"aria-expanded":s})}}const eq=e=>{const t={};for(const[n,r]of Object.entries(e))bn(r)&&(t[n]=r);return t},tq=(e,t)=>J.useMemo(()=>({...e,filters:eq(t)}),[e,t]);function nq(e){return(n,r)=>{const o=n.querySelector(`.${e}-container`);let s=r;if(o){const i=getComputedStyle(o),l=Number.parseInt(i.borderLeftWidth,10),c=Number.parseInt(i.borderRightWidth,10);s=r-l-c}return s}}const rq=(e,t)=>a.useMemo(()=>{if(!t)return e;const n=r=>r.map(o=>{if(o===Wo||o===Uo)return o;if("children"in o&&Array.isArray(o.children))return{...Fa(t,o),children:n(o.children)};const s=Dt(t,["children"]);return Fa(s,o)});return n(e)},[e,t]),Fs=(e,t)=>"key"in e&&bn(e.key)?e.key:e.dataIndex?Array.isArray(e.dataIndex)?e.dataIndex.join("."):e.dataIndex:t;function al(e,t){return t?`${t}-${e}`:`${e}`}const Km=(e,t)=>bt(e)?e(t):e,oq=(e,t)=>{const n=Km(e,t);return dt(n)||Array.isArray(n)?"":n},Kw=e=>{const t=e.toLowerCase();return t.includes("center")?"center":t.includes("left")||t.includes("start")?"start":"end"},sq=(e,t)=>{if(e)return e;if(t==="small"||t==="medium")return"small"};var $M={};Object.defineProperty($M,"__esModule",{value:!0});var iq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z"}}]},name:"filter",theme:"filled"},aq=$M.default=iq;function Qv(){return Qv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Qv({},e,{ref:t,icon:aq})),cq=a.forwardRef(lq);var SM={};Object.defineProperty(SM,"__esModule",{value:!0});var uq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z"}}]},name:"file",theme:"outlined"},dq=SM.default=uq;function Jv(){return Jv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Jv({},e,{ref:t,icon:dq})),CM=a.forwardRef(fq);var wM={};Object.defineProperty(wM,"__esModule",{value:!0});var mq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 00-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z"}}]},name:"folder-open",theme:"outlined"},pq=wM.default=mq;function Zv(){return Zv=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,Zv({},e,{ref:t,icon:pq})),hq=a.forwardRef(gq);var EM={};Object.defineProperty(EM,"__esModule",{value:!0});var yq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 298.4H521L403.7 186.2a8.15 8.15 0 00-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z"}}]},name:"folder",theme:"outlined"},vq=EM.default=yq;function e0(){return e0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,e0({},e,{ref:t,icon:vq})),xq=a.forwardRef(bq);var IM={};Object.defineProperty(IM,"__esModule",{value:!0});var $q={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"}}]},name:"holder",theme:"outlined"},Sq=IM.default=$q;function t0(){return t0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,t0({},e,{ref:t,icon:Sq})),wq=a.forwardRef(Cq),Eq=({treeCls:e,treeNodeCls:t,directoryNodeSelectedBg:n,directoryNodeSelectedColor:r,motionDurationMid:o,borderRadius:s,controlItemBgHover:i})=>({[`${e}${e}-directory ${t}`]:{[`${e}-node-content-wrapper`]:{position:"static",[`&:has(${e}-drop-indicator)`]:{position:"relative"},[`> *:not(${e}-drop-indicator)`]:{position:"relative"},"&:hover":{background:"transparent"},"&:before":{position:"absolute",inset:0,transition:`background-color ${o}`,content:'""',borderRadius:s},"&:hover:before":{background:i}},[`${e}-switcher, ${e}-checkbox, ${e}-draggable-icon`]:{zIndex:1},"&-selected":{background:n,borderRadius:s,[`${e}-switcher, ${e}-draggable-icon`]:{color:r},[`${e}-node-content-wrapper`]:{color:r,background:"transparent","&, &:hover":{color:r},"&:before, &:hover:before":{background:n}}}}}),Iq=new Ht("ant-tree-node-fx-do-not-use",{"0%":{opacity:0},"100%":{opacity:1}}),Pq=(e,t)=>({[`.${e}-switcher-icon`]:{display:"inline-block",fontSize:10,verticalAlign:"baseline",svg:{transition:`transform ${t.motionDurationSlow}`}}}),Nq=(e,t)=>({[`.${e}-drop-indicator`]:{position:"absolute",zIndex:1,height:2,backgroundColor:t.colorPrimary,borderRadius:1,pointerEvents:"none","&:after":{position:"absolute",top:-3,insetInlineStart:-6,width:8,height:8,backgroundColor:"transparent",border:`${G(t.lineWidthBold)} solid ${t.colorPrimary}`,borderRadius:"50%",content:'""'}}}),Rq=(e,t)=>{const{treeCls:n,treeNodeCls:r,treeNodePadding:o,titleHeight:s,indentSize:i,switcherSize:l,motionDurationMid:c,nodeSelectedBg:u,nodeHoverBg:d,colorTextQuaternary:m,controlItemBgActiveDisabled:f}=t;return{[n]:{...Ft(t),"--rc-virtual-list-scrollbar-bg":t.colorSplit,background:t.colorBgContainer,borderRadius:t.borderRadius,transition:`background-color ${t.motionDurationSlow}`,"&-rtl":{direction:"rtl"},[`&${n}-rtl ${n}-switcher_close ${n}-switcher-icon svg`]:{transform:"rotate(90deg)"},[`${n}-list`]:{"&:focus-visible":{outline:"none",[`${r}-active ${n}-node-content-wrapper`]:{...jr(t)}}},[`${n}-list-holder-inner`]:{alignItems:"flex-start"},[`&${n}-block-node`]:{[`${n}-list-holder-inner`]:{alignItems:"stretch",[`${n}-node-content-wrapper`]:{flex:"auto"},[`${r}.dragging:after`]:{position:"absolute",inset:0,border:`1px solid ${t.colorPrimary}`,opacity:0,animationName:Iq,animationDuration:t.motionDurationSlow,animationPlayState:"running",animationFillMode:"forwards",content:'""',pointerEvents:"none",borderRadius:t.borderRadius}}},[r]:{display:"flex",alignItems:"flex-start",marginBottom:o,lineHeight:G(s),position:"relative","&:before":{content:'""',position:"absolute",zIndex:1,insetInlineStart:0,width:"100%",top:"100%",height:o},[`&-disabled ${n}-node-content-wrapper`]:{color:t.colorTextDisabled,cursor:"not-allowed","&:hover":{background:"transparent"}},[`${n}-checkbox-disabled + ${n}-node-selected,&${r}-disabled${r}-selected ${n}-node-content-wrapper`]:{backgroundColor:f},[`${n}-checkbox-disabled`]:{pointerEvents:"unset"},[`&:not(${r}-disabled)`]:{[`${n}-node-content-wrapper`]:{"&:hover":{color:t.nodeHoverColor}}},[`&-active ${n}-node-content-wrapper`]:{background:t.controlItemBgHover},[`&:not(${r}-disabled).filter-node ${n}-title`]:{color:t.colorPrimary,fontWeight:t.fontWeightStrong},"&-draggable":{cursor:"grab",[`${n}-draggable-icon`]:{flexShrink:0,width:l,textAlign:"center",visibility:"visible",color:m},[`&${r}-disabled ${n}-draggable-icon`]:{visibility:"hidden"}}},[`${n}-indent`]:{alignSelf:"stretch",whiteSpace:"nowrap",userSelect:"none","&-unit":{display:"inline-block",width:i}},[`${n}-draggable-icon`]:{visibility:"hidden"},[`${n}-switcher, ${n}-checkbox`]:{marginInlineEnd:t.calc(t.calc(l).sub(t.controlInteractiveSize)).div(2).equal()},[`${n}-checkbox`]:{flexShrink:0,alignSelf:"flex-start",marginBlockStart:t.calc(t.calc(s).sub(t.controlInteractiveSize)).div(2).equal()},[`${n}-switcher`]:{...Pq(e,t),position:"relative",flex:"none",alignSelf:"stretch",width:l,textAlign:"center",cursor:"pointer",userSelect:"none",transition:`all ${t.motionDurationSlow}`,"&-noop":{cursor:"unset"},"&:before":{pointerEvents:"none",content:'""',width:l,height:s,position:"absolute",left:{_skip_check_:!0,value:0},top:0,borderRadius:t.borderRadius,transition:`all ${t.motionDurationSlow}`},[`&:not(${n}-switcher-noop):hover:before`]:{backgroundColor:t.colorBgTextHover},[`&_close ${n}-switcher-icon svg`]:{transform:"rotate(-90deg)"},"&-loading-icon":{color:t.colorPrimary},"&-leaf-line":{position:"relative",zIndex:1,display:"inline-block",width:"100%",height:"100%","&:before":{position:"absolute",top:0,insetInlineEnd:t.calc(l).div(2).equal(),bottom:t.calc(o).mul(-1).equal(),marginInlineStart:-1,borderInlineEnd:`1px solid ${t.colorBorder}`,content:'""'},"&:after":{position:"absolute",width:t.calc(t.calc(l).div(2).equal()).mul(.8).equal(),height:t.calc(s).div(2).equal(),borderBottom:`1px solid ${t.colorBorder}`,content:'""'}}},[`${n}-node-content-wrapper`]:{position:"relative",minHeight:s,paddingBlock:0,paddingInline:t.paddingXS,background:"transparent",borderRadius:t.borderRadius,cursor:"pointer",transition:[`all ${c}`,"border 0s","line-height 0s","box-shadow 0s"].join(", "),...Nq(e,t),"&:hover":{backgroundColor:d},[`&${n}-node-selected`]:{color:t.nodeSelectedColor,backgroundColor:u},[`${n}-iconEle`]:{display:"inline-block",width:l,height:s,textAlign:"center",verticalAlign:"top","&:empty":{display:"none"}}},[`${n}-unselectable ${n}-node-content-wrapper:hover`]:{backgroundColor:"transparent"},[`${r}.drop-container > [draggable]`]:{boxShadow:`0 0 0 2px ${t.colorPrimary}`},"&-show-line":{[`${n}-indent-unit`]:{position:"relative",height:"100%","&:before":{position:"absolute",top:0,insetInlineEnd:t.calc(l).div(2).equal(),bottom:t.calc(o).mul(-1).equal(),borderInlineEnd:`1px solid ${t.colorBorder}`,content:'""'},"&-end:before":{display:"none"}},[`${n}-switcher`]:{background:"transparent","&-line-icon":{verticalAlign:"-0.15em"}}},[`${r}-leaf-last ${n}-switcher-leaf-line:before`]:{top:"auto !important",bottom:"auto !important",height:`${G(t.calc(s).div(2).equal())} !important`}}}},Tq=(e,t,n=!0)=>{const r=`.${e}`,o=`${r}-treenode`,s=t.calc(t.paddingXS).div(2).equal(),i=Rt(t,{treeCls:r,treeNodeCls:o,treeNodePadding:s});return[Rq(e,i),n&&Eq(i)].filter(Boolean)},Mq=e=>{const{controlHeightSM:t,controlItemBgHover:n,controlItemBgActive:r}=e,o=t;return{titleHeight:o,switcherSize:o,indentSize:o,nodeHoverBg:n,nodeHoverColor:e.colorText,nodeSelectedBg:r,nodeSelectedColor:e.colorText}},Oq=e=>{const{colorTextLightSolid:t,colorPrimary:n}=e;return{...Mq(e),directoryNodeSelectedColor:t,directoryNodeSelectedBg:n}},_q=Tt("Tree",(e,{prefixCls:t})=>[{[e.componentCls]:pT(`${t}-checkbox`,e)},Tq(t,e),gx(e)],Oq),Uw=4,zq=e=>{const{dropPosition:t,dropLevelOffset:n,prefixCls:r,indent:o,direction:s="ltr"}=e,i=s==="ltr"?"left":"right",l=s==="ltr"?"right":"left",c={[i]:-n*o+Uw,[l]:0};switch(t){case-1:c.top=-3;break;case 1:c.bottom=-3;break;default:c.bottom=-3,c[i]=o+Uw;break}return J.createElement("div",{style:c,className:`${r}-drop-indicator`})};var PM={};Object.defineProperty(PM,"__esModule",{value:!0});var jq={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"}}]},name:"caret-down",theme:"filled"},Bq=PM.default=jq;function n0(){return n0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,n0({},e,{ref:t,icon:Bq})),kq=a.forwardRef(Lq);var NM={};Object.defineProperty(NM,"__esModule",{value:!0});var Aq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M328 544h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"}},{tag:"path",attrs:{d:"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"}}]},name:"minus-square",theme:"outlined"},Dq=NM.default=Aq;function r0(){return r0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,r0({},e,{ref:t,icon:Dq})),Hq=a.forwardRef(Fq);var RM={};Object.defineProperty(RM,"__esModule",{value:!0});var Vq={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"}},{tag:"path",attrs:{d:"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"}}]},name:"plus-square",theme:"outlined"},Wq=RM.default=Vq;function o0(){return o0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,o0({},e,{ref:t,icon:Wq})),Uq=a.forwardRef(Kq),qq=e=>{var f,p;const{prefixCls:t,switcherIcon:n,treeNodeProps:r,showLine:o,switcherLoadingIcon:s}=e,{isLeaf:i,expanded:l,loading:c}=r;if(c)return a.isValidElement(s)?s:a.createElement(ki,{className:`${t}-switcher-loading-icon`});let u;if(dt(o)&&(u=o.showLeafIcon),i){if(!o)return null;if(typeof u!="boolean"&&u){const y=bt(u)?u(r):u,b=`${t}-switcher-line-custom-icon`;return a.isValidElement(y)?Fn(y,{className:H((f=y.props)==null?void 0:f.className,b)}):y}return u?a.createElement(CM,{className:`${t}-switcher-line-icon`}):a.createElement("span",{className:`${t}-switcher-leaf-line`})}const d=`${t}-switcher-icon`,m=bt(n)?n(r):n;return a.isValidElement(m)?Fn(m,{className:H((p=m.props)==null?void 0:p.className,o?`${t}-switcher-line-icon`:d)}):m!==void 0?m:o?l?a.createElement(Hq,{className:`${t}-switcher-line-icon`}):a.createElement(Uq,{className:`${t}-switcher-line-icon`}):a.createElement(kq,{className:d})},TM=J.forwardRef((e,t)=>{var K;const{getPrefixCls:n,direction:r,className:o,style:s,classNames:i,styles:l}=Pt("tree"),{virtual:c}=J.useContext(ct),{prefixCls:u,className:d,showIcon:m=!1,showLine:f,switcherIcon:p,switcherLoadingIcon:y,blockNode:b=!1,children:x,checkable:v=!1,selectable:g=!0,draggable:h,disabled:$,motion:C,style:N,rootClassName:S,classNames:E,styles:w,icon:R}=e,P=J.useContext(cr),T=$??P,M=n("tree",u),z=n(),B=C??{...cf(z),motionAppear:!1},F={...e,showIcon:m,blockNode:b,checkable:v,selectable:g,disabled:T,motion:B},[L,j]=Ot([i,E],[l,w],{props:F}),O={...F,showLine:!!f,icon:R,dropIndicatorRender:zq},[A,k]=_q(M),[,_]=Yn(),D=_.paddingXS/2+(((K=_.Tree)==null?void 0:K.titleHeight)||_.controlHeightSM),V=J.useMemo(()=>{if(!h)return!1;let q={};switch(typeof h){case"function":q.nodeDraggable=h;break;case"object":q={...h};break}return q.icon!==!1&&(q.icon=q.icon||J.createElement(wq,null)),q},[h]),W=q=>J.createElement(qq,{prefixCls:M,switcherIcon:p,switcherLoadingIcon:y,treeNodeProps:q,showLine:f});return J.createElement(O9,{itemHeight:D,ref:t,virtual:c,...O,prefixCls:M,className:H({[`${M}-icon-hide`]:!m,[`${M}-block-node`]:b,[`${M}-unselectable`]:!g,[`${M}-rtl`]:r==="rtl",[`${M}-disabled`]:T},o,d,A,k),style:{...s,...N},rootClassName:H(L.root,S),rootStyle:j.root,classNames:L,styles:j,direction:r,checkable:v&&J.createElement("span",{className:`${M}-checkbox-inner`}),selectable:g,switcherIcon:W,draggable:V},x)}),qw=0,xg=1,Gw=2;function s$(e,t,n){const{key:r,children:o}=n;function s(i){const l=i[r],c=i[o];t(l,i)!==!1&&s$(c||[],t,n)}e.forEach(s)}function Gq({treeData:e,expandedKeys:t,startKey:n,endKey:r,fieldNames:o}){const s=[];let i=qw;if(n&&n===r)return[n];if(!n||!r)return[];function l(c){return c===n||c===r}return s$(e,c=>{if(i===Gw)return!1;if(l(c)){if(s.push(c),i===qw)i=xg;else if(i===xg)return i=Gw,!1}else i===xg&&s.push(c);return t.includes(c)},Xa(o)),s}function $g(e,t,n){const r=$t(t),o=[];return s$(e,(s,i)=>{const l=r.indexOf(s);return l!==-1&&(o.push(i),r.splice(l,1)),!!r.length},Xa(n)),o}function Xq(e){const{isLeaf:t,expanded:n}=e;return t?a.createElement(CM,null):n?a.createElement(hq,null):a.createElement(xq,null)}function Xw({treeData:e,children:t}){return e||uT(t)}const Yq=a.forwardRef((e,t)=>{const{defaultExpandAll:n,defaultExpandParent:r=!0,defaultExpandedKeys:o,...s}=e,i=a.useRef(null),l=a.useRef(null),c=()=>{const{keyEntities:E}=Xx(Xw(s),{fieldNames:s.fieldNames});let w;const R=s.expandedKeys||o||[];return n?w=Object.keys(E):r?w=Iv(R,E):w=R,w},[u,d]=a.useState(s.selectedKeys||s.defaultSelectedKeys||[]),[m,f]=a.useState(()=>c());a.useEffect(()=>{"selectedKeys"in s&&d(s.selectedKeys)},[s.selectedKeys]),a.useEffect(()=>{"expandedKeys"in s&&f(s.expandedKeys)},[s.expandedKeys]);const p=(E,w)=>{var R;return"expandedKeys"in s||f(E),(R=s.onExpand)==null?void 0:R.call(s,E,w)},y=(E,w)=>{var A;const{multiple:R,fieldNames:P}=s,{node:T,nativeEvent:M}=w,{key:z=""}=T,B=Xw(s),F={...w,selected:!0},L=(M==null?void 0:M.ctrlKey)||(M==null?void 0:M.metaKey),j=M==null?void 0:M.shiftKey;let O;R&&L?(O=E,i.current=z,l.current=O,F.selectedNodes=$g(B,O,P)):R&&j?(O=Array.from(new Set([].concat($t(l.current||[]),$t(Gq({treeData:B,expandedKeys:m,startKey:z,endKey:i.current,fieldNames:P}))))),F.selectedNodes=$g(B,O,P)):(O=[z],i.current=z,l.current=O,F.selectedNodes=$g(B,O,P)),(A=s.onSelect)==null||A.call(s,O,F),"selectedKeys"in s||d(O)},{getPrefixCls:b,direction:x}=a.useContext(ct),{prefixCls:v,className:g,showIcon:h=!0,expandAction:$="click",...C}=s,N=b("tree",v),S=H(`${N}-directory`,{[`${N}-directory-rtl`]:x==="rtl"},g);return a.createElement(TM,{icon:Xq,ref:t,blockNode:!0,...C,showIcon:h,expandAction:$,prefixCls:N,className:S,defaultExpandParent:r,expandedKeys:m,selectedKeys:u,onSelect:y,onExpand:p})}),i$=TM;i$.DirectoryTree=Yq;i$.TreeNode=zc;const Yw=e=>{const{value:t,filterSearch:n,tablePrefixCls:r,locale:o,onChange:s}=e;return n?a.createElement("div",{className:`${r}-filter-dropdown-search`},a.createElement(au,{prefix:a.createElement(Tx,null),placeholder:o.filterSearchPlaceholder,onChange:s,value:t,htmlSize:1,className:`${r}-filter-dropdown-search-input`})):null},Qq=e=>{const{keyCode:t}=e;t===nt.ENTER&&e.stopPropagation()},Jq=a.forwardRef((e,t)=>a.createElement("div",{className:e.className,onClick:n=>n.stopPropagation(),onKeyDown:Qq,ref:t,role:"presentation"},e.children));function Ma(e){let t=[];return(e||[]).forEach(({value:n,children:r})=>{t.push(n),r&&(t=[].concat($t(t),$t(Ma(r))))}),t}function Zq(e){return e.some(({children:t})=>t)}const MM=(e,t)=>typeof t=="string"||Rn(t)?t.toString().toLowerCase().includes(e):!1,OM=e=>{const{filters:t,prefixCls:n,filteredKeys:r,filterMultiple:o,searchValue:s,normalizedSearchValue:i,filterSearch:l}=e;return t.map((c,u)=>{const d=String(c.value);if(c.children)return{key:d||u,label:c.text,popupClassName:`${n}-dropdown-submenu`,children:OM({filters:c.children,prefixCls:n,filteredKeys:r,filterMultiple:o,searchValue:s,normalizedSearchValue:i,filterSearch:l})};const m=o?as:ou,f={key:c.value!==void 0?d:u,label:a.createElement(a.Fragment,null,a.createElement(m,{checked:r.includes(d)}),a.createElement("span",null,c.text))};return i?bt(l)?l(i,c)?f:null:MM(i,c.text)?f:null:f})};function Sg(e){return e||[]}const eG=e=>{var re;const{tablePrefixCls:t,prefixCls:n,column:r,dropdownPrefixCls:o,columnKey:s,filterOnClose:i,filterMultiple:l,filterMode:c="menu",filterSearch:u=!1,filterState:d,triggerFilter:m,locale:f,children:p,getPopupContainer:y,rootClassName:b}=e,{filterResetToDefaultFilteredValue:x,defaultFilteredValue:v,filterDropdownProps:g={},filterDropdownOpen:h,onFilterDropdownOpenChange:$}=r,[C,N]=a.useState(!1),S=a.useContext(zx),E=!!(d&&((re=d.filteredKeys)!=null&&re.length||d.forceFiltered)),w=X=>{var se;N(X),(se=g.onOpenChange)==null||se.call(g,X),$==null||$(X)},R=g.open??h??C,P=d==null?void 0:d.filteredKeys,[T,M]=nB(Sg(P)),z=({selectedKeys:X})=>{M(X)},B=(X,{node:se,checked:ge})=>{z(l?{selectedKeys:X}:{selectedKeys:ge&&se.key?[se.key]:[]})};a.useEffect(()=>{C&&z({selectedKeys:Sg(P)})},[P,C]);const[F,L]=a.useState([]),j=X=>{L(X)},[O,A]=a.useState(""),k=a.useMemo(()=>O.trim().toLowerCase(),[O]),_=X=>{const{value:se}=X.target;A(se)};a.useEffect(()=>{C||A("")},[C]);const D=X=>{const se=X!=null&&X.length?X:null;if(se===null&&(!d||!d.filteredKeys)||ho(se,d==null?void 0:d.filteredKeys,!0))return null;m({column:r,key:s,filteredKeys:se})},V=()=>{w(!1),D(T())},W=({confirm:X,closeDropdown:se}={confirm:!1,closeDropdown:!1})=>{X&&D([]),se&&w(!1),A(""),M(x?(v||[]).map(String):[])},K=({closeDropdown:X}={closeDropdown:!0})=>{X&&w(!1),D(T())},q=(X,se)=>{se.source==="trigger"&&(X&&P!==void 0&&M(Sg(P)),w(X),!X&&!r.filterDropdown&&i&&V())},Y=H({[`${o}-menu-without-submenu`]:!Zq(r.filters||[])}),ee=X=>{if(X.target.checked){const se=Ma(r==null?void 0:r.filters).map(String);M(se)}else M([])},ie=({filters:X})=>(X||[]).map((se,ge)=>{const de=String(se.value),Se={title:se.text,key:se.value!==void 0?de:String(ge)};return se.children&&(Se.children=ie({filters:se.children})),Se}),ae=X=>{var se;return{...X,text:X.title,value:X.key,children:((se=X.children)==null?void 0:se.map(ae))||[]}};let U;const{direction:Q,renderEmpty:Z}=a.useContext(ct);if(bt(r.filterDropdown))U=r.filterDropdown({prefixCls:`${o}-custom`,setSelectedKeys:X=>z({selectedKeys:X}),selectedKeys:T(),confirm:K,clearFilters:W,filters:r.filters,visible:R,close:()=>{w(!1)}});else if(r.filterDropdown)U=r.filterDropdown;else{const X=T()||[],se=()=>{const de=(Z==null?void 0:Z("Table.filter"))??a.createElement(Go,{image:Go.PRESENTED_IMAGE_SIMPLE,description:f.filterEmptyText,styles:{image:{height:24}},style:{margin:0,padding:"16px 0"}});if((r.filters||[]).length===0)return de;if(c==="tree")return a.createElement(a.Fragment,null,a.createElement(Yw,{filterSearch:u,value:O,onChange:_,tablePrefixCls:t,locale:f}),a.createElement("div",{className:`${t}-filter-dropdown-tree`},l?a.createElement(as,{checked:X.length===Ma(r.filters).length,indeterminate:X.length>0&&X.lengthbt(u)?u(O,ae(be)):MM(k,be.title):void 0})));const Se=OM({filters:r.filters||[],filterSearch:u,prefixCls:n,filteredKeys:T(),filterMultiple:l,searchValue:O,normalizedSearchValue:k}),ue=Se.every(be=>be===null);return a.createElement(a.Fragment,null,a.createElement(Yw,{filterSearch:u,value:O,onChange:_,tablePrefixCls:t,locale:f}),ue?de:a.createElement(Fi,{selectable:!0,multiple:l,prefixCls:`${o}-menu`,className:Y,onSelect:z,onDeselect:z,selectedKeys:X,getPopupContainer:y,openKeys:F,onOpenChange:j,items:Se}))},ge=()=>x?ho((v||[]).map(String),X,!0):X.length===0;U=a.createElement(a.Fragment,null,se(),a.createElement("div",{className:`${n}-dropdown-btns`},a.createElement(Xe,{type:"link",size:"small",disabled:ge(),onClick:()=>W()},f.filterReset),a.createElement(Xe,{type:"primary",size:"small",onClick:V},f.filterConfirm)))}r.filterDropdown&&(U=a.createElement(B4,{selectable:void 0},U)),U=a.createElement(Jq,{className:`${n}-dropdown`},U);const oe=(()=>{let X;return bt(r.filterIcon)?X=r.filterIcon(E):r.filterIcon?X=r.filterIcon:X=a.createElement(cq,null),a.createElement("span",{role:"button",tabIndex:-1,className:H(`${n}-trigger`,{active:E}),onClick:se=>{se.stopPropagation()}},X)})();if(S)return a.createElement("div",{className:`${n}-column`},a.createElement("span",{className:`${t}-column-title`},p),oe);const le=Fa({trigger:["click"],placement:Q==="rtl"?"bottomLeft":"bottomRight",children:oe,getPopupContainer:y},{...g,rootClassName:H(b,g.rootClassName),open:R,onOpenChange:q,popupRender:()=>bt(g==null?void 0:g.dropdownRender)?g.dropdownRender(U):U});return a.createElement("div",{className:`${n}-column`},a.createElement("span",{className:`${t}-column-title`},p),a.createElement(Jx,{...le}))},s0=(e,t,n)=>{let r=[];return(e||[]).forEach((o,s)=>{const i=al(s,n),l=o.filterDropdown!==void 0;if(o.filters||l||"onFilter"in o)if("filteredValue"in o){let c=o.filteredValue;l||(c=(c==null?void 0:c.map(String))??c),r.push({column:o,key:Fs(o,i),filteredKeys:c,forceFiltered:o.filtered})}else r.push({column:o,key:Fs(o,i),filteredKeys:t&&o.defaultFilteredValue?o.defaultFilteredValue:void 0,forceFiltered:o.filtered});"children"in o&&(r=[].concat($t(r),$t(s0(o.children,t,i))))}),r};function _M(e,t,n,r,o,s,i,l,c){return n.map((u,d)=>{const m=al(d,l),{filterOnClose:f=!0,filterMultiple:p=!0,filterMode:y,filterSearch:b}=u;let x=u;if(x.filters||x.filterDropdown){const v=Fs(x,m),g=r.find(({key:h})=>v===h);x={...x,title:h=>a.createElement(eG,{tablePrefixCls:e,prefixCls:`${e}-filter`,dropdownPrefixCls:t,column:x,columnKey:v,filterState:g,filterOnClose:f,filterMultiple:p,filterMode:y,filterSearch:b,triggerFilter:s,locale:o,getPopupContainer:i,rootClassName:c},Km(u.title,h))}}return"children"in x&&(x={...x,children:_M(e,t,x.children,r,o,s,i,m,c)}),x})}const Qw=e=>{const t={};return e.forEach(({key:n,filteredKeys:r,column:o})=>{const s=n,{filters:i,filterDropdown:l}=o;if(l)t[s]=r||null;else if(Array.isArray(r)){const c=Ma(i);t[s]=c.filter(u=>r.includes(String(u)))}else t[s]=null}),t},i0=(e,t,n)=>t.reduce((o,s)=>{const{column:{onFilter:i,filters:l},filteredKeys:c}=s;if(i&&c&&c.length){const u=Ma(l),d=new Map;u.forEach(p=>{const y=String(p);d.has(y)||d.set(y,p)});const m=c.map(p=>{const y=String(p);return d.get(y)??p});return(p=>p.reduce((y,b)=>{const x={...b};return x[n]&&(x[n]=i0(x[n],t,n)),m.some(v=>i(v,x))&&y.push(x),y},[]))(o)}return o},e),zM=e=>e.flatMap(t=>"children"in t?[t].concat($t(zM(t.children||[]))):[t]),tG=e=>{const{prefixCls:t,dropdownPrefixCls:n,mergedColumns:r,onFilterChange:o,getPopupContainer:s,locale:i,rootClassName:l}=e;yo();const c=a.useMemo(()=>zM(r||[]),[r]),[u,d]=a.useState(()=>s0(c,!0)),m=a.useMemo(()=>{const b=s0(c,!1);if(b.length===0)return b;let x=!0;if(b.forEach(({filteredKeys:v})=>{v!==void 0&&(x=!1)}),x){const v=(c||[]).map((g,h)=>Fs(g,al(h)));return u.reduce((g,h)=>{const $=v.indexOf(h.key);if($!==-1){const C=c[$];g.push({...h,column:{...h.column,...C},forceFiltered:C.filtered})}return g},[])}return b},[c,u]),f=a.useMemo(()=>Qw(m),[m]),p=b=>{const x=m.filter(({key:v})=>v!==b.key);x.push(b),d(x),o(Qw(x),x)};return[b=>_M(t,n,b,m,i,p,s,void 0,l),m,f]},nG=(e,t,n)=>{const r=a.useRef({});function o(s){var l;if(!r.current||r.current.data!==e||r.current.childrenColumnName!==t||r.current.getRowKey!==n){let u=function(d){d.forEach((m,f)=>{const p=n(m,f);c.set(p,m),dt(m)&&t in m&&u(m[t]||[])})};var i=u;const c=new Map;u(e),r.current={data:e,childrenColumnName:t,kvMap:c,getRowKey:n}}return(l=r.current.kvMap)==null?void 0:l.get(s)}return[o]},jM=10;function rG(e,t){const n={current:e.current,pageSize:e.pageSize},r=dt(t)?t:{};return Object.keys(r).forEach(o=>{const s=e[o];bt(s)||(n[o]=s)}),n}function oG(e,t,n){const{total:r=0,...o}=dt(n)?n:{},[s,i]=a.useState(()=>({current:"defaultCurrent"in o?o.defaultCurrent:1,pageSize:"defaultPageSize"in o?o.defaultPageSize:jM})),l=Fa(s,o,{total:r>0?r:e}),c=Math.ceil((r||e)/l.pageSize);l.current>c&&(l.current=c||1);const u=(m,f)=>{i({current:m??1,pageSize:f||l.pageSize})},d=(m,f)=>{var p;n&&((p=n.onChange)==null||p.call(n,m,f)),u(m,f),t(m,f||(l==null?void 0:l.pageSize))};return n===!1?[{},()=>{}]:[{...l,onChange:d},u]}var BM={};Object.defineProperty(BM,"__esModule",{value:!0});var sG={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"}}]},name:"caret-down",theme:"outlined"},iG=BM.default=sG;function a0(){return a0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,a0({},e,{ref:t,icon:iG})),lG=a.forwardRef(aG);var LM={};Object.defineProperty(LM,"__esModule",{value:!0});var cG={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z"}}]},name:"caret-up",theme:"outlined"},uG=LM.default=cG;function l0(){return l0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,l0({},e,{ref:t,icon:uG})),fG=a.forwardRef(dG),Nd="ascend",Cg="descend",Mf=e=>dt(e.sorter)&&Rn(e.sorter.multiple)?e.sorter.multiple:!1,Jw=e=>bt(e)?e:dt(e)&&e.compare?e.compare:!1,mG=(e,t)=>t?e[e.indexOf(t)+1]:e[0],c0=(e,t,n)=>{let r=[];const o=(s,i)=>{r.push({column:s,key:Fs(s,i),multiplePriority:Mf(s),sortOrder:s.sortOrder})};return(e||[]).forEach((s,i)=>{const l=al(i,n);s.children?("sortOrder"in s&&o(s,l),r=[].concat($t(r),$t(c0(s.children,t,l)))):s.sorter&&("sortOrder"in s?o(s,l):t&&s.defaultSortOrder&&r.push({column:s,key:Fs(s,l),multiplePriority:Mf(s),sortOrder:s.defaultSortOrder}))}),r},kM=(e,t,n,r,o,s,i,l,c)=>(t||[]).map((d,m)=>{const f=al(m,l);let p=d;if(p.sorter){const y=p.sortDirections||o,b=p.showSorterTooltip===void 0?i:p.showSorterTooltip,x=Fs(p,f),v=n.find(({key:R})=>R===x),g=v?v.sortOrder:null,h=mG(y,g);let $;if(d.sortIcon)$=d.sortIcon({sortOrder:g});else{const R=y.includes(Nd)&&a.createElement(fG,{className:H(`${e}-column-sorter-up`,{active:g===Nd})}),P=y.includes(Cg)&&a.createElement(lG,{className:H(`${e}-column-sorter-down`,{active:g===Cg})});$=a.createElement("span",{className:H(`${e}-column-sorter`,{[`${e}-column-sorter-full`]:!!(R&&P)})},a.createElement("span",{className:`${e}-column-sorter-inner`,"aria-hidden":"true"},R,P))}const{cancelSort:C,triggerAsc:N,triggerDesc:S}=s||{};let E=C;h===Cg?E=S:h===Nd&&(E=N);const w=dt(b)?{title:E,...b}:{title:E};p={...p,className:H(p.className,{[`${e}-column-sort`]:g}),title:R=>{const P=`${e}-column-sorters`,T=a.createElement("span",{className:`${e}-column-title`},Km(d.title,R)),M=a.createElement("div",{className:P},T,$);return b?typeof b!="boolean"&&(b==null?void 0:b.target)==="sorter-icon"?a.createElement("div",{className:H(P,`${P}-tooltip-target-sorter`)},T,a.createElement(bo,{...w},$)):a.createElement(bo,{...w},M):M},onHeaderCell:R=>{var F;const P=((F=d.onHeaderCell)==null?void 0:F.call(d,R))||{},T=P.onClick,M=P.onKeyDown;P.onClick=L=>{r({column:d,key:x,sortOrder:h,multiplePriority:Mf(d)}),T==null||T(L)},P.onKeyDown=L=>{L.keyCode===nt.ENTER&&(r({column:d,key:x,sortOrder:h,multiplePriority:Mf(d)}),M==null||M(L))};const z=oq(d.title,{}),B=z==null?void 0:z.toString();return g&&(P["aria-sort"]=g==="ascend"?"ascending":"descending"),P["aria-description"]=c==null?void 0:c.sortable,P["aria-label"]=B||"",P.className=H(P.className,`${e}-column-has-sorters`),P.tabIndex=0,d.ellipsis&&(P.title=(z??"").toString()),P}}}return"children"in p&&(p={...p,children:kM(e,p.children,n,r,o,s,i,f,c)}),p}),Zw=e=>{const{column:t,sortOrder:n}=e;return{column:t,order:n,field:t.dataIndex,columnKey:t.key}},eE=e=>{const t=e.reduce((n,r)=>(r.sortOrder&&n.push(Zw(r)),n),[]);if(t.length===0&&e.length){const n=e.length-1;return{...Zw(e[n]),column:void 0,order:void 0,field:void 0,columnKey:void 0}}return t.length<=1?t[0]||{}:t},u0=(e,t,n)=>{const r=t.slice().sort((i,l)=>l.multiplePriority-i.multiplePriority),o=e.slice(),s=r.filter(({column:{sorter:i},sortOrder:l})=>Jw(i)&&l);return s.length?o.sort((i,l)=>{for(let c=0;c{const l=i[n];return l?{...i,[n]:u0(l,t,n)}:i}):o},pG=e=>{const{prefixCls:t,mergedColumns:n,baseColumns:r,sortDirections:o,tableLocale:s,showSorterTooltip:i,onSorterChange:l,globalLocale:c}=e,u=r??n,[d,m]=a.useState(()=>c0(u,!0)),f=(g,h)=>{const $=[];return g.forEach((C,N)=>{const S=al(N,h);if($.push(Fs(C,S)),Array.isArray(C.children)){const E=f(C.children,S);$.push.apply($,$t(E))}}),$},p=a.useMemo(()=>{let g=!0;const h=c0(u,!1);if(!h.length){const S=f(u);return d.filter(({key:E})=>S.includes(E))}const $=[];function C(S){g?$.push(S):$.push({...S,sortOrder:null})}let N=null;return h.forEach(S=>{N===null?(C(S),S.sortOrder&&(S.multiplePriority===!1?g=!1:N=!0)):(N&&S.multiplePriority!==!1||(g=!1),C(S))}),$},[u,d]),y=a.useMemo(()=>{var h,$;const g=p.map(({column:C,sortOrder:N})=>({column:C,order:N}));return{sortColumns:g,sortColumn:(h=g[0])==null?void 0:h.column,sortOrder:($=g[0])==null?void 0:$.order}},[p]),b=g=>{let h;g.multiplePriority===!1||!p.length||p[0].multiplePriority===!1?h=[g]:h=[].concat($t(p.filter(({key:$})=>$!==g.key)),[g]),m(h),l(eE(h),h)};return[g=>kM(t,g,p,b,o,s,i,void 0,c),p,y,()=>eE(p)]},gG=e=>J.useMemo(()=>typeof e=="boolean"?{spinning:e}:dt(e)?{spinning:!0,...e}:{},[e]),AM=(e,t)=>e.map(r=>{const o={...r};return o.title=Km(r.title,t),"children"in o&&(o.children=AM(o.children,t)),o}),hG=e=>[a.useCallback(n=>AM(n,e),[e])],yG=yM((e,t)=>{const{_renderTimes:n}=e,{_renderTimes:r}=t;return n!==r}),vG=bM((e,t)=>{const{_renderTimes:n}=e,{_renderTimes:r}=t;return n!==r}),bG=e=>{const{componentCls:t,lineWidth:n,lineType:r,tableBorderColor:o,tableHeaderBg:s,tablePaddingVertical:i,tablePaddingHorizontal:l,calc:c}=e,u=`${G(n)} ${r} ${o}`,d=(m,f,p)=>({[`&${t}-${m}`]:{[`> ${t}-container`]:{[`> ${t}-content, > ${t}-body`]:{"> table > tbody > tr > th, > table > tbody > tr > td":{[`> ${t}-expanded-row-fixed`]:{margin:`${G(c(f).mul(-1).equal())} + ${G(c(c(p).add(n)).mul(-1).equal())}`}}}}}});return{[`${t}-wrapper`]:{[`${t}${t}-bordered`]:{[`> ${t}-title`]:{border:u,borderBottom:0},[`> ${t}-container`]:{borderInlineStart:u,borderTop:u,[`> ${t}-header${t}-sticky-holder`]:{marginTop:c(n).mul(-1).equal(),borderTop:u},[`> ${t}-content, > ${t}-header, > ${t}-body, > ${t}-summary`]:{"> table":{"> thead > tr > th, > thead > tr > td, > tbody > tr > th, > tbody > tr > td, > tfoot > tr > th, > tfoot > tr > td":{borderInlineEnd:u},"> thead":{"> tr:not(:last-child) > th":{borderBottom:u},"> tr > th::before":{backgroundColor:"transparent !important"}},"> thead > tr, > tbody > tr, > tfoot > tr":{[`> ${t}-cell-fix-right-first:not(${t}-cell-fix-right-last)::after`]:{borderInlineEnd:u}},"> tbody > tr > th, > tbody > tr > td":{[`> ${t}-expanded-row-fixed`]:{margin:`${G(c(i).mul(-1).equal())} ${G(c(c(l).add(n)).mul(-1).equal())}`,"&::after":{position:"absolute",top:0,insetInlineEnd:n,bottom:0,borderInlineEnd:u,content:'""'}}}}}},[`&${t}-scroll-horizontal`]:{[`> ${t}-container > ${t}-body`]:{"> table > tbody":{[` + > tr${t}-expanded-row, + > tr${t}-placeholder + `]:{"> th, > td":{borderInlineEnd:0}}}}},...d("medium",e.tablePaddingVerticalMiddle,e.tablePaddingHorizontalMiddle),...d("small",e.tablePaddingVerticalSmall,e.tablePaddingHorizontalSmall),[`> ${t}-footer`]:{border:u,borderTop:0}},[`${t}-cell`]:{[`${t}-container:first-child`]:{borderTop:0},"&-scrollbar:not([rowspan])":{boxShadow:`0 ${G(n)} 0 ${G(n)} ${s}`}},[`${t}-bordered ${t}-cell-scrollbar`]:{borderInlineEnd:u}}}},xG=e=>{const{componentCls:t}=e;return{[`${t}-wrapper`]:{[`${t}-cell-ellipsis`]:{...ar,wordBreak:"keep-all",[` + &${t}-cell-fix-start-shadow, + &${t}-cell-fix-end-shadow + `]:{overflow:"visible",[`${t}-cell-content`]:{...ar,display:"block"}},[`${t}-column-title`]:{...ar,wordBreak:"keep-all"}}}}},$G=e=>{const{componentCls:t}=e;return{[`${t}-wrapper`]:{[`${t}-tbody > tr${t}-placeholder`]:{textAlign:"center",color:e.colorTextDisabled,"&:hover > th, &:hover > td":{background:e.colorBgContainer}}}}},SG=e=>{const{componentCls:t,antCls:n,motionDurationSlow:r,lineWidth:o,paddingXS:s,lineType:i,tableBorderColor:l,tableExpandIconBg:c,tableExpandColumnWidth:u,borderRadius:d,tablePaddingVertical:m,tablePaddingHorizontal:f,tableExpandedRowBg:p,paddingXXS:y,expandIconMarginTop:b,expandIconSize:x,expandIconHalfInner:v,expandIconScale:g,calc:h}=e,$=`${G(o)} ${i} ${l}`,C=h(y).sub(o).equal();return{[`${t}-wrapper`]:{[`${t}-expand-icon-col`]:{width:u},[`${t}-row-expand-icon-cell`]:{textAlign:"center",[`${t}-row-expand-icon`]:{display:"inline-flex",float:"none",verticalAlign:"sub"}},[`${t}-row-indent`]:{height:1,float:"left"},[`${t}-row-expand-icon`]:{...ex(e),position:"relative",float:"left",width:x,height:x,color:"inherit",lineHeight:G(x),background:c,border:$,borderRadius:d,transform:`scale(${g})`,"&:focus, &:hover, &:active":{borderColor:"currentcolor"},"&::before, &::after":{position:"absolute",background:"currentcolor",transition:`transform ${r} ease-out`,content:'""'},"&::before":{top:v,insetInlineEnd:C,insetInlineStart:C,height:o},"&::after":{top:C,bottom:C,insetInlineStart:v,width:o,transform:"rotate(90deg)"},"&-collapsed::before":{transform:"rotate(-180deg)"},"&-collapsed::after":{transform:"rotate(0deg)"},"&-spaced":{"&::before, &::after":{display:"none",content:"none"},background:"transparent",border:0,visibility:"hidden"}},[`${t}-row-indent + ${t}-row-expand-icon`]:{marginTop:b,marginInlineEnd:s},[`tr${t}-expanded-row`]:{"&, &:hover":{"> th, > td":{background:p}},[`${n}-descriptions-view`]:{display:"flex",table:{flex:"auto",width:"100%"}}},[`${t}-expanded-row-fixed`]:{position:"relative",margin:`${G(h(m).mul(-1).equal())} ${G(h(f).mul(-1).equal())}`,padding:`${G(m)} ${G(f)}`}}}},CG=e=>{const{componentCls:t,antCls:n,iconCls:r,tableFilterDropdownWidth:o,tableFilterDropdownSearchWidth:s,paddingXXS:i,paddingXS:l,colorText:c,lineWidth:u,lineType:d,tableBorderColor:m,headerIconColor:f,fontSizeSM:p,tablePaddingHorizontal:y,borderRadius:b,motionDurationSlow:x,colorIcon:v,colorPrimary:g,tableHeaderFilterActiveBg:h,colorTextDisabled:$,tableFilterDropdownBg:C,tableFilterDropdownHeight:N,controlItemBgHover:S,controlItemBgActive:E,boxShadowSecondary:w,filterDropdownMenuBg:R,calc:P}=e,T=`${n}-dropdown`,M=`${t}-filter-dropdown`,z=`${n}-tree`,B=`${G(u)} ${d} ${m}`;return[{[`${t}-wrapper`]:{[`${t}-filter-column`]:{display:"flex",justifyContent:"space-between"},[`${t}-filter-trigger`]:{position:"relative",display:"flex",alignItems:"center",marginBlock:P(i).mul(-1).equal(),marginInline:`${G(i)} ${G(P(y).div(2).mul(-1).equal())}`,padding:`0 ${G(i)}`,color:f,fontSize:p,borderRadius:b,cursor:"pointer",transition:`all ${x}`,"&:hover":{color:v,background:h},"&.active":{color:g}}}},{[`${n}-dropdown`]:{[M]:{...Ft(e),minWidth:o,backgroundColor:C,borderRadius:b,boxShadow:w,overflow:"hidden",[`${T}-menu`]:{maxHeight:N,overflowX:"hidden",border:0,boxShadow:"none",borderRadius:"unset",backgroundColor:R,"&:empty::after":{display:"block",padding:`${G(l)} 0`,color:$,fontSize:p,textAlign:"center",content:'"Not Found"'}},[`${M}-tree`]:{paddingBlock:`${G(l)} 0`,paddingInline:l,[z]:{padding:0},[`${z}-treenode ${z}-node-content-wrapper:hover`]:{backgroundColor:S},[`${z}-treenode-checkbox-checked ${z}-node-content-wrapper`]:{"&, &:hover":{backgroundColor:E}}},[`${M}-search`]:{padding:l,borderBottom:B,"&-input":{input:{minWidth:s},[r]:{color:$}}},[`${M}-checkall`]:{width:"100%",marginBottom:i,marginInlineStart:i},[`${M}-btns`]:{display:"flex",justifyContent:"space-between",padding:`${G(P(l).sub(u).equal())} ${G(l)}`,overflow:"hidden",borderTop:B}}}},{[`${n}-dropdown ${M}, ${M}-submenu`]:{[`${n}-checkbox-wrapper + span`]:{paddingInlineStart:l,color:c},"> ul":{maxHeight:"calc(100vh - 130px)",overflowX:"hidden",overflowY:"auto"}}}]};function DM({colorSplit:e}){const t={boxShadow:`inset 10px 0 8px -8px ${e}`},n={boxShadow:`inset -10px 0 8px -8px ${e}`};return[t,n]}const wG=e=>{const{componentCls:t,lineWidth:n,motionDurationSlow:r,zIndexTableFixed:o,tableBg:s,calc:i}=e,l=`${t}-cell`,c=`${l}-fix`,u={position:"absolute",top:0,bottom:i(n).mul(-1).equal(),width:30,transition:`box-shadow ${r}`,content:'""',pointerEvents:"none"},[d,m]=DM(e);return{[`${t}-wrapper`]:{[`${l}${c}`]:{position:"sticky"},[c]:{zIndex:`calc(var(--z-offset-reverse) + ${o})`,background:s,"&:after":u,"&-start:after":{insetInlineStart:"100%"},"&-end:after":{insetInlineEnd:"100%"},"&-start-shadow-show:after":d,"&-end-shadow-show:after":m},[`${t}-container`]:{position:"relative","&:before, &:after":{...u,zIndex:`calc(var(--columns-count) * 2 + ${o} + 1)`},"&:before":{insetInlineStart:0},"&:after":{insetInlineEnd:0}},[`${t}-has-fix-start ${t}-container:before`]:{display:"none"},[`${t}-has-fix-end ${t}-container:after`]:{display:"none"},[`${t}-fix-start-shadow-show ${t}-container:before`]:d,[`${t}-fix-end-shadow-show ${t}-container:after`]:m}}},EG=e=>{const{componentCls:t,antCls:n,margin:r}=e;return{[`${t}-wrapper`]:{[`${t}-pagination${n}-pagination`]:{margin:`${G(r)} 0`},[`${t}-pagination`]:{display:"flex",flexWrap:"wrap",rowGap:e.paddingXS,"> *":{flex:"none"},"&-start":{justifyContent:"flex-start"},"&-center":{justifyContent:"center"},"&-end":{justifyContent:"flex-end"}}}}},IG=e=>{const{componentCls:t,tableRadius:n}=e;return{[`${t}-wrapper`]:{[t]:{[`${t}-title, ${t}-header`]:{borderRadius:`${G(n)} ${G(n)} 0 0`},[`${t}-title + ${t}-container`]:{borderStartStartRadius:0,borderStartEndRadius:0,[`${t}-header, table`]:{borderRadius:0},"table > thead > tr:first-child":{"th:first-child, th:last-child, td:first-child, td:last-child":{borderRadius:0}}},"&-container":{borderStartStartRadius:n,borderStartEndRadius:n,"&::before":{borderStartStartRadius:n},"&::after":{borderStartEndRadius:n},[`> ${t}-content`]:{borderStartStartRadius:n,borderStartEndRadius:n},"table > thead > tr:first-child":{"> *:first-child":{borderStartStartRadius:n},"> *:last-child":{borderStartEndRadius:n}}},"&-footer":{borderRadius:`0 0 ${G(n)} ${G(n)}`}}}}},PG=e=>{const{componentCls:t}=e,[n,r]=DM(e);return{[`${t}-wrapper-rtl`]:{direction:"rtl",table:{direction:"rtl"},[`${t}-row-expand-icon`]:{float:"right","&::after":{transform:"rotate(-90deg)"},"&-collapsed::before":{transform:"rotate(180deg)"},"&-collapsed::after":{transform:"rotate(0deg)"}},[`${t}-cell-fix`]:{"&-start-shadow-show:after":r,"&-end-shadow-show:after":n},[`${t}-container`]:{[`${t}-row-indent`]:{float:"right"}},[`${t}-fix-start-shadow-show ${t}-container:before`]:r,[`${t}-fix-end-shadow-show ${t}-container:after`]:n}}},NG=e=>{const{componentCls:t,antCls:n,iconCls:r,fontSizeIcon:o,padding:s,paddingXS:i,headerIconColor:l,headerIconHoverColor:c,tableSelectionColumnWidth:u,tableSelectedRowBg:d,tableSelectedRowHoverBg:m,tableRowHoverBg:f,tablePaddingHorizontal:p,calc:y}=e;return{[`${t}-wrapper`]:{[`${t}-selection-col`]:{width:u,[`&${t}-selection-col-with-dropdown`]:{width:y(u).add(o).add(y(s).div(4)).equal()}},[`${t}-bordered ${t}-selection-col`]:{width:y(u).add(y(i).mul(2)).equal(),[`&${t}-selection-col-with-dropdown`]:{width:y(u).add(o).add(y(s).div(4)).add(y(i).mul(2)).equal()}},[` + table tr th${t}-selection-column, + table tr td${t}-selection-column, + ${t}-selection-column + `]:{paddingInlineEnd:e.paddingXS,paddingInlineStart:e.paddingXS,textAlign:"center",[`${n}-radio-wrapper`]:{marginInlineEnd:0}},[`table tr th${t}-selection-column${t}-cell-fix-left`]:{zIndex:y(e.zIndexTableFixed).add(1).equal({unit:!1})},[`table tr th${t}-selection-column::after`]:{backgroundColor:"transparent !important"},[`${t}-selection`]:{position:"relative",display:"inline-flex",flexDirection:"column"},[`${t}-selection-extra`]:{position:"absolute",top:0,zIndex:1,cursor:"pointer",transition:`all ${e.motionDurationSlow}`,marginInlineStart:"100%",paddingInlineStart:G(y(p).div(4).equal()),[r]:{color:l,fontSize:o,verticalAlign:"baseline","&:hover":{color:c}}},[`${t}-tbody`]:{[`${t}-row`]:{[`&${t}-row-selected`]:{[`> ${t}-cell`]:{background:d,"&-row-hover":{background:m}}},[`> ${t}-cell-row-hover`]:{background:f}}}}}},RG=e=>{const{componentCls:t,tableExpandColumnWidth:n,calc:r}=e,o=(s,i,l,c)=>({[`${t}${t}-${s}`]:{fontSize:c,[` + ${t}-title, + ${t}-footer, + ${t}-cell, + ${t}-thead > tr > th, + ${t}-tbody > tr > th, + ${t}-tbody > tr > td, + tfoot > tr > th, + tfoot > tr > td + `]:{padding:`${G(i)} ${G(l)}`},[`${t}-filter-trigger`]:{marginInlineEnd:G(r(l).div(2).mul(-1).equal())},[`${t}-expanded-row-fixed`]:{margin:`${G(r(i).mul(-1).equal())} ${G(r(l).mul(-1).equal())}`},[`${t}-tbody`]:{[`${t}-wrapper:only-child ${t}`]:{marginBlock:G(r(i).mul(-1).equal()),marginInline:`${G(r(n).sub(l).equal())} ${G(r(l).mul(-1).equal())}`}},[`${t}-selection-extra`]:{paddingInlineStart:G(r(l).div(4).equal())}}});return{[`${t}-wrapper`]:{...o("medium",e.tablePaddingVerticalMiddle,e.tablePaddingHorizontalMiddle,e.tableFontSizeMiddle),...o("small",e.tablePaddingVerticalSmall,e.tablePaddingHorizontalSmall,e.tableFontSizeSmall)}}},TG=e=>{const{componentCls:t,marginXXS:n,fontSizeIcon:r,headerIconColor:o,headerIconHoverColor:s}=e;return{[`${t}-wrapper`]:{[`${t}-thead th${t}-column-has-sorters`]:{outline:"none",cursor:"pointer",transition:`all ${e.motionDurationSlow}, left 0s`,"&:hover":{background:e.tableHeaderSortHoverBg,"&::before":{backgroundColor:"transparent !important"}},"&:focus-visible":{color:e.colorPrimary},[` + &${t}-cell-fix-left:hover, + &${t}-cell-fix-right:hover + `]:{background:e.tableFixedHeaderSortActiveBg}},[`${t}-thead th${t}-column-sort`]:{background:e.tableHeaderSortBg,"&::before":{backgroundColor:"transparent !important"}},[`td${t}-column-sort`]:{background:e.tableBodySortBg},[`${t}-column-title`]:{position:"relative",zIndex:1,flex:1,minWidth:0},[`${t}-column-sorters`]:{display:"flex",flex:"auto",alignItems:"center",justifyContent:"space-between","&::after":{position:"absolute",inset:0,width:"100%",height:"100%",content:'""'}},[`${t}-column-sorters-tooltip-target-sorter`]:{"&::after":{content:"none"}},[`${t}-column-sorter`]:{marginInlineStart:n,color:o,fontSize:0,transition:`color ${e.motionDurationSlow}`,"&-inner":{display:"inline-flex",flexDirection:"column",alignItems:"center"},"&-up, &-down":{fontSize:r,"&.active":{color:e.colorPrimary}},[`${t}-column-sorter-up + ${t}-column-sorter-down`]:{marginTop:"-0.3em"}},[`${t}-column-sorters:hover ${t}-column-sorter`]:{color:s}}}},MG=e=>{const{componentCls:t,opacityLoading:n,tableScrollThumbBg:r,tableScrollThumbBgHover:o,tableScrollThumbSize:s,tableScrollBg:i,stickyScrollBarBorderRadius:l,lineWidth:c,lineType:u,tableBorderColor:d,zIndexTableFixed:m}=e,f=`${G(c)} ${u} ${d}`;return{[`${t}-wrapper`]:{[`${t}-sticky`]:{"&-holder":{position:"sticky",zIndex:`calc(var(--columns-count) * 2 + ${m} + 1)`,background:e.colorBgContainer},"&-scroll":{position:"sticky",bottom:0,height:`${G(s)} !important`,zIndex:`calc(var(--columns-count) * 2 + ${m} + 1)`,display:"flex",alignItems:"center",background:i,borderTop:f,opacity:n,"&:hover":{transformOrigin:"center bottom"},"&-bar":{height:s,backgroundColor:r,borderRadius:l,transition:`all ${e.motionDurationSlow}, transform 0s`,position:"absolute",bottom:0,"&:hover, &-active":{backgroundColor:o}}}}}}},tE=e=>{const{componentCls:t,lineWidth:n,tableBorderColor:r,calc:o}=e,s=`${G(n)} ${e.lineType} ${r}`;return{[`${t}-wrapper`]:{[`${t}-summary`]:{position:"relative",zIndex:e.zIndexTableFixed,background:e.tableBg,"> tr":{"> th, > td":{borderBottom:s}}},[`div${t}-summary`]:{boxShadow:`0 ${G(o(n).mul(-1).equal())} 0 ${r}`}}}},OG=e=>{const{componentCls:t,motionDurationMid:n,lineWidth:r,lineType:o,tableBorderColor:s,calc:i}=e,l=`${G(r)} ${o} ${s}`,c=`${t}-expanded-row-cell`;return{[`${t}-wrapper`]:{[`${t}-tbody-virtual`]:{[`${t}-tbody-virtual-holder-inner`]:{[` + & > ${t}-row, + & > div:not(${t}-row) > ${t}-row + `]:{display:"flex",boxSizing:"border-box",width:"100%"}},[`${t}-cell`]:{borderBottom:l,transition:`background-color ${n}`},[`${t}-expanded-row`]:{[`${c}${c}-fixed`]:{position:"sticky",insetInlineStart:0,overflow:"hidden",width:`calc(var(--virtual-width) - ${G(r)})`,borderInlineEnd:"none"}}},[`${t}-bordered`]:{[`${t}-tbody-virtual`]:{"&:after":{content:'""',insetInline:0,bottom:0,borderBottom:l,position:"absolute"},[`${t}-cell`]:{borderInlineEnd:l,[`&${t}-cell-fix-right-first:before`]:{content:'""',position:"absolute",insetBlock:0,insetInlineStart:i(r).mul(-1).equal(),borderInlineStart:l}}},[`&${t}-virtual`]:{[`${t}-placeholder ${t}-cell`]:{borderInlineEnd:l,borderBottom:l}}}}}},_G=e=>{const{componentCls:t,fontWeightStrong:n,tablePaddingVertical:r,tablePaddingHorizontal:o,tableExpandColumnWidth:s,lineWidth:i,lineType:l,tableBorderColor:c,tableFontSize:u,tableBg:d,tableRadius:m,tableHeaderTextColor:f,motionDurationMid:p,tableHeaderBg:y,tableHeaderCellSplitColor:b,tableFooterTextColor:x,tableFooterBg:v,calc:g}=e,h=`${G(i)} ${l} ${c}`;return{[`${t}-wrapper`]:{clear:"both",maxWidth:"100%","--rc-virtual-list-scrollbar-bg":e.tableScrollBg,...Ls(),[t]:{...Ft(e),fontSize:u,background:d,borderRadius:`${G(m)} ${G(m)} 0 0`,scrollbarColor:`${e.tableScrollThumbBg} ${e.tableScrollBg}`},table:{width:"100%",textAlign:"start",borderRadius:`${G(m)} ${G(m)} 0 0`,borderCollapse:"separate",borderSpacing:0},[` + ${t}-cell, + ${t}-thead > tr > th, + ${t}-tbody > tr > th, + ${t}-tbody > tr > td, + tfoot > tr > th, + tfoot > tr > td + `]:{position:"relative",padding:`${G(r)} ${G(o)}`,overflowWrap:"break-word"},[`${t}-title`]:{padding:`${G(r)} ${G(o)}`},[`${t}-thead`]:{"> tr > th, > tr > td":{position:"relative",color:f,fontWeight:n,textAlign:"start",background:y,borderBottom:h,transition:`background-color ${p} ease`,"&[colspan]:not([colspan='1'])":{textAlign:"center"},[`&:not(:last-child):not(${t}-selection-column):not(${t}-row-expand-icon-cell):not([colspan])::before`]:{position:"absolute",top:"50%",insetInlineEnd:0,width:1,height:"1.6em",backgroundColor:b,transform:"translateY(-50%)",transition:`background-color ${p}`,content:'""'}},"> tr:not(:last-child) > th[colspan]":{borderBottom:0}},[`${t}-tbody`]:{"> tr":{"> th, > td":{borderBottom:h,transition:["background-color","border-color"].map($=>`${$} ${p}`).join(", "),[` + > ${t}-wrapper:only-child, + > ${t}-expanded-row-fixed > ${t}-wrapper:only-child + `]:{[t]:{marginBlock:G(g(r).mul(-1).equal()),marginInline:`${G(g(s).sub(o).equal())} + ${G(g(o).mul(-1).equal())}`,[`${t}-tbody > tr:last-child > td`]:{borderBottomWidth:0,"&:first-child, &:last-child":{borderRadius:0}}}}},"> th":{position:"relative",color:f,fontWeight:n,textAlign:"start",background:y,borderBottom:h,transition:`background-color ${p} ease`},[`& > ${t}-measure-cell`]:{paddingBlock:"0 !important",borderBlock:"0 !important",[`${t}-measure-cell-content`]:{height:0,overflow:"hidden",pointerEvents:"none"}}}},[`${t}-footer`]:{padding:`${G(r)} ${G(o)}`,color:x,background:v}}}},zG=e=>{const{colorFillAlter:t,colorBgContainer:n,colorTextHeading:r,colorFillSecondary:o,colorFillContent:s,controlItemBgActive:i,controlItemBgActiveHover:l,padding:c,paddingSM:u,paddingXS:d,colorBorderSecondary:m,borderRadiusLG:f,controlHeight:p,colorTextPlaceholder:y,fontSize:b,fontSizeSM:x,lineHeight:v,lineWidth:g,colorIcon:h,colorIconHover:$,opacityLoading:C,controlInteractiveSize:N}=e,S=new Gt(o).onBackground(n).toHexString(),E=new Gt(s).onBackground(n).toHexString(),w=new Gt(t).onBackground(n).toHexString(),R=new Gt(h),P=new Gt($),T=N/2-g,M=T*2+g*3;return{headerBg:w,headerColor:r,headerSortActiveBg:S,headerSortHoverBg:E,bodySortBg:w,rowHoverBg:w,rowSelectedBg:i,rowSelectedHoverBg:l,rowExpandedBg:t,cellPaddingBlock:c,cellPaddingInline:c,cellPaddingBlockMD:u,cellPaddingInlineMD:d,cellPaddingBlockSM:d,cellPaddingInlineSM:d,borderColor:m,headerBorderRadius:f,footerBg:w,footerColor:r,cellFontSize:b,cellFontSizeMD:b,cellFontSizeSM:b,headerSplitColor:m,fixedHeaderSortActiveBg:S,headerFilterHoverBg:s,filterDropdownMenuBg:n,filterDropdownBg:n,expandIconBg:n,selectionColumnWidth:p,stickyScrollBarBg:y,stickyScrollBarBorderRadius:100,expandIconMarginTop:(b*v-g*3)/2-Math.ceil((x*1.4-g*3)/2),headerIconColor:R.clone().setA(R.a*C).toRgbString(),headerIconHoverColor:P.clone().setA(P.a*C).toRgbString(),expandIconHalfInner:T,expandIconSize:M,expandIconScale:N/M}},jG=2,BG=Tt("Table",e=>{const{colorTextHeading:t,colorSplit:n,colorBgContainer:r,controlInteractiveSize:o,headerBg:s,headerColor:i,headerSortActiveBg:l,headerSortHoverBg:c,bodySortBg:u,rowHoverBg:d,rowSelectedBg:m,rowSelectedHoverBg:f,rowExpandedBg:p,cellPaddingBlock:y,cellPaddingInline:b,cellPaddingBlockMD:x,cellPaddingInlineMD:v,cellPaddingBlockSM:g,cellPaddingInlineSM:h,borderColor:$,footerBg:C,footerColor:N,headerBorderRadius:S,cellFontSize:E,cellFontSizeMD:w,cellFontSizeSM:R,headerSplitColor:P,fixedHeaderSortActiveBg:T,headerFilterHoverBg:M,filterDropdownBg:z,expandIconBg:B,selectionColumnWidth:F,stickyScrollBarBg:L,calc:j}=e,O=Rt(e,{tableFontSize:E,tableBg:r,tableRadius:S,tablePaddingVertical:y,tablePaddingHorizontal:b,tablePaddingVerticalMiddle:x,tablePaddingHorizontalMiddle:v,tablePaddingVerticalSmall:g,tablePaddingHorizontalSmall:h,tableBorderColor:$,tableHeaderTextColor:i,tableHeaderBg:s,tableFooterTextColor:N,tableFooterBg:C,tableHeaderCellSplitColor:P,tableHeaderSortBg:l,tableHeaderSortHoverBg:c,tableBodySortBg:u,tableFixedHeaderSortActiveBg:T,tableHeaderFilterActiveBg:M,tableFilterDropdownBg:z,tableRowHoverBg:d,tableSelectedRowBg:m,tableSelectedRowHoverBg:f,zIndexTableFixed:jG,tableFontSizeMiddle:w,tableFontSizeSmall:R,tableSelectionColumnWidth:F,tableExpandIconBg:B,tableExpandColumnWidth:j(o).add(j(e.padding).mul(2)).equal(),tableExpandedRowBg:p,tableFilterDropdownWidth:120,tableFilterDropdownHeight:264,tableFilterDropdownSearchWidth:140,tableScrollThumbSize:8,tableScrollThumbBg:L,tableScrollThumbBgHover:t,tableScrollBg:n});return[_G(O),EG(O),tE(O),TG(O),CG(O),bG(O),IG(O),SG(O),tE(O),$G(O),NG(O),wG(O),MG(O),xG(O),RG(O),PG(O),OG(O)]},zG,{resetFont:!1,unitless:{expandIconScale:!0}}),nE=[],FM=a.createContext({}),LG=e=>{const{ariaProps:t,component:n="table"}=a.useContext(FM);return a.createElement(n,{...t,...e})},kG=(e,t)=>{var Cr,mr,wr;const{prefixCls:n,className:r,rootClassName:o,style:s,classNames:i,styles:l,size:c,bordered:u,dropdownPrefixCls:d,dataSource:m,pagination:f,rowSelection:p,rowKey:y,rowClassName:b,column:x,columns:v,children:g,childrenColumnName:h,onChange:$,getPopupContainer:C,loading:N,expandIcon:S,expandable:E,expandedRowRender:w,expandIconColumnIndex:R,indentSize:P,scroll:T,sortDirections:M,locale:z,showSorterTooltip:B={target:"full-header"},virtual:F}=e;yo();const L=a.useMemo(()=>v||r$(g),[v,g]),j=rq(L,x),O=a.useMemo(()=>j.some(mt=>mt.responsive),[j]),A=Mm(O),k=a.useMemo(()=>{const mt=new Set(Object.keys(A).filter(Wt=>A[Wt]));return j.filter(Wt=>!Wt.responsive||Wt.responsive.some(en=>mt.has(en)))},[j,A]),_=Dt(e,["className","style","column","columns"]),D=_.components,V=Nn(_,{aria:!0}),W=Object.keys(V).length>0,K=a.useMemo(()=>{var mt;return{ariaProps:V,component:(mt=D==null?void 0:D.header)==null?void 0:mt.table}},[V,(Cr=D==null?void 0:D.header)==null?void 0:Cr.table]),q=a.useMemo(()=>W?{...D,header:{...D==null?void 0:D.header,table:LG}}:D,[D,W]),{locale:Y=Zr,table:ee}=a.useContext(ct),{getPrefixCls:ie,direction:ae,renderEmpty:U,getPopupContainer:Q,className:Z,style:ne,classNames:oe,styles:le}=Pt("table"),re=Cn(mt=>c==="middle"?"medium":c??mt),X={...e,size:re,bordered:u},se=Mt(ne),ge=Mt(s),[de,Se]=Ot([oe,i],[le,se,l,ge],{props:X},{pagination:{_default:"root"},header:{_default:"wrapper"},body:{_default:"wrapper"}}),ue={...Y.Table,...z},[be]=Ar("global",Zr.global),Ne=m||nE,we=ie("table",n),ze=ie("dropdown",d),[,he]=Yn(),ke=a.useMemo(()=>{var mt;return dt(p)?{columnWidth:(mt=he.Table)==null?void 0:mt.selectionColumnWidth,...p}:p},[p,(mr=he.Table)==null?void 0:mr.selectionColumnWidth]),Oe=on(we),[Ce,Me]=BG(we,Oe),xe={childrenColumnName:h,expandIconColumnIndex:R,...E,expandIcon:(E==null?void 0:E.expandIcon)??((wr=ee==null?void 0:ee.expandable)==null?void 0:wr.expandIcon)},{childrenColumnName:Ee="children"}=xe,Ve=a.useMemo(()=>Ne.some(mt=>mt==null?void 0:mt[Ee])?"nest":w||E!=null&&E.expandedRowRender?"row":null,[Ee,Ne]),qe={body:a.useRef(null)},me=nq(we),Re=a.useRef(null),Te=a.useRef(null);tB(t,()=>({...Te.current,nativeElement:Re.current}));const Ue=y||(ee==null?void 0:ee.rowKey)||"key",Ge=T??(ee==null?void 0:ee.scroll),Fe=a.useMemo(()=>bt(Ue)?Ue:mt=>mt==null?void 0:mt[Ue],[Ue]),[et]=nG(Ne,Ee,Fe),ve={},je=(mt,Wt,en=!1)=>{var wt,Ye,Et,Kt;const tt={...ve,...mt};en&&((wt=ve.resetPagination)==null||wt.call(ve),(Ye=tt.pagination)!=null&&Ye.current&&(tt.pagination.current=1),f&&((Kt=f.onChange)==null||Kt.call(f,1,(Et=tt.pagination)==null?void 0:Et.pageSize))),T&&T.scrollToFirstRowOnChange!==!1&&qe.body.current&&K5(0,{getContainer:()=>qe.body.current}),$==null||$(tt.pagination,tt.filters,tt.sorter,{currentDataSource:i0(u0(Ne,tt.sorterStates,Ee),tt.filterStates,Ee),action:Wt})},ce=(mt,Wt)=>{je({sorter:mt,sorterStates:Wt},"sort",!1)},[Pe,pe,$e,_e]=pG({prefixCls:we,mergedColumns:k,baseColumns:j,onSorterChange:ce,sortDirections:M||["ascend","descend"],tableLocale:ue,showSorterTooltip:B,globalLocale:be}),Ie=a.useMemo(()=>u0(Ne,pe,Ee),[Ee,Ne,pe]);ve.sorter=_e(),ve.sorterStates=pe;const Be=(mt,Wt)=>{je({filters:mt,filterStates:Wt},"filter",!0)},[te,ye,Ae]=tG({prefixCls:we,locale:ue,dropdownPrefixCls:ze,mergedColumns:k,onFilterChange:Be,getPopupContainer:C||Q,rootClassName:H(o,Oe)}),Je=i0(Ie,ye,Ee);ve.filters=Ae,ve.filterStates=ye;const St=tq($e,Ae),[ht]=hG(St),Nt=(mt,Wt)=>{je({pagination:{...ve.pagination,current:mt,pageSize:Wt}},"paginate")},[yt,at]=oG(Je.length,Nt,f);ve.pagination=f===!1?{}:rG(yt,f),ve.resetPagination=at;const Ze=a.useMemo(()=>{if(f===!1||!yt.pageSize)return Je;const{current:mt=1,total:Wt,pageSize:en=jM}=yt;return Je.lengthen?Je.slice((mt-1)*en,mt*en):Je:Je.slice((mt-1)*en,mt*en)},[!!f,Je,yt==null?void 0:yt.current,yt==null?void 0:yt.pageSize,yt==null?void 0:yt.total]),[De,Le]=JU({prefixCls:we,data:Je,pageData:Ze,getRowKey:Fe,getRecordByKey:et,expandType:Ve,childrenColumnName:Ee,locale:ue,getPopupContainer:C||Q},ke),Ke=(mt,Wt,en)=>H({[`${we}-row-selected`]:Le.has(Fe(mt,Wt))},bt(b)?b(mt,Wt,en):b);xe.__PARENT_RENDER_ICON__=xe.expandIcon,xe.expandIcon=xe.expandIcon||S||ZU(ue),Ve==="nest"&&xe.expandIconColumnIndex===void 0?xe.expandIconColumnIndex=ke?1:0:xe.expandIconColumnIndex>0&&ke&&(xe.expandIconColumnIndex-=1),Rn(xe.indentSize)||(xe.indentSize=Rn(P)?P:15);const lt=a.useCallback(mt=>ht(De(te(Pe(mt)))),[Pe,te,De]);let _t,ft;if(f!==!1&&(yt!=null&&yt.total)){const mt=sq(yt.size,re),Wt=(Ye="end")=>a.createElement(yK,{...yt,classNames:de.pagination,styles:Se.pagination,className:H(`${we}-pagination`,`${we}-pagination-${Ye}`,yt.className),size:mt}),{placement:en,position:tt}=yt,wt=en??tt;if(Array.isArray(wt)){const[Ye,Et]=["top","bottom"].map(pr=>wt.find(Dr=>Dr.includes(pr))),Kt=wt.every(pr=>`${pr}`=="none");!Ye&&!Et&&!Kt&&(ft=Wt()),Ye&&(_t=Wt(Kw(Ye))),Et&&(ft=Wt(Kw(Et)))}else ft=Wt()}const xt=gG(N),jt=H(Me,Oe,`${we}-wrapper`,Z,{[`${we}-wrapper-rtl`]:ae==="rtl"},r,o,de.root,Ce),pt=a.useMemo(()=>xt!=null&&xt.spinning&&Ne===nE?null:typeof(z==null?void 0:z.emptyText)<"u"?z.emptyText:(U==null?void 0:U("Table"))||a.createElement(YR,{componentName:"Table"}),[xt==null?void 0:xt.spinning,Ne,z==null?void 0:z.emptyText,U]),qt=F?vG:yG,cn={},hn=a.useMemo(()=>{const{fontSize:mt,lineHeight:Wt,lineWidth:en,padding:tt,paddingXS:wt,paddingSM:Ye}=he,Et=Math.floor(mt*Wt);switch(re){case"medium":return Ye*2+Et+en;case"small":return wt*2+Et+en;default:return tt*2+Et+en}},[he,re]);return F&&(cn.listItemHeight=hn),a.createElement("div",{ref:Re,className:jt,style:Se.root},a.createElement(YT,{spinning:!1,...xt},_t,a.createElement(FM.Provider,{value:K},a.createElement(qt,{...cn,..._,components:q,scroll:Ge,classNames:de,styles:Se,ref:Te,columns:k,direction:ae,expandable:xe,prefixCls:we,className:H({[`${we}-medium`]:re==="medium",[`${we}-small`]:re==="small",[`${we}-bordered`]:u,[`${we}-empty`]:Ne.length===0},Me,Oe,Ce),data:Ze,rowKey:Fe,rowClassName:Ke,emptyText:pt,internalHooks:cu,internalRefs:qe,transformColumns:lt,getContainerWidth:me,measureRowRender:mt=>a.createElement(zx.Provider,{value:!0},a.createElement(to,{getPopupContainer:Wt=>Wt},mt))})),ft))},AG=a.forwardRef(kG),DG=(e,t)=>{const n=a.useRef(0);return n.current+=1,a.createElement(AG,{...e,ref:t,_renderTimes:n.current})},Vn=a.forwardRef(DG);Vn.SELECTION_COLUMN=Wo;Vn.EXPAND_COLUMN=Uo;Vn.SELECTION_ALL=Gv;Vn.SELECTION_INVERT=Xv;Vn.SELECTION_NONE=Yv;Vn.Column=YU;Vn.ColumnGroup=QU;Vn.Summary=iM;const FG=e=>{const{paddingXXS:t,lineWidth:n,tagPaddingHorizontal:r,componentCls:o,calc:s}=e,i=s(r).sub(n).equal(),l=s(t).sub(n).equal();return{[o]:{...Ft(e),display:"inline-block",height:"auto",paddingInline:i,fontSize:e.tagFontSize,lineHeight:e.tagLineHeight,whiteSpace:"nowrap",backgroundColor:e.defaultBg,border:`${G(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,opacity:1,transition:`all ${e.motionDurationMid}`,textAlign:"start",position:"relative",[`&${o}-rtl`]:{direction:"rtl"},"&, a, a:hover":{color:e.defaultColor},[`${o}-close-icon`]:{marginInlineStart:l,fontSize:e.tagIconSize,color:e.colorIcon,cursor:"pointer",transition:`all ${e.motionDurationMid}`,"&:hover":{color:e.colorTextHeading}},"&-checkable":{backgroundColor:"transparent",borderColor:"transparent",cursor:"pointer",[`&:not(${o}-checkable-checked):hover`]:{color:e.colorPrimary,backgroundColor:e.colorFillSecondary},"&:active, &-checked":{color:e.colorTextLightSolid},"&-checked":{backgroundColor:e.colorPrimary,"&:hover":{backgroundColor:e.colorPrimaryHover}},"&:active":{backgroundColor:e.colorPrimaryActive},"&-disabled":{cursor:"not-allowed",[`&:not(${o}-checkable-checked)`]:{color:e.colorTextDisabled,"&:hover":{backgroundColor:"transparent"}},[`&${o}-checkable-checked`]:{color:e.colorTextDisabled,backgroundColor:e.colorBgContainerDisabled},"&:hover, &:active":{backgroundColor:e.colorBgContainerDisabled,color:e.colorTextDisabled},[`&:not(${o}-checkable-checked):hover`]:{color:e.colorTextDisabled}},"&-group":{display:"flex",flexWrap:"wrap",gap:e.paddingXS}},"&-hidden":{display:"none"},[`> ${e.iconCls} + span, > span + ${e.iconCls}`]:{marginInlineStart:i}},[`&${e.componentCls}-solid`]:{borderColor:"transparent",color:e.colorTextLightSolid,backgroundColor:e.colorBgSolid,[`&${o}-default`]:{color:e.solidTextColor}},[`${o}-filled`]:{borderColor:"transparent",backgroundColor:e.tagBorderlessBg},[`&${o}-disabled`]:{color:e.colorTextDisabled,cursor:"not-allowed",backgroundColor:e.colorBgContainerDisabled,a:{cursor:"not-allowed",pointerEvents:"none",color:e.colorTextDisabled,"&:hover":{color:e.colorTextDisabled}},"a&":{"&:hover, &:active":{color:e.colorTextDisabled}},[`&${o}-outlined`]:{borderColor:e.colorBorderDisabled},[`&${o}-solid, &${o}-filled`]:{color:e.colorTextDisabled,[`${o}-close-icon`]:{color:e.colorTextDisabled}},[`${o}-close-icon`]:{cursor:"not-allowed",color:e.colorTextDisabled,"&:hover":{color:e.colorTextDisabled}}}}},a$=e=>{const{lineWidth:t,fontSizeIcon:n,calc:r}=e,o=e.fontSizeSM;return Rt(e,{tagFontSize:o,tagLineHeight:G(r(e.lineHeightSM).mul(o).equal()),tagIconSize:r(n).sub(r(t).mul(2)).equal(),tagPaddingHorizontal:8,tagBorderlessBg:e.defaultBg})},l$=e=>{const t=GN(new df(e.colorBgSolid),"#fff")?"#000":"#fff";return{defaultBg:new Gt(e.colorFillTertiary).onBackground(e.colorBgContainer).toHexString(),defaultColor:e.colorText,solidTextColor:t}},c$=Tt("Tag",e=>{const t=a$(e);return FG(t)},l$),HM=a.forwardRef((e,t)=>{const{prefixCls:n,style:r,className:o,checked:s,children:i,icon:l,onChange:c,onClick:u,onKeyDown:d,disabled:m,...f}=e,{getPrefixCls:p,tag:y}=a.useContext(ct),b=a.useContext(cr),x=m??b,v=S=>{x||(c==null||c(!s),u==null||u(S))},g=S=>{d==null||d(S),!(S.defaultPrevented||x)&&S.key===" "&&(S.preventDefault(),c==null||c(!s))},h=p("tag",n),[$,C]=c$(h),N=H(h,`${h}-checkable`,{[`${h}-checkable-checked`]:s,[`${h}-checkable-disabled`]:x},y==null?void 0:y.className,o,$,C);return a.createElement("span",{...f,ref:t,role:"checkbox","aria-checked":s,"aria-disabled":x||void 0,tabIndex:x?-1:0,style:{...r,...y==null?void 0:y.style},className:N,onClick:v,onKeyDown:g},l,a.createElement("span",null,i))}),HG=J.forwardRef((e,t)=>{const{id:n,prefixCls:r,rootClassName:o,className:s,style:i,classNames:l,styles:c,disabled:u,options:d,value:m,defaultValue:f,onChange:p,multiple:y,...b}=e,{getPrefixCls:x,direction:v,className:g,style:h,classNames:$,styles:C}=Pt("tag"),N=x("tag",r),S=`${N}-checkable-group`,E=on(N),[w,R]=c$(N,E),P=Mt(h),T=Mt(i),[M,z]=Ot([$,l],[C,P,c,T],{props:e}),B=a.useMemo(()=>Array.isArray(d)?d.map(k=>dt(k)?k:{value:k,label:k}):[],[d]),[F,L]=nn(f,m),j=(k,_)=>{let D=null;if(y){const V=F||[];D=k?[].concat($t(V),[_.value]):V.filter(W=>W!==_.value)}else D=k?_.value:null;L(D),p==null||p(D)},O=J.useRef(null);a.useImperativeHandle(t,()=>({nativeElement:O.current}));const A=Nn(b,{aria:!0,data:!0});return J.createElement("div",{...A,className:H(S,g,o,{[`${S}-disabled`]:u,[`${S}-rtl`]:v==="rtl"},w,R,s,M.root),style:z.root,id:n,ref:O},B.map(k=>J.createElement(HM,{key:k.value,className:H(`${S}-item`,M.item,k.className),style:{...z.item,...k.style},checked:y?(F||[]).includes(k.value):F===k.value,onChange:_=>j(_,k),disabled:u},k.label)))});function VG(e,t){const{color:n,variant:r,bordered:o}=e;return a.useMemo(()=>{const s=n==null?void 0:n.endsWith("-inverse");let i;r?i=r:s?i="solid":o===!1?i="filled":i=t||"filled";let l=s?n==null?void 0:n.replace("-inverse",""):n;l===void 0&&i==="solid"&&(l="default");const c=a4(l),u=DF(l),d={};if(!c&&!u&&l)if(i==="solid")d.backgroundColor=n;else{const m=new Gt(l).toHsl();m.l=.95,d.backgroundColor=new Gt(m).toHexString(),d.color=n,i==="outlined"&&(d.borderColor=n)}return[i,l,c,u,d]},[n,r,o,t])}const WG=e=>EP(e,(t,{textColor:n,lightBorderColor:r,lightColor:o,darkColor:s})=>({[`${e.componentCls}${e.componentCls}-${t}:not(${e.componentCls}-disabled)`]:{[`&${e.componentCls}-outlined`]:{backgroundColor:o,borderColor:r,color:n},[`&${e.componentCls}-solid`]:{backgroundColor:s,borderColor:s,color:e.colorTextLightSolid},[`&${e.componentCls}-filled`]:{backgroundColor:o,color:n}}})),KG=Ks(["Tag","preset"],e=>{const t=a$(e);return WG(t)},l$);function UG(e){return typeof e!="string"?e:e.charAt(0).toUpperCase()+e.slice(1)}const Xu=(e,t,n)=>{const r=UG(n);return{[`${e.componentCls}${e.componentCls}-${t}:not(${e.componentCls}-disabled)`]:{[`&${e.componentCls}-outlined`]:{backgroundColor:e[`color${r}Bg`],borderColor:e[`color${r}Border`],color:e[`color${n}`]},[`&${e.componentCls}-solid`]:{backgroundColor:e[`color${n}`],borderColor:e[`color${n}`]},[`&${e.componentCls}-filled`]:{backgroundColor:e[`color${r}Bg`],color:e[`color${n}`]}}}},qG=Ks(["Tag","status"],e=>{const t=a$(e);return[Xu(t,"success","Success"),Xu(t,"processing","Info"),Xu(t,"error","Error"),Xu(t,"warning","Warning")]},l$),GG=a.forwardRef((e,t)=>{var le;const{prefixCls:n,className:r,rootClassName:o,style:s,children:i,icon:l,color:c,variant:u,onClose:d,bordered:m,disabled:f,href:p,target:y,styles:b,classNames:x,...v}=e,{getPrefixCls:g,direction:h,className:$,variant:C,style:N,classNames:S,styles:E}=Pt("tag"),[w,R,P,T,M]=VG(e,C),z=P||T,B=a.useContext(cr),F=f??B,{tag:L}=a.useContext(ct),[j,O]=a.useState(!0),A=Dt(v,["closeIcon","closable"]),k={...e,color:R,variant:w,disabled:F},[_,D]=Ot([S,x],[E,b],{props:k}),V=a.useMemo(()=>{let re={...D.root,...N,...s};return F||(re={...M,...re}),re},[D.root,N,s,M,F]),W=g("tag",n),[K,q]=c$(W),Y=H(W,$,_.root,`${W}-${w}`,{[`${W}-${R}`]:z,[`${W}-hidden`]:!j,[`${W}-rtl`]:h==="rtl",[`${W}-disabled`]:F},r,o,K,q),ee=re=>{F||(re.stopPropagation(),d==null||d(re),!re.defaultPrevented&&O(!1))},ie=re=>{(re.key==="Enter"||re.key===" ")&&(re.preventDefault(),re.currentTarget.click())},[,ae]=NN(Ua(e),Ua(L),{closable:!1,closeIconRender:re=>{const X=a.createElement("span",{role:"button",tabIndex:F?-1:0,"aria-disabled":F||void 0,className:H(`${W}-close-icon`,_.close),onClick:ee,onKeyDown:ie,style:D.close},re);return uN(re,X,se=>({onClick:ge=>{var de;(de=se==null?void 0:se.onClick)==null||de.call(se,ge),ee(ge)},onKeyDown:ge=>{var de;(de=se==null?void 0:se.onKeyDown)==null||de.call(se,ge),ge.defaultPrevented||ie(ge)},role:"button",tabIndex:F?-1:0,"aria-disabled":F||void 0,className:H(se==null?void 0:se.className,`${W}-close-icon`,_.close),style:{...D.close,...se==null?void 0:se.style}}))}}),U=bt(v.onClick)||i&&i.type==="a",Q=Fn(l,{className:H(a.isValidElement(l)?(le=l.props)==null?void 0:le.className:void 0,_.icon),style:D.icon}),Z=Q?a.createElement(a.Fragment,null,Q,i&&a.createElement("span",{className:_.content,style:D.content},i)):i,ne=p?"a":"span",oe=a.createElement(ne,{...A,ref:t,className:Y,style:V,href:F?void 0:p,target:y,onClick:F?void 0:A.onClick,...p&&F?{"aria-disabled":!0}:{}},Z,ae,P&&a.createElement(KG,{key:"preset",prefixCls:W}),T&&a.createElement(qG,{key:"status",prefixCls:W}));return U?a.createElement(Sm,{component:"Tag"},oe):oe}),Yt=GG;Yt.CheckableTag=HM;Yt.CheckableTagGroup=HG;const XG=e=>{const t=e!=null&&e.algorithm?tf(e.algorithm):Jb,n={...Wa,...e==null?void 0:e.token};return eP(n,{override:e==null?void 0:e.token},t,$P)};function YG(e){const{sizeUnit:t,sizeStep:n}=e,r=n-2;return{sizeXXL:t*(r+10),sizeXL:t*(r+6),sizeLG:t*(r+2),sizeMD:t*(r+2),sizeMS:t*(r+1),size:t*r,sizeSM:t*r,sizeXS:t*(r-1),sizeXXS:t*(r-1)}}const QG=(e,t)=>{const n=t??hm(e),r=n.fontSizeSM,o=n.controlHeight-4;return{...n,...YG(t??e),...xP(r),controlHeight:o,...bP({...n,controlHeight:o})}},Vr=(e,t)=>new Gt(e).setA(t).toRgbString(),Zs=(e,t)=>new Gt(e).lighten(t).toHexString(),rE=e=>{const t=gm(e,{theme:"dark"});return{1:t[0],2:t[1],3:t[2],4:t[3],5:t[6],6:t[5],7:t[4],8:t[6],9:t[5],10:t[4]}},JG=(e,t,n)=>{const r=e||"#000",o=t||"#fff";return{colorBgBase:r,colorTextBase:o,colorShadow:n||"rgba(255, 255, 255, 0.2)",colorText:Vr(o,.85),colorTextSecondary:Vr(o,.65),colorTextTertiary:Vr(o,.45),colorTextQuaternary:Vr(o,.25),colorFill:Vr(o,.18),colorFillSecondary:Vr(o,.12),colorFillTertiary:Vr(o,.08),colorFillQuaternary:Vr(o,.04),colorBgSolid:Vr(o,.95),colorBgSolidHover:Vr(o,1),colorBgSolidActive:Vr(o,.9),colorBgElevated:Zs(r,12),colorBgContainer:Zs(r,8),colorBgLayout:Zs(r,0),colorBgSpotlight:Zs(r,26),colorBgBlur:Vr(o,.04),colorBorder:Zs(r,26),colorBorderDisabled:Zs(r,26),colorBorderSecondary:Zs(r,19)}},ZG=(e,t)=>{const n=Object.keys(Qb).map(i=>{const l=gm(e[i],{theme:"dark"});return Array.from({length:10},()=>1).reduce((c,u,d)=>(c[`${i}-${d+1}`]=l[d],c[`${i}${d+1}`]=l[d],c),{})}).reduce((i,l)=>(i={...i,...l},i),{}),r=t??hm(e),o=vP(e,{generateColorPalettes:rE,generateNeutralColorPalettes:JG}),s=Oo.reduce((i,l)=>{const c=e[l];if(c){const u=rE(c);i[`${l}Hover`]=u[7],i[`${l}Active`]=u[5]}return i},{});return{...r,...n,...o,...s,colorPrimaryBg:o.colorPrimaryBorder,colorPrimaryBgHover:o.colorPrimaryBorderHover}};function eX(){const[e,t,n,r]=Yn();return{theme:e,token:t,hashId:n,cssVar:r}}const tX={defaultSeed:Ec.token,useToken:eX,defaultAlgorithm:hm,darkAlgorithm:ZG,compactAlgorithm:QG,getDesignToken:XG,defaultConfig:Ec,_internalContext:Zb};var VM={};Object.defineProperty(VM,"__esModule",{value:!0});var nX={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"}}]},name:"delete",theme:"outlined"},rX=VM.default=nX;function d0(){return d0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,d0({},e,{ref:t,icon:rX})),u$=a.forwardRef(oX);var WM={};Object.defineProperty(WM,"__esModule",{value:!0});var sX={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"}}]},name:"edit",theme:"outlined"},iX=WM.default=sX;function f0(){return f0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,f0({},e,{ref:t,icon:iX})),uu=a.forwardRef(aX);var KM={};Object.defineProperty(KM,"__esModule",{value:!0});var lX={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 000 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z"}}]},name:"enter",theme:"outlined"},cX=KM.default=lX;function m0(){return m0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,m0({},e,{ref:t,icon:cX})),dX=a.forwardRef(uX),fX=(e,t,n,r)=>{const{titleMarginBottom:o,fontWeightStrong:s}=r;return{marginBottom:o,color:n,fontWeight:s,fontSize:e,lineHeight:t}},mX=e=>{const t=[1,2,3,4,5],n={};return t.forEach(r=>{n[` + h${r}&, + div&-h${r}, + div&-h${r} > textarea, + h${r} + `]=fX(e[`fontSizeHeading${r}`],e[`lineHeightHeading${r}`],e.colorTextHeading,e)}),n},pX=e=>{const{componentCls:t}=e;return{[`&${`${t}-link`}`]:{...ex(e),userSelect:"text",[`&[disabled], &${t}-disabled`]:{color:e.colorTextDisabled,cursor:"not-allowed","&:active, &:hover":{color:e.colorTextDisabled},"&:active":{pointerEvents:"none",[`${t}-actions`]:{pointerEvents:"auto"}}}}}},gX=e=>({code:{margin:"0 0.2em",paddingInline:"0.4em",paddingBlock:"0.2em 0.1em",fontSize:"85%",fontFamily:e.fontFamilyCode,background:"rgba(150, 150, 150, 0.1)",border:"1px solid rgba(100, 100, 100, 0.2)",borderRadius:3},kbd:{margin:"0 0.2em",paddingInline:"0.4em",paddingBlock:"0.15em 0.1em",fontSize:"90%",fontFamily:e.fontFamilyCode,background:"rgba(150, 150, 150, 0.06)",border:"1px solid rgba(100, 100, 100, 0.2)",borderBottomWidth:2,borderRadius:3},mark:{padding:0,backgroundColor:rf[2]},"u, ins":{textDecoration:"underline",textDecorationSkipInk:"auto"},"s, del":{textDecoration:"line-through"},strong:{fontWeight:e.fontWeightStrong},"ul, ol":{marginInline:0,marginBlock:"0 1em",padding:0,li:{marginInline:"20px 0",marginBlock:0,paddingInline:"4px 0",paddingBlock:0}},ul:{listStyleType:"circle",ul:{listStyleType:"disc"}},ol:{listStyleType:"decimal"},"pre, blockquote":{margin:"1em 0"},pre:{padding:"0.4em 0.6em",whiteSpace:"pre-wrap",wordWrap:"break-word",background:"rgba(150, 150, 150, 0.1)",border:"1px solid rgba(100, 100, 100, 0.2)",borderRadius:3,fontFamily:e.fontFamilyCode,code:{display:"inline",margin:0,padding:0,fontSize:"inherit",fontFamily:"inherit",background:"transparent",border:0}},blockquote:{paddingInline:"0.6em 0",paddingBlock:0,borderInlineStart:"4px solid rgba(100, 100, 100, 0.2)",opacity:.85},table:{width:"100%",textAlign:"start",borderCollapse:"separate",borderSpacing:0,marginBlock:"1em","th, td":{padding:G(e.padding),overflowWrap:"break-word",borderBottom:`${G(e.lineWidth)} ${e.lineType} ${e.colorSplit}`},"thead > tr:first-child > th:first-child":{borderStartStartRadius:e.borderRadiusLG},"thead > tr:first-child > th:last-child":{borderStartEndRadius:e.borderRadiusLG},"thead > tr > th":{textAlign:"start",position:"relative",color:e.colorTextHeading,fontWeight:e.fontWeightStrong,backgroundColor:e.colorFillAlter,transition:`background-color ${e.motionDurationMid} ease`,"&:not(:last-child)::before":{position:"absolute",top:"50%",insetInlineEnd:0,width:1,height:"1.6em",backgroundColor:e.colorSplit,transform:"translateY(-50%)",content:'""'}},"tbody > tr":{"> th, > td":{transition:`background-color ${e.motionDurationMid} ease`},"&:hover > th, &:hover > td":{backgroundColor:e.colorFillAlter}}}}),hX=e=>{const{componentCls:t,paddingSM:n}=e,r=n;return{"&-edit-content":{position:"relative","div&":{insetInlineStart:e.calc(e.paddingSM).mul(-1).equal(),insetBlockStart:e.calc(r).div(-2).add(1).equal(),marginBottom:e.calc(r).div(2).sub(2).equal()},[`${t}-edit-content-confirm`]:{position:"absolute",insetInlineEnd:e.calc(e.marginXS).add(2).equal(),insetBlockEnd:e.marginXS,color:e.colorIcon,fontWeight:"normal",fontSize:e.fontSize,fontStyle:"normal",pointerEvents:"none"},textarea:{margin:"0!important",MozTransition:"none",height:"1em"}}}},yX=e=>({[`${e.componentCls}-copy-success`]:{"&, &:hover, &:focus":{color:e.colorSuccess}},[`${e.componentCls}-copy-icon-only`]:{marginInlineStart:0}}),vX=()=>({"a&-ellipsis, span&-ellipsis":{display:"inline-block",maxWidth:"100%"},"&-ellipsis-single-line":{...ar,"a&, span&":{verticalAlign:"bottom"},"> code":{paddingBlock:0,maxWidth:"calc(100% - 1.2em)",display:"inline-block",overflow:"hidden",textOverflow:"ellipsis",verticalAlign:"bottom",boxSizing:"content-box"}},"&-ellipsis-multiple-line":{display:"-webkit-box",overflow:"hidden",WebkitLineClamp:3,WebkitBoxOrient:"vertical"}}),bX=e=>{const{componentCls:t,titleMarginTop:n}=e;return{[t]:{color:e.colorText,wordBreak:"break-word",lineHeight:e.lineHeight,[`&${t}-secondary, &${t}-link${t}-secondary`]:{color:e.colorTextDescription},[`&${t}-success, &${t}-link${t}-success`]:{color:e.colorSuccessText},[`&${t}-warning, &${t}-link${t}-warning`]:{color:e.colorWarningText},[`&${t}-danger, &${t}-link${t}-danger`]:{color:e.colorErrorText,[`&${t}-link:active, &${t}-link:focus`]:{color:e.colorErrorTextActive},[`&${t}-link:hover`]:{color:e.colorErrorTextHover}},[`&${t}-disabled`]:{color:e.colorTextDisabled,cursor:"not-allowed",userSelect:"none"},"div&, p":{marginBottom:"1em"},...mX(e),[`& + h1${t}, & + h2${t}, & + h3${t}, & + h4${t}, & + h5${t}`]:{marginTop:n},"div, ul, li, p, h1, h2, h3, h4, h5":{"+ h1, + h2, + h3, + h4, + h5":{marginTop:n}},...gX(e),...pX(e),[`${t}-actions`]:{display:"inline"},[` + ${t}-expand, + ${t}-collapse, + ${t}-edit, + ${t}-copy + `]:{...ex(e),marginInlineStart:e.marginXXS},[`${t}-actions-start`]:{[` + ${t}-expand, + ${t}-collapse, + ${t}-edit, + ${t}-copy:not(${t}-copy-icon-only) + `]:{marginInlineStart:0,marginInlineEnd:e.marginXXS}},...hX(e),...yX(e),...vX(),"&-rtl":{direction:"rtl"}}}},xX=()=>({titleMarginTop:"1.2em",titleMarginBottom:"0.5em"}),UM=Tt("Typography",bX,xX),$X=e=>{const{prefixCls:t,"aria-label":n,className:r,style:o,classNames:s,styles:i,direction:l,maxLength:c,autoSize:u=!0,value:d,onSave:m,onCancel:f,onEnd:p,component:y,enterIcon:b=a.createElement(dX,null)}=e,x=a.useRef(null),v=a.useRef(!1),g=a.useRef(null),[h,$]=a.useState(d);a.useEffect(()=>{$(d)},[d]),a.useEffect(()=>{var B;if((B=x.current)!=null&&B.resizableTextArea){const{textArea:F}=x.current.resizableTextArea;F.focus();const{length:L}=F.value;F.setSelectionRange(L,L)}},[]);const C=({target:B})=>{$(B.value.replace(/[\n\r]/g,""))},N=()=>{v.current=!0},S=()=>{v.current=!1},E=({keyCode:B})=>{v.current||(g.current=B)},w=()=>{m(h.trim())},R=({keyCode:B,ctrlKey:F,altKey:L,metaKey:j,shiftKey:O})=>{g.current!==B||v.current||F||L||j||O||(B===nt.ENTER?(w(),p==null||p()):B===nt.ESC&&f())},P=()=>{w()},[T,M]=UM(t),z=H(t,`${t}-edit-content`,{[`${t}-rtl`]:l==="rtl",[`${t}-${y}`]:!!y},r,s.root,T,M);return a.createElement("div",{className:z,style:{...i.root,...o}},a.createElement(HT,{ref:x,maxLength:c,value:h,onChange:C,onKeyDown:E,onKeyUp:R,onCompositionStart:N,onCompositionEnd:S,onBlur:P,"aria-label":n,rows:1,autoSize:u,className:s.textarea,style:i.textarea}),b!==null?Fn(b,{className:`${t}-edit-content-confirm`}):null)},SX=(e,t)=>{let n=!1;const r=o=>{var s,i,l;o.stopPropagation(),o.preventDefault(),(s=o.clipboardData)==null||s.clearData(),(i=o.clipboardData)==null||i.setData("text/plain",e),t&&((l=o.clipboardData)==null||l.setData("text/html",e)),n=!0};try{return document.addEventListener("copy",r,{capture:!0}),document.execCommand("copy"),n}catch{return!1}finally{document.removeEventListener("copy",r,{capture:!0})}},CX=async(e,t)=>{try{return t?await navigator.clipboard.write([new ClipboardItem({"text/html":new Blob([e],{type:"text/html"}),"text/plain":new Blob([e],{type:"text/plain"})})]):await navigator.clipboard.writeText(e),!0}catch{return!1}};async function wX(e,t){if(typeof e!="string")return!1;const n=(t==null?void 0:t.format)==="text/html";return!!(await CX(e,n)||SX(e,n))}const EX=({copyConfig:e,children:t})=>{const[n,r]=a.useState(!1),[o,s]=a.useState(!1),i=a.useRef(null),l=()=>{i.current&&clearTimeout(i.current)},c={};e.format&&(c.format=e.format),a.useEffect(()=>l,[]);const u=vt(async d=>{var m;d==null||d.preventDefault(),d==null||d.stopPropagation(),s(!0);try{const f=bt(e.text)?await e.text():e.text;await wX(f||QT(t,{skipEmpty:!0}).join("")||"",c),s(!1),r(!0),l(),i.current=setTimeout(()=>{r(!1)},3e3),(m=e.onCopy)==null||m.call(e,d)}catch(f){throw s(!1),f}});return{copied:n,copyLoading:o,onClick:u}},wg=(e,t)=>{const n=!!e;return a.useMemo(()=>{const r={...t,...n&&dt(e)?e:null};return[n,r]},[n,e,t])},IX=e=>{const t=a.useRef(void 0);return a.useEffect(()=>{t.current=e}),t.current},PX=(e,t,n)=>a.useMemo(()=>e===!0?{title:t??n}:a.isValidElement(e)?{title:e}:dt(e)?{title:t??n,...e}:{title:e},[e,t,n]),qM=(e,t,n,r,o)=>{const{getPrefixCls:s,direction:i,className:l,style:c,classNames:u,styles:d}=Pt("typography"),m=r??i,f=s("typography",e),p={...o,prefixCls:f,direction:m},y=a.useMemo(()=>({root:l}),[l]),b=a.useMemo(()=>({root:c}),[c]),[x,v]=Ot([y,u,t],[b,d,n],{props:p});return[x,v,f,m]},GM=a.forwardRef((e,t)=>{const{component:n="article",className:r,rootClassName:o,children:s,direction:i,style:l,classNames:c,styles:u,prefixCls:d,...m}=e,[f,p]=UM(d),y=H(d,{[`${d}-rtl`]:i==="rtl"},r,o,f,p,c==null?void 0:c.root),b={...u==null?void 0:u.root,...l};return a.createElement(n,{...m,className:y,style:b,ref:t},s)}),NX=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,rootClassName:o,direction:s,classNames:i,styles:l,...c}=e,[u,d,m,f]=qM(n,i,l,s,e);return a.createElement(GM,{ref:t,className:H(r,o),direction:f,classNames:u,styles:d,prefixCls:m,...c})});var XM={};Object.defineProperty(XM,"__esModule",{value:!0});var RX={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"}}]},name:"copy",theme:"outlined"},TX=XM.default=RX;function p0(){return p0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,p0({},e,{ref:t,icon:TX})),OX=a.forwardRef(MX),oE=e=>e===!1?[!1,!1]:QT(e);function Eg(e,t,n){return e===!0||e===void 0?t:e||n&&t}function _X(e){const t=document.createElement("em");e.appendChild(t);const n=e.getBoundingClientRect(),r=t.getBoundingClientRect();return e.removeChild(t),n.left>r.left||r.right>n.right||n.top>r.top||r.bottom>n.bottom}const d$=e=>["string","number"].includes(typeof e),zX=e=>{const{prefixCls:t,copied:n,locale:r,iconOnly:o,tooltips:s,icon:i,tabIndex:l,onCopy:c,loading:u,className:d,style:m}=e,f=oE(s),p=oE(i),{copied:y,copy:b}=r??{},x=n?y:b,v=Eg(f[n?1:0],x),g=typeof v=="string"?v:x;return a.createElement(bo,{title:v},a.createElement("button",{type:"button",className:H(`${t}-copy`,d,{[`${t}-copy-success`]:n,[`${t}-copy-icon-only`]:o}),style:m,onClick:c,"aria-label":g,tabIndex:l},n?Eg(p[1],a.createElement(Nx,null),!0):Eg(p[0],u?a.createElement(ki,null):a.createElement(OX,null),!0)))},Yu=a.forwardRef(({style:e,children:t},n)=>{const r=a.useRef(null);return a.useImperativeHandle(n,()=>({isExceed:()=>{const o=r.current;return o.scrollHeight>o.clientHeight},getHeight:()=>r.current.clientHeight})),a.createElement("span",{"aria-hidden":!0,ref:r,style:{position:"fixed",display:"block",left:0,top:0,pointerEvents:"none",backgroundColor:"rgba(255, 0, 0, 0.65)",...e}},t)}),jX=e=>e.reduce((t,n)=>t+(d$(n)?String(n).length:1),0);function sE(e,t){let n=0;const r=[];for(let o=0;ot){const u=t-n;return r.push(String(s).slice(0,u)),r}r.push(s),n=c}return e}const Ig=0,Pg=1,Ng=2,Rg=3,iE=4,Qu={display:"-webkit-box",overflow:"hidden",WebkitBoxOrient:"vertical"};function BX(e){const{enableMeasure:t,width:n,text:r,children:o,rows:s,expanded:i,measureDeps:l,miscDeps:c,onEllipsis:u}=e,d=a.useMemo(()=>zn(r),[r]),m=a.useMemo(()=>jX(d),[r]),f=a.useMemo(()=>o(d,!1),[r].concat($t(l))),[p,y]=a.useState(null),b=a.useRef(null),x=a.useRef(null),v=a.useRef(null),g=a.useRef(null),h=a.useRef(null),[$,C]=a.useState(!1),[N,S]=a.useState(Ig),[E,w]=a.useState(0),[R,P]=a.useState(null);It(()=>{S(t&&n&&m?Pg:Ig)},[n,r,s,t,d].concat($t(l))),It(()=>{var B,F,L,j;if(N===Pg){S(Ng);const O=x.current&&getComputedStyle(x.current).whiteSpace;P(O)}else if(N===Ng){const O=!!((B=v.current)!=null&&B.isExceed());S(O?Rg:iE),y(O?[0,m]:null),C(O);const A=((F=v.current)==null?void 0:F.getHeight())||0,k=s===1?0:((L=g.current)==null?void 0:L.getHeight())||0,_=((j=h.current)==null?void 0:j.getHeight())||0,D=Math.max(A,k+_);w(D+1),u(O)}},[N]);const T=p?Math.ceil((p[0]+p[1])/2):0;It(()=>{var L;const[B,F]=p||[0,0];if(B!==F){const O=(((L=b.current)==null?void 0:L.getHeight())||0)>E;let A=T;F-B===1&&(A=O?B:F),y(O?[B,A]:[A,F])}},[p,T]);const M=a.useMemo(()=>{if(!t)return o(d,!1);if(N!==Rg||!p||p[0]!==p[1]){const B=o(d,!1);return[iE,Ig].includes(N)?B:a.createElement("span",{style:{...Qu,WebkitLineClamp:s}},B)}return o(i?d:sE(d,p[0]),$)},[i,N,p,d].concat($t(c))),z={width:n,margin:0,padding:0,whiteSpace:R==="nowrap"?"normal":"inherit"};return a.createElement(a.Fragment,null,M,N===Ng&&a.createElement(a.Fragment,null,a.createElement(Yu,{style:{...z,...Qu,WebkitLineClamp:s},ref:v},f),a.createElement(Yu,{style:{...z,...Qu,WebkitLineClamp:s-1},ref:g},f),a.createElement(Yu,{style:{...z,...Qu,WebkitLineClamp:1},ref:h},o([],!0))),N===Rg&&p&&p[0]!==p[1]&&a.createElement(Yu,{style:{...z,top:400},ref:b},o(sE(d,T),!0)),N===Pg&&a.createElement("span",{style:{whiteSpace:"inherit"},ref:x}))}const LX=({enableEllipsis:e,isEllipsis:t,open:n,children:r,tooltipProps:o})=>{if(!(o!=null&&o.title)||!e)return r;const s=n&&t;return a.createElement(bo,{open:s,...o},r)};function kX({mark:e,code:t,underline:n,delete:r,strong:o,keyboard:s,italic:i},l){let c=l;function u(d,m){m&&(c=a.createElement(d,{},c))}return u("strong",o),u("u",n),u("del",r),u("code",t),u("mark",e),u("kbd",s),u("i",i),c}const AX="...",aE=["delete","mark","code","underline","strong","keyboard","italic"],Um=a.forwardRef((e,t)=>{const{prefixCls:n,className:r,style:o,classNames:s,styles:i,direction:l,type:c,disabled:u,children:d,ellipsis:m,editable:f,copyable:p,actions:y,component:b,title:x,onMouseEnter:v,onMouseLeave:g,...h}=e,[$]=Ar("Text"),C=a.useRef(null),N=a.useRef(null),[S,E,w,R]=qM(n,s,i,l,e),P=Dt(h,aE),[T,M]=wg(f),[z,B]=nn(!1,M.editing),{triggerType:F=["icon"]}=M,L=Pe=>{var pe;Pe&&((pe=M.onStart)==null||pe.call(M)),B(Pe)},j=IX(z);It(()=>{var Pe;!z&&j&&((Pe=N.current)==null||Pe.focus())},[z]);const O=Pe=>{Pe==null||Pe.preventDefault(),L(!0)},A=Pe=>{var pe;(pe=M.onChange)==null||pe.call(M,Pe),L(!1)},k=()=>{var Pe;(Pe=M.onCancel)==null||Pe.call(M),L(!1)},[_,D]=wg(p),{placement:V="end"}=y??{},{copied:W,copyLoading:K,onClick:q}=EX({copyConfig:D,children:d}),[Y,ee]=a.useState(!1),[ie,ae]=a.useState(!1),[U,Q]=a.useState(!1),[Z,ne]=a.useState(!1),[oe,le]=a.useState(!0),[re,X]=wg(m,{expandable:!1,symbol:Pe=>Pe?$==null?void 0:$.collapse:$==null?void 0:$.expand}),[se,ge]=nn(X.defaultExpanded||!1,X.expanded),de=re&&(!se||X.expandable==="collapsible"),{rows:Se=1}=X,ue=a.useMemo(()=>de&&(X.suffix!==void 0||X.onEllipsis||X.expandable||T||_),[de,X,T,_]);It(()=>{re&&!ue&&(ee(BS("webkitLineClamp")),ae(BS("textOverflow")))},[ue,re]);const[be,Ne]=a.useState(de),we=a.useMemo(()=>ue?!1:Se===1?ie:Y,[ue,Se,Y,ie]);It(()=>{Ne(we&&de)},[we,de]);const ze=PX(X.tooltip,M.text,d),he=be&&!!ze.title,ke=de&&(be?he&&Z:U),Oe=de&&Se===1&&be,Ce=de&&Se>1&&be,Me=(Pe,pe)=>{var $e;ge(pe.expanded),($e=X.onExpand)==null||$e.call(X,Pe,pe)},[xe,Ee]=a.useState(0),[Ve,qe]=a.useState(!1),[me,Re]=a.useState(!1),Te=({offsetWidth:Pe})=>{Ee(Pe)},Ue=Pe=>{var pe;Q(Pe),U!==Pe&&((pe=X.onEllipsis)==null||pe.call(X,Pe))};a.useEffect(()=>{const Pe=C.current;if(re&&he&&Pe){const pe=_X(Pe);Z!==pe&&ne(pe)}},[re,he,d,Ce,oe,xe]),a.useEffect(()=>{const Pe=C.current;if(typeof IntersectionObserver>"u"||!Pe||!he||!de)return;const pe=new IntersectionObserver(()=>{le(!!Pe.offsetParent)});return pe.observe(Pe),()=>{pe.disconnect()}},[he,de]);const Ge=a.useMemo(()=>{if(!(!re||be))return[M.text,d,x,ze.title].find(d$)},[re,be,x,ze.title,ke,M.text]);if(z)return a.createElement($X,{value:M.text??(typeof d=="string"?d:""),onSave:A,onCancel:k,onEnd:M.onEnd,prefixCls:w,className:r,style:o,direction:R,component:b,maxLength:M.maxLength,autoSize:M.autoSize,enterIcon:M.enterIcon,classNames:S,styles:E});const Fe=()=>{const{expandable:Pe,symbol:pe}=X;return Pe?a.createElement("button",{type:"button",key:"expand",className:H(`${w}-${se?"collapse":"expand"}`,S.action),style:E.action,onClick:$e=>Me($e,{expanded:!se}),"aria-label":se?$.collapse:$==null?void 0:$.expand},bt(pe)?pe(se):pe):null},et=()=>{if(!T)return;const{icon:Pe,tooltip:pe,tabIndex:$e}=M,_e=zn(pe)[0]||($==null?void 0:$.edit),Ie=typeof _e=="string"?_e:"";return F.includes("icon")?a.createElement(bo,{key:"edit",title:pe===!1?"":_e},a.createElement("button",{type:"button",ref:N,className:H(`${w}-edit`,S.action),style:E.action,onClick:O,"aria-label":Ie,tabIndex:$e},Pe||a.createElement(uu,{role:"button"}))):null},ve=()=>_?a.createElement(zX,{key:"copy",...D,prefixCls:w,copied:W,locale:$,onCopy:q,loading:K,iconOnly:!$n(d),className:S.action,style:E.action}):null,je=Pe=>{const pe=Pe&&Fe(),$e=et(),_e=ve();return!pe&&!$e&&!_e?null:a.createElement("span",{key:"operations",className:H(`${w}-actions`,S.actions,{[`${w}-actions-start`]:V==="start"}),style:E.actions,onMouseEnter:()=>qe(!0),onMouseLeave:()=>qe(!1)},pe,$e,_e)},ce=Pe=>[Pe&&!se&&a.createElement("span",{"aria-hidden":!0,key:"ellipsis"},AX),X.suffix];return a.createElement(ir,{onResize:Te,disabled:!de},Pe=>a.createElement(LX,{tooltipProps:ze,enableEllipsis:de,isEllipsis:ke,open:me&&!Ve},a.createElement(GM,{onMouseEnter:pe=>{Re(!0),v==null||v(pe)},onMouseLeave:pe=>{Re(!1),g==null||g(pe)},className:H({[`${w}-${c}`]:c,[`${w}-disabled`]:u,[`${w}-ellipsis`]:re,[`${w}-ellipsis-single-line`]:Oe,[`${w}-ellipsis-multiple-line`]:Ce,[`${w}-link`]:b==="a"},r),classNames:S,styles:E,prefixCls:w,style:{...o,WebkitLineClamp:Ce?Se:void 0},component:b,ref:Tn(Pe,C,t),direction:R,onClick:F.includes("text")?O:void 0,"aria-label":Ge==null?void 0:Ge.toString(),title:x,...P},a.createElement(BX,{enableMeasure:de&&!be,text:d,rows:Se,width:xe,onEllipsis:Ue,expanded:se,measureDeps:[V],miscDeps:[W,se,K,T,_,V,$].concat($t(aE.map(pe=>e[pe])))},(pe,$e)=>kX(e,a.createElement(a.Fragment,null,V==="start"?je($e):null,pe.length>0&&$e&&!se&&Ge?a.createElement("span",{key:"show-content","aria-hidden":!0},pe):pe,ce($e),V==="start"?null:je($e)))))))}),DX=a.forwardRef((e,t)=>{const{ellipsis:n,rel:r,children:o,navigate:s,...i}=e,l={...i,rel:r===void 0&&i.target==="_blank"?"noopener noreferrer":r};return a.createElement(Um,{...l,ref:t,ellipsis:!!n,component:"a"},o)}),FX=a.forwardRef((e,t)=>{const{children:n,...r}=e;return a.createElement(Um,{ref:t,...r,component:"div"},n)}),HX=a.forwardRef((e,t)=>{const{ellipsis:n,children:r,...o}=e,s=a.useMemo(()=>dt(n)?Dt(n,["expandable","rows"]):n,[n]);return a.createElement(Um,{ref:t,...o,ellipsis:s,component:"span"},r)}),VX=[1,2,3,4,5],WX=a.forwardRef((e,t)=>{const{level:n=1,children:r,...o}=e,s=VX.includes(n)?`h${n}`:"h1";return a.createElement(Um,{ref:t,...o,component:s},r)}),ot=NX;ot.Text=HX;ot.Link=DX;ot.Title=WX;ot.Paragraph=FX;var qm={},YM={exports:{}};(function(e){function t(n){return n&&n.__esModule?n:{default:n}}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports})(YM);var f$=YM.exports,Gm={};Object.defineProperty(Gm,"__esModule",{value:!0});Gm.default=void 0;const KX={items_per_page:"条/页",jump_to:"跳至",jump_to_confirm:"确定",page:"页",prev_page:"上一页",next_page:"下一页",prev_5:"向前 5 页",next_5:"向后 5 页",prev_3:"向前 3 页",next_3:"向后 3 页",page_size:"页码"};Gm.default=KX;var Xm={},du={},Ym={},Qm={};Object.defineProperty(Qm,"__esModule",{value:!0});Qm.commonLocale=void 0;Qm.commonLocale={yearFormat:"YYYY",dayFormat:"D",cellMeridiemFormat:"A",monthBeforeYear:!0};Object.defineProperty(Ym,"__esModule",{value:!0});Ym.default=void 0;var UX=Qm;const qX={...UX.commonLocale,locale:"zh_CN",today:"今天",now:"此刻",backToToday:"返回今天",ok:"确定",timeSelect:"选择时间",dateSelect:"选择日期",weekSelect:"选择周",clear:"清除",week:"周",month:"月",year:"年",previousMonth:"上个月 (翻页上键)",nextMonth:"下个月 (翻页下键)",monthSelect:"选择月份",yearSelect:"选择年份",decadeSelect:"选择年代",previousYear:"上一年 (Control键加左方向键)",nextYear:"下一年 (Control键加右方向键)",previousDecade:"上一年代",nextDecade:"下一年代",previousCentury:"上一世纪",nextCentury:"下一世纪",yearFormat:"YYYY年",cellDateFormat:"D",monthBeforeYear:!1};Ym.default=qX;var fu={};Object.defineProperty(fu,"__esModule",{value:!0});fu.default=void 0;const GX={placeholder:"请选择时间",rangePlaceholder:["开始时间","结束时间"]};fu.default=GX;var QM=f$.default;Object.defineProperty(du,"__esModule",{value:!0});du.default=void 0;var XX=QM(Ym),YX=QM(fu);const JM={lang:{placeholder:"请选择日期",yearPlaceholder:"请选择年份",quarterPlaceholder:"请选择季度",monthPlaceholder:"请选择月份",weekPlaceholder:"请选择周",rangePlaceholder:["开始日期","结束日期"],rangeYearPlaceholder:["开始年份","结束年份"],rangeMonthPlaceholder:["开始月份","结束月份"],rangeQuarterPlaceholder:["开始季度","结束季度"],rangeWeekPlaceholder:["开始周","结束周"],...XX.default},timePickerLocale:{...YX.default}};JM.lang.ok="确定";du.default=JM;var QX=f$.default;Object.defineProperty(Xm,"__esModule",{value:!0});Xm.default=void 0;var JX=QX(du);Xm.default=JX.default;var Jm=f$.default;Object.defineProperty(qm,"__esModule",{value:!0});qm.default=void 0;var ZX=Jm(Gm),eY=Jm(Xm),tY=Jm(du),nY=Jm(fu);const Pr="${label}不是一个有效的${type}",rY={locale:"zh-cn",Pagination:ZX.default,DatePicker:tY.default,TimePicker:nY.default,Calendar:eY.default,global:{placeholder:"请选择",close:"关闭",sortable:"可排序",show:"显示",hide:"隐藏"},Table:{filterTitle:"筛选",filterConfirm:"确定",filterReset:"重置",filterEmptyText:"无筛选项",filterCheckAll:"全选",filterSearchPlaceholder:"在筛选项中搜索",emptyText:"暂无数据",selectAll:"全选当页",selectInvert:"反选当页",selectNone:"清空所有",selectionAll:"全选所有",sortTitle:"排序",expand:"展开行",collapse:"关闭行",triggerDesc:"点击降序",triggerAsc:"点击升序",cancelSort:"取消排序"},Modal:{okText:"确定",cancelText:"取消",justOkText:"知道了"},Tour:{Next:"下一步",Previous:"上一步",Finish:"结束导览"},Popconfirm:{cancelText:"取消",okText:"确定"},Transfer:{titles:["",""],searchPlaceholder:"请输入搜索内容",itemUnit:"项",itemsUnit:"项",remove:"删除",selectCurrent:"全选当页",removeCurrent:"删除当页",selectAll:"全选所有",deselectAll:"取消全选",removeAll:"删除全部",selectInvert:"反选当页"},Upload:{uploading:"文件上传中",removeFile:"删除文件",uploadError:"上传错误",previewFile:"预览文件",downloadFile:"下载文件"},Empty:{description:"暂无数据"},Icon:{icon:"图标"},Text:{edit:"编辑",copy:"复制",copied:"复制成功",expand:"展开",collapse:"收起"},Form:{optional:"(可选)",defaultValidateMessages:{default:"字段验证错误${label}",required:"请输入${label}",enum:"${label}必须是其中一个[${enum}]",whitespace:"${label}不能为空字符",date:{format:"${label}日期格式无效",parse:"${label}不能转换为日期",invalid:"${label}是一个无效日期"},types:{string:Pr,method:Pr,array:Pr,object:Pr,number:Pr,date:Pr,boolean:Pr,integer:Pr,float:Pr,regexp:Pr,email:Pr,url:Pr,hex:Pr},string:{len:"${label}须为${len}个字符",min:"${label}最少${min}个字符",max:"${label}最多${max}个字符",range:"${label}须在${min}-${max}字符之间"},number:{len:"${label}必须等于${len}",min:"${label}最小值为${min}",max:"${label}最大值为${max}",range:"${label}须在${min}-${max}之间"},array:{len:"须为${len}个${label}",min:"最少${min}个${label}",max:"最多${max}个${label}",range:"${label}数量须在${min}-${max}之间"},pattern:{mismatch:"${label}与模式不匹配${pattern}"}}},QRCode:{expired:"二维码过期",refresh:"点击刷新",scanned:"已扫描"},ColorPicker:{presetEmpty:"暂无",transparent:"无色",singleColor:"单色",gradientColor:"渐变色"}};qm.default=rY;var oY=qm;const sY=k0(oY);let m$=null;function iY(){return m$}function Tg(){m$=null}function lE(e){m$=e}function aY(e){const t=typeof window<"u"?window.__AJZ_PREVIEW__:void 0;return!(t!=null&&t.blueprint)||t.id&&t.id!==e?null:{id:String(t.id||e),blueprint:t.blueprint,rows:Array.isArray(t.rows)?t.rows:[],resource:String(t.resource||"records")}}async function lY(e){const t=aY(e);if(t)return lE(t),t;const n=await fetch(`/ai/api/v1/preview/${encodeURIComponent(e)}`),r=await n.json();if(!n.ok||!(r!=null&&r.ok))throw new Error((r==null?void 0:r.error)||`预览不存在或已过期 (${n.status})`);const o={id:String(r.id||e),blueprint:r.blueprint,rows:Array.isArray(r.rows)?r.rows:[],resource:String(r.resource||"records")};return lE(o),o}function Ju(){const t=(window.location.hash||"").replace(/^#/,"").match(/^\/?preview\/([a-zA-Z0-9_-]{6,32})\/?$/);return t?t[1]:null}const ZM=[{title:"模块",items:[{id:"读取模块",label:"读取模块"},{id:"写入模块",label:"写入模块"},{id:"发布模块",label:"发布模块"}]},{title:"数据",items:[{id:"查询数据",label:"查询数据"},{id:"新增数据",label:"新增数据"},{id:"更新数据",label:"更新数据"},{id:"删除数据",label:"删除数据"},{id:"导入数据",label:"导入数据"},{id:"导出数据",label:"导出数据"}]},{title:"其他",items:[{id:"下载文件",label:"下载文件"},{id:"上传文件",label:"上传文件"},{id:"查看审计",label:"查看审计"}]}],e6={"app.read":"读取模块","app.write":"写入模块","app.admin":"发布模块","row.create":"新增数据","row.read":"查询数据","row.update":"更新数据","row.delete":"删除数据","row.export":"导出数据","row.import":"导入数据","audit.read":"查看审计","storage.write":"上传文件","storage.read":"下载文件","agent.admin":"管理智能体","tenant.invite":"邀请成员","org.admin":"管理组织","sync.admin":"数据同步"},cY={...Object.fromEntries(ZM.flatMap(e=>e.items.map(t=>[t.id,t.label]))),管理智能体:"管理智能体",邀请成员:"邀请成员",管理组织:"管理组织",数据同步:"数据同步"};function Of(e){const t=e6[e]||e;return cY[t]||t}function Zm(e){switch(e){case"platform_admin":case"super_admin":case"超级管理员":return"超级管理员";case"owner":case"管理员":return"管理员";case"editor":case"编辑":return"编辑";case"viewer":case"只读":return"只读";case"pending":case"待加入":return"待加入";case"agent":case"智能体":return"智能体";default:return e}}function nc(e){return Zm(e)}function Nr(e){return Zm(e||"")==="超级管理员"}function zl(e){return Zm(e||"")==="管理员"}function uY(e){var r;const t=((r=e.allowedModules)!=null&&r.length?e.allowedModules:ZM.map(o=>({title:o.title,items:o.items.map(s=>({perm:s.id,desc:s.label}))}))).map(o=>({title:o.title,items:o.items||(o.perms||[]).map(s=>({perm:s,desc:Of(s)}))})),n=e.value.map(o=>e6[o]||o);return I.jsx(Vt,{direction:"vertical",size:"middle",style:{width:"100%"},children:t.map(o=>{const s=o.items.map(i=>i.perm);return I.jsxs("div",{children:[I.jsx(ot.Text,{type:"secondary",strong:!0,children:o.title}),I.jsx("div",{style:{marginTop:8},children:I.jsx(as.Group,{style:{width:"100%",display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(200px, 1fr))",gap:8},value:n.filter(i=>s.includes(i)),onChange:i=>{const l=new Set(s),c=n.filter(u=>!l.has(u));e.onChange([...c,...i])},options:o.items.map(i=>({label:I.jsxs("span",{children:[Of(i.perm),i.desc?I.jsxs(I.Fragment,{children:[I.jsx("br",{}),I.jsx(ot.Text,{type:"secondary",style:{fontSize:12},children:i.desc})]}):null]}),value:i.perm}))})})]},o.title)})})}function dY(e,t){return e==null?!0:e.includes(t)}const Xn="",Lc="/ai",p$="ajz_session";function Mg(e){if(!e)return!1;const t=Zm(e.role||"");return t==="超级管理员"||e.username==="ljk_admin"?!1:t==="待加入"||e.status==="pending"||!e.tenantId}function t6(e){if(!e||e.split(".").length<2)return"";try{const t=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),n=t+"=".repeat((4-t.length%4)%4),r=atob(n),o=Uint8Array.from(r,i=>i.charCodeAt(0)),s=JSON.parse(new TextDecoder().decode(o));return typeof(s==null?void 0:s.role)=="string"?s.role:""}catch{return""}}function Oa(e){try{localStorage.removeItem(p$)}catch{}(e==null?void 0:e.emit)!==!1&&typeof window<"u"&&window.dispatchEvent(new CustomEvent("ajz:session-expired"))}function Og(){try{const e=localStorage.getItem(p$);if(!e)return null;const t=JSON.parse(e);return!(t!=null&&t.accessToken)||typeof t.accessToken!="string"||t.accessToken.split(".").length!==3?(Oa({emit:!1}),null):t.expiresAt&&t.expiresAt*1e3Object.entries(r).every(([x,v])=>!v||String(b[x]??"")===String(v))));const y=Math.max(0,(o-1)*s);return{items:p.slice(y,y+s),total:p.length}}const d={};e!=null&&e.accessToken&&(d.Authorization=`Bearer ${e.accessToken}`);const m=await st(c,{headers:d}),f=await it(m);return ut(m,f,"list failed"),f}async function $Y(e,t,n,r){if(!(e!=null&&e.accessToken))throw new Error("预览模式为只读,写操作请先登录控制台");const o=await st(`${Xn}/api/v1/apps/${t}/${n}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.accessToken}`},body:JSON.stringify(r)}),s=await it(o);return ut(o,s,"create failed"),s}async function SY(e,t,n,r,o){if(!(e!=null&&e.accessToken))throw new Error("预览模式为只读,写操作请先登录控制台");const s=await st(`${Xn}/api/v1/apps/${t}/${n}/${r}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.accessToken}`},body:JSON.stringify(o)}),i=await it(s);return ut(s,i,"update failed"),i}async function CY(e,t,n,r){if(!(e!=null&&e.accessToken))throw new Error("预览模式为只读,写操作请先登录控制台");const o=await st(`${Xn}/api/v1/apps/${t}/${n}/${r}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}});if(!o.ok&&o.status!==204){const s=await it(o).catch(()=>({}));ut(o,s,"delete failed")}}async function wY(e,t,n,r,o){const s=new URLSearchParams;r&&s.set("group_by",r),o&&s.set("sum",o);const i={};e!=null&&e.accessToken&&(i.Authorization=`Bearer ${e.accessToken}`);const l=await st(`${Xn}/api/v1/apps/${t}/${n}/aggregate?${s}`,{headers:i}),c=await it(l);return ut(l,c,"aggregate failed"),c}async function EY(e,t){const n=await st(`${Xn}/api/v1/apps/${t}/agent-capsule`,{headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"capsule failed"),r}async function IY(e,t,n,r){if(!(e!=null&&e.accessToken))throw new Error("预览模式为只读,写操作请先登录控制台");const o=new FormData;o.append("file",r);const s=await st(`${Xn}/api/v1/apps/${t}/${n}/import`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`},body:o}),i=await it(s);return ut(s,i,"import failed"),i}async function PY(e,t,n,r="xlsx"){if(!(e!=null&&e.accessToken))throw new Error("预览模式为只读,写操作请先登录控制台");const o=await st(`${Xn}/api/v1/apps/${t}/${n}/export?format=${r}`,{headers:{Authorization:`Bearer ${e.accessToken}`}});if(!o.ok){const c=await it(o).catch(()=>({}));ut(o,c,"export failed")}const s=await o.blob(),i=URL.createObjectURL(s),l=document.createElement("a");l.href=i,l.download=`${n}.${r==="csv"?"csv":"xlsx"}`,l.click(),URL.revokeObjectURL(i)}async function NY(e,t=1){const n=await st(`/api/v1/audit/logs?page=${t}&page_size=50`,{headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"audit failed"),r}async function RY(e){const t=await st("/api/v1/admin/agents",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list agents failed"),n}async function TY(e,t){const n=await st("/api/v1/admin/agents",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create agent failed"),r}async function uE(e,t,n){const r=await st(`/api/v1/admin/agents/${t}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)}),o=await it(r);return ut(r,o,"update agent failed"),o}async function MY(e,t){const n=await st(`/api/v1/admin/agents/${t}/rotate-secret`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"rotate secret failed"),r}async function OY(e,t){const n=await st(`/api/v1/admin/agents/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);ut(n,r,"delete agent failed")}async function r6(e){const t=await st("/api/v1/admin/roles",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list roles failed"),n}async function _Y(e,t){const n=await st("/api/v1/admin/roles",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create role failed"),r}async function zY(e,t,n){const r=await st(`/api/v1/admin/roles/${t}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)}),o=await it(r);return ut(r,o,"update role failed"),o}async function jY(e,t){const n=await st(`/api/v1/admin/roles/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);ut(n,r,"delete role failed")}async function BY(e,t){const n=await st("/api/v1/auth/invites/accept",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({code:t})}),r=await it(n);return ut(n,r,"accept invite failed"),ll(r)}async function LY(e,t){const n=await st("/api/v1/tenants",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({name:t})}),r=await it(n);return ut(n,r,"create tenant failed"),ll(r)}async function kY(e){const t=await st("/api/v1/admin/invites",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list invites failed"),n}async function AY(e,t){const n=await st("/api/v1/admin/invites",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create invite failed"),r}async function DY(e,t){const n=await st(`/api/v1/admin/invites/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);ut(n,r,"revoke invite failed")}async function g$(e){const t=await st("/api/v1/admin/org-units",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list org units failed"),n}async function FY(e,t){const n=await st("/api/v1/admin/org-units",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create org unit failed"),r}async function HY(e,t,n){const r=await st(`/api/v1/admin/org-units/${t}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)}),o=await it(r);return ut(r,o,"update org unit failed"),o}async function VY(e,t){const n=await st(`/api/v1/admin/org-units/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);ut(n,r,"delete org unit failed")}async function WY(e){const t=await st("/api/v1/admin/sync/channels",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list sync channels failed"),n}async function KY(e,t){const n=await st("/api/v1/admin/sync/channels",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create sync channel failed"),r}async function UY(e,t,n){const r=await st(`/api/v1/admin/sync/channels/${encodeURIComponent(t)}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)}),o=await it(r);return ut(r,o,"update sync channel failed"),o}async function qY(e,t){const n=await st(`/api/v1/admin/sync/channels/${encodeURIComponent(t)}`,{method:"DELETE",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);ut(n,r,"delete sync channel failed")}async function GY(e,t){const n=await st("/api/v1/admin/sync/test",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"test sync failed"),r}async function XY(e,t){const n=await st(`/api/v1/admin/sync/channels/${encodeURIComponent(t)}/start`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"start sync failed"),r}async function YY(e,t){const n=await st(`/api/v1/admin/sync/channels/${encodeURIComponent(t)}/stop`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"stop sync failed"),r}async function QY(e,t=!0){const r=await st(`/api/v1/admin/sync/conflicts${t?"?unresolved=1":"?unresolved=0"}`,{headers:{Authorization:`Bearer ${e.accessToken}`}}),o=await it(r);return ut(r,o,"list conflicts failed"),o}async function dE(e,t,n){const r=await st(`/api/v1/admin/sync/conflicts/${encodeURIComponent(t)}/resolve`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({resolution:n})}),o=await it(r);return ut(r,o,"resolve conflict failed"),o}async function JY(e,t){const n=await st(`/api/v1/admin/sync/channels/${encodeURIComponent(t)}/reconcile`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"reconcile failed"),r}async function ZY(e){const t=await st("/api/v1/platform/tenants",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list tenants failed"),n}async function eQ(e){const t=await st("/api/v1/platform/perm-modules",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list perm modules failed"),n}async function tQ(e,t){const n=await st(`/api/v1/platform/tenants/${t}/permissions`,{headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"get tenant perms failed"),r}async function nQ(e,t,n){const r=await st(`/api/v1/platform/tenants/${t}/permissions`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({permissions:n})}),o=await it(r);return ut(r,o,"set tenant perms failed"),o}async function rQ(e,t,n=!1,r=""){const o=await st("/api/v1/platform/tenants",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({name:t,slug:r||void 0,with_admin_invite:n})}),s=await it(o);return ut(o,s,"create tenant failed"),s}async function oQ(e,t,n,r=""){const o=await st(`/api/v1/platform/tenants/${t}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({name:n,slug:r||void 0})}),s=await it(o);return ut(o,s,"update tenant failed"),s}async function sQ(e,t){const n=await st(`/api/v1/platform/tenants/${t}/admin-invite`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"admin invite failed"),r}async function iQ(e,t){const n=await st(`/api/v1/platform/tenants/${t}/admin-account`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"admin account failed"),r}async function aQ(e,t){const n=await st(`/api/v1/platform/tenants/${t}/enter`,{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),r=await it(n);return ut(n,r,"enter tenant failed"),ll(r)}async function lQ(e){const t=await st("/api/v1/platform/exit",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"exit tenant failed"),ll(n)}async function o6(e){const t=await st("/api/v1/admin/entitlements",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"get entitlements failed"),n}async function cQ(e){const t=await st("/api/v1/admin/members",{headers:{Authorization:`Bearer ${e.accessToken}`}}),n=await it(t);return ut(t,n,"list members failed"),n}async function uQ(e,t){const n=await st("/api/v1/admin/members",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await it(n);return ut(n,r,"create member failed"),r}async function dQ(e,t,n){const r=await st("/api/v1/auth/password",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({old_password:t,new_password:n})}),o=await it(r);return ut(r,o,"change password failed"),o}async function fQ(e,t){const n=await st("/api/v1/auth/phone",{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify({phone:t})}),r=await it(n);return ut(n,r,"bind phone failed"),r}async function mQ(e,t,n){const r=await st(`/api/v1/admin/members/${t}`,{method:"PUT",headers:{Authorization:`Bearer ${e.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)}),o=await it(r);return ut(r,o,"update member failed"),o}async function pQ(e,t){const n=new FormData;n.append("file",t);const r=await st("/api/v1/storage",{method:"POST",headers:{Authorization:`Bearer ${e.accessToken}`},body:n}),o=await it(r);return ut(r,o,"upload failed"),o}var s6={};Object.defineProperty(s6,"__esModule",{value:!0});var gQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M908 640H804V488c0-4.4-3.6-8-8-8H548v-96h108c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16H368c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h108v96H228c-4.4 0-8 3.6-8 8v152H116c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16H292v-88h440v88H620c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16zm-564 76v168H176V716h168zm84-408V140h168v168H428zm420 576H680V716h168v168z"}}]},name:"apartment",theme:"outlined"},hQ=s6.default=gQ;function g0(){return g0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,g0({},e,{ref:t,icon:hQ})),vQ=a.forwardRef(yQ);var i6={};Object.defineProperty(i6,"__esModule",{value:!0});var bQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M917.7 148.8l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 00-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 000 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM769.1 441.7l-59.4 59.4-186.8-186.8 59.4-59.4c24.9-24.9 58.1-38.7 93.4-38.7 35.3 0 68.4 13.7 93.4 38.7 24.9 24.9 38.7 58.1 38.7 93.4 0 35.3-13.8 68.4-38.7 93.4zm-190.2 105a8.03 8.03 0 00-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 00-11.3 0L363 475.3l-43-43a7.85 7.85 0 00-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2c-68.9 69-77 175.7-24.3 253.5l-76.1 76.1a8.03 8.03 0 000 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2zM441.7 769.1a131.32 131.32 0 01-93.4 38.7c-35.3 0-68.4-13.7-93.4-38.7a131.32 131.32 0 01-38.7-93.4c0-35.3 13.7-68.4 38.7-93.4l59.4-59.4 186.8 186.8-59.4 59.4z"}}]},name:"api",theme:"outlined"},xQ=i6.default=bQ;function h0(){return h0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,h0({},e,{ref:t,icon:xQ})),a6=a.forwardRef($Q);var l6={};Object.defineProperty(l6,"__esModule",{value:!0});var SQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"},CQ=l6.default=SQ;function y0(){return y0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,y0({},e,{ref:t,icon:CQ})),EQ=a.forwardRef(wQ);var c6={};Object.defineProperty(c6,"__esModule",{value:!0});var IQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M296 250c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm184 144H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-48 458H208V148h560v320c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm440-88H728v-36.6c46.3-13.8 80-56.6 80-107.4 0-61.9-50.1-112-112-112s-112 50.1-112 112c0 50.7 33.7 93.6 80 107.4V764H520c-8.8 0-16 7.2-16 16v152c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16V780c0-8.8-7.2-16-16-16zM646 620c0-27.6 22.4-50 50-50s50 22.4 50 50-22.4 50-50 50-50-22.4-50-50zm180 266H566v-60h260v60z"}}]},name:"audit",theme:"outlined"},PQ=c6.default=IQ;function v0(){return v0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,v0({},e,{ref:t,icon:PQ})),RQ=a.forwardRef(NQ);var u6={};Object.defineProperty(u6,"__esModule",{value:!0});var TQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 00-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM512 196.7l271.1 197.2H240.9L512 196.7zM264 462h117v374H264V462zm189 0h117v374H453V462zm307 374H642V462h118v374z"}}]},name:"bank",theme:"outlined"},MQ=u6.default=TQ;function b0(){return b0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,b0({},e,{ref:t,icon:MQ})),fE=a.forwardRef(OQ);var d6={};Object.defineProperty(d6,"__esModule",{value:!0});var _Q={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M811.4 368.9C765.6 248 648.9 162 512.2 162S258.8 247.9 213 368.8C126.9 391.5 63.5 470.2 64 563.6 64.6 668 145.6 752.9 247.6 762c4.7.4 8.7-3.3 8.7-8v-60.4c0-4-3-7.4-7-7.9-27-3.4-52.5-15.2-72.1-34.5-24-23.5-37.2-55.1-37.2-88.6 0-28 9.1-54.4 26.2-76.4 16.7-21.4 40.2-36.9 66.1-43.7l37.9-10 13.9-36.7c8.6-22.8 20.6-44.2 35.7-63.5 14.9-19.2 32.6-36 52.4-50 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.3c19.9 14 37.5 30.8 52.4 50 15.1 19.3 27.1 40.7 35.7 63.5l13.8 36.6 37.8 10c54.2 14.4 92.1 63.7 92.1 120 0 33.6-13.2 65.1-37.2 88.6-19.5 19.2-44.9 31.1-71.9 34.5-4 .5-6.9 3.9-6.9 7.9V754c0 4.7 4.1 8.4 8.8 8 101.7-9.2 182.5-94 183.2-198.2.6-93.4-62.7-172.1-148.6-194.9z"}},{tag:"path",attrs:{d:"M376.9 656.4c1.8-33.5 15.7-64.7 39.5-88.6 25.4-25.5 60-39.8 96-39.8 36.2 0 70.3 14.1 96 39.8 1.4 1.4 2.7 2.8 4.1 4.3l-25 19.6a8 8 0 003 14.1l98.2 24c5 1.2 9.9-2.6 9.9-7.7l.5-101.3c0-6.7-7.6-10.5-12.9-6.3L663 532.7c-36.6-42-90.4-68.6-150.5-68.6-107.4 0-195 85.1-199.4 191.7-.2 4.5 3.4 8.3 8 8.3H369c4.2-.1 7.7-3.4 7.9-7.7zM703 664h-47.9c-4.2 0-7.7 3.3-8 7.6-1.8 33.5-15.7 64.7-39.5 88.6-25.4 25.5-60 39.8-96 39.8-36.2 0-70.3-14.1-96-39.8-1.4-1.4-2.7-2.8-4.1-4.3l25-19.6a8 8 0 00-3-14.1l-98.2-24c-5-1.2-9.9 2.6-9.9 7.7l-.4 101.4c0 6.7 7.6 10.5 12.9 6.3l23.2-18.2c36.6 42 90.4 68.6 150.5 68.6 107.4 0 195-85.1 199.4-191.7.2-4.5-3.4-8.3-8-8.3z"}}]},name:"cloud-sync",theme:"outlined"},zQ=d6.default=_Q;function x0(){return x0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,x0({},e,{ref:t,icon:zQ})),BQ=a.forwardRef(jQ);var f6={};Object.defineProperty(f6,"__esModule",{value:!0});var LQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5L255.8 713.6l-62.3-62.3a8.19 8.19 0 00-11.4 0l-39.8 39.8a8.19 8.19 0 000 11.4l62.3 62.3-44.9 44.9-62.3-62.3a8.19 8.19 0 00-11.4 0l-39.8 39.8a8.19 8.19 0 000 11.4l110.3 111.2c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6A304.1 304.1 0 00608 720c167.9 0 304-136.1 304-304S775.9 112 608 112m161.2 465.2C726.2 620.3 668.9 644 608 644s-118.2-23.7-161.2-66.8C403.7 534.2 380 476.9 380 416s23.7-118.2 66.8-161.2c43-43.1 100.3-66.8 161.2-66.8s118.2 23.7 161.2 66.8c43.1 43 66.8 100.3 66.8 161.2s-23.7 118.2-66.8 161.2"}}]},name:"key",theme:"outlined"},kQ=f6.default=LQ;function $0(){return $0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,$0({},e,{ref:t,icon:kQ})),_f=a.forwardRef(AQ);var m6={};Object.defineProperty(m6,"__esModule",{value:!0});var DQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"defs",attrs:{},children:[{tag:"style",attrs:{}}]},{tag:"path",attrs:{d:"M521.7 82c-152.5-.4-286.7 78.5-363.4 197.7-3.4 5.3.4 12.3 6.7 12.3h70.3c4.8 0 9.3-2.1 12.3-5.8 7-8.5 14.5-16.7 22.4-24.5 32.6-32.5 70.5-58.1 112.7-75.9 43.6-18.4 90-27.8 137.9-27.8 47.9 0 94.3 9.3 137.9 27.8 42.2 17.8 80.1 43.4 112.7 75.9 32.6 32.5 58.1 70.4 76 112.5C865.7 417.8 875 464.1 875 512c0 47.9-9.4 94.2-27.8 137.8-17.8 42.1-43.4 80-76 112.5s-70.5 58.1-112.7 75.9A352.8 352.8 0 01520.6 866c-47.9 0-94.3-9.4-137.9-27.8A353.84 353.84 0 01270 762.3c-7.9-7.9-15.3-16.1-22.4-24.5-3-3.7-7.6-5.8-12.3-5.8H165c-6.3 0-10.2 7-6.7 12.3C234.9 863.2 368.5 942 520.6 942c236.2 0 428-190.1 430.4-425.6C953.4 277.1 761.3 82.6 521.7 82zM395.02 624v-76h-314c-4.4 0-8-3.6-8-8v-56c0-4.4 3.6-8 8-8h314v-76c0-6.7 7.8-10.5 13-6.3l141.9 112a8 8 0 010 12.6l-141.9 112c-5.2 4.1-13 .4-13-6.3z"}}]},name:"login",theme:"outlined"},FQ=m6.default=DQ;function S0(){return S0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,S0({},e,{ref:t,icon:FQ})),VQ=a.forwardRef(HQ);var p6={};Object.defineProperty(p6,"__esModule",{value:!0});var WQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M868 732h-70.3c-4.8 0-9.3 2.1-12.3 5.8-7 8.5-14.5 16.7-22.4 24.5a353.84 353.84 0 01-112.7 75.9A352.8 352.8 0 01512.4 866c-47.9 0-94.3-9.4-137.9-27.8a353.84 353.84 0 01-112.7-75.9 353.28 353.28 0 01-76-112.5C167.3 606.2 158 559.9 158 512s9.4-94.2 27.8-137.8c17.8-42.1 43.4-80 76-112.5s70.5-58.1 112.7-75.9c43.6-18.4 90-27.8 137.9-27.8 47.9 0 94.3 9.3 137.9 27.8 42.2 17.8 80.1 43.4 112.7 75.9 7.9 7.9 15.3 16.1 22.4 24.5 3 3.7 7.6 5.8 12.3 5.8H868c6.3 0 10.2-7 6.7-12.3C798 160.5 663.8 81.6 511.3 82 271.7 82.6 79.6 277.1 82 516.4 84.4 751.9 276.2 942 512.4 942c152.1 0 285.7-78.8 362.3-197.7 3.4-5.3-.4-12.3-6.7-12.3zm88.9-226.3L815 393.7c-5.3-4.2-13-.4-13 6.3v76H488c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h314v76c0 6.7 7.8 10.5 13 6.3l141.9-112a8 8 0 000-12.6z"}}]},name:"logout",theme:"outlined"},KQ=p6.default=WQ;function C0(){return C0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,C0({},e,{ref:t,icon:KQ})),g6=a.forwardRef(UQ);var h6={};Object.defineProperty(h6,"__esModule",{value:!0});var qQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 000 13.8z"}}]},name:"menu-fold",theme:"outlined"},GQ=h6.default=qQ;function w0(){return w0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,w0({},e,{ref:t,icon:GQ})),YQ=a.forwardRef(XQ);var y6={};Object.defineProperty(y6,"__esModule",{value:!0});var QQ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 000-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0014.4 7z"}}]},name:"menu-unfold",theme:"outlined"},JQ=y6.default=QQ;function E0(){return E0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,E0({},e,{ref:t,icon:JQ})),eJ=a.forwardRef(ZQ);var v6={};Object.defineProperty(v6,"__esModule",{value:!0});var tJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64zm-8 824H288V134h448v752zM472 784a40 40 0 1080 0 40 40 0 10-80 0z"}}]},name:"mobile",theme:"outlined"},nJ=v6.default=tJ;function I0(){return I0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,I0({},e,{ref:t,icon:nJ})),mE=a.forwardRef(rJ);var b6={};Object.defineProperty(b6,"__esModule",{value:!0});var oJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm-88-532h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8zm224 0h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8z"}}]},name:"pause-circle",theme:"outlined"},sJ=b6.default=oJ;function P0(){return P0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,P0({},e,{ref:t,icon:sJ})),aJ=a.forwardRef(iJ);var x6={};Object.defineProperty(x6,"__esModule",{value:!0});var lJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M719.4 499.1l-296.1-215A15.9 15.9 0 00398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 000-25.8zm-257.6 134V390.9L628.5 512 461.8 633.1z"}}]},name:"play-circle",theme:"outlined"},cJ=x6.default=lJ;function N0(){return N0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,N0({},e,{ref:t,icon:cJ})),dJ=a.forwardRef(uJ);var $6={};Object.defineProperty($6,"__esModule",{value:!0});var fJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},mJ=$6.default=fJ;function R0(){return R0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,R0({},e,{ref:t,icon:mJ})),gJ=a.forwardRef(pJ);var S6={};Object.defineProperty(S6,"__esModule",{value:!0});var hJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6zm-405.8-201c-3-4.1-7.8-6.6-13-6.6H336c-6.5 0-10.3 7.4-6.5 12.7l126.4 174a16.1 16.1 0 0026 0l212.6-292.7c3.8-5.3 0-12.7-6.5-12.7h-55.2c-5.1 0-10 2.5-13 6.6L468.9 542.4l-64.7-89.1z"}}]},name:"safety-certificate",theme:"outlined"},yJ=S6.default=hJ;function T0(){return T0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,T0({},e,{ref:t,icon:yJ})),bJ=a.forwardRef(vJ);var C6={};Object.defineProperty(C6,"__esModule",{value:!0});var xJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z"}}]},name:"stop",theme:"outlined"},$J=C6.default=xJ;function M0(){return M0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,M0({},e,{ref:t,icon:$J})),w6=a.forwardRef(SJ);var E6={};Object.defineProperty(E6,"__esModule",{value:!0});var CJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M824.2 699.9a301.55 301.55 0 00-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 00-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 008 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 01612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 008-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 01-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 01612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 01-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 008 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"}}]},name:"team",theme:"outlined"},wJ=E6.default=CJ;function O0(){return O0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,O0({},e,{ref:t,icon:wJ})),IJ=a.forwardRef(EJ);var I6={};Object.defineProperty(I6,"__esModule",{value:!0});var PJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z"}}]},name:"thunderbolt",theme:"outlined"},NJ=I6.default=PJ;function _0(){return _0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,_0({},e,{ref:t,icon:NJ})),TJ=a.forwardRef(RJ);var P6={};Object.defineProperty(P6,"__esModule",{value:!0});var MJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M678.3 642.4c24.2-13 51.9-20.4 81.4-20.4h.1c3 0 4.4-3.6 2.2-5.6a371.67 371.67 0 00-103.7-65.8c-.4-.2-.8-.3-1.2-.5C719.2 505 759.6 431.7 759.6 349c0-137-110.8-248-247.5-248S264.7 212 264.7 349c0 82.7 40.4 156 102.6 201.1-.4.2-.8.3-1.2.5-44.7 18.9-84.8 46-119.3 80.6a373.42 373.42 0 00-80.4 119.5A373.6 373.6 0 00137 888.8a8 8 0 008 8.2h59.9c4.3 0 7.9-3.5 8-7.8 2-77.2 32.9-149.5 87.6-204.3C357 628.2 432.2 597 512.2 597c56.7 0 111.1 15.7 158 45.1a8.1 8.1 0 008.1.3zM512.2 521c-45.8 0-88.9-17.9-121.4-50.4A171.2 171.2 0 01340.5 349c0-45.9 17.9-89.1 50.3-121.6S466.3 177 512.2 177s88.9 17.9 121.4 50.4A171.2 171.2 0 01683.9 349c0 45.9-17.9 89.1-50.3 121.6C601.1 503.1 558 521 512.2 521zM880 759h-84v-84c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v84h-84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h84v84c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-84h84c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z"}}]},name:"user-add",theme:"outlined"},OJ=P6.default=MJ;function z0(){return z0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,z0({},e,{ref:t,icon:OJ})),zJ=a.forwardRef(_J);var N6={};Object.defineProperty(N6,"__esModule",{value:!0});var jJ={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"}}]},name:"user",theme:"outlined"},BJ=N6.default=jJ;function j0(){return j0=Object.assign?Object.assign.bind():function(e){for(var t=1;ta.createElement(gt,j0({},e,{ref:t,icon:BJ})),kJ=a.forwardRef(LJ);function AJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i,modal:l}=Lo.useApp(),[c,u]=a.useState([]),[d,m]=a.useState([]),[f,p]=a.useState(!1),[y,b]=a.useState(!1),[x,v]=a.useState(null),[g]=We.useForm(),h=We.useWatch("role_id",g),$=a.useMemo(()=>d.find(R=>R.role_id===h),[d,h]);async function C(){p(!0);try{const[R,P]=await Promise.all([RY(t),r6(t)]);u(R.items||[]),m(P.items||[])}catch(R){const P=R.message||String(R);o(P),i.error(P)}finally{p(!1)}}a.useEffect(()=>{C()},[t.accessToken]);function N(){v(null);const R=d.find(P=>P.code==="生成发布"||P.code==="publisher")||d.find(P=>P.code==="运维"||P.code==="operator")||d[0];g.resetFields(),g.setFieldsValue({name:"",role_id:R==null?void 0:R.role_id,app_slugs:[]}),b(!0)}function S(R){v(R),g.setFieldsValue({name:R.name||"",role_id:R.role_id||void 0,app_slugs:R.app_slugs||[]}),b(!0)}async function E(R=!1){const P=await g.validateFields();r(!0);try{if(x)await uE(t,x.agent_id,{name:P.name.trim(),role_id:P.role_id,app_slugs:P.app_slugs||[],...R?{status:"active"}:{}}),i.success(R?"已保存并启用":"已保存"),s(R?`已保存并启用「${P.name.trim()}」`:`已保存「${P.name.trim()}」`);else{const T=await TY(t,{name:P.name.trim(),role_id:P.role_id,app_slugs:P.app_slugs||[]});l.success({title:"用户已创建",content:I.jsxs("div",{children:[I.jsx("p",{children:"请立即保存 client_secret(只显示一次):"}),I.jsx(ot.Paragraph,{copyable:!0,code:!0,children:T.client_secret})]})}),s(`已创建用户「${P.name.trim()}」`)}b(!1),await C()}catch(T){const M=T.message||String(T);o(M),i.error(M)}finally{r(!1)}}const w=R=>R==="active"?I.jsx(Yt,{color:"success",children:"active"}):R==="pending"?I.jsx(Yt,{color:"warning",children:"pending"}):I.jsx(Yt,{children:R});return I.jsxs(tr,{title:"用户管理",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:C,loading:f,children:"刷新"}),I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:N,disabled:!d.length,children:"新建用户"})]}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["当前租户 #",t.tenantId,"。用户挂在本租户下;同角色跨公司靠租户隔离,不靠再拆一套角色。 角色请在「角色管理」中维护。"]}),I.jsx(Vn,{rowKey:"agent_id",loading:f||n,dataSource:c,pagination:{pageSize:10},columns:[{title:"用户名称",dataIndex:"name",render:(R,P)=>I.jsxs("div",{children:[I.jsx("strong",{children:R||"—"}),P.host_key?I.jsx("div",{children:I.jsxs(ot.Text,{type:"secondary",style:{fontSize:12},children:["host: ",P.host_key]})}):null]})},{title:"client_id",dataIndex:"client_id",render:R=>I.jsx(ot.Text,{code:!0,children:R})},{title:"状态",dataIndex:"status",width:110,render:w},{title:"角色",render:(R,P)=>P.role_name||P.role_code||"—"},{title:"可访问模块",dataIndex:"app_slugs",render:R=>(R||[]).length?I.jsx(Vt,{size:[4,4],wrap:!0,children:(R||[]).map(P=>I.jsx(Yt,{children:P},P))}):"—"},{title:"操作",width:320,render:(R,P)=>I.jsxs(Vt,{wrap:!0,children:[I.jsx(Xe,{size:"small",icon:I.jsx(uu,{}),onClick:()=>S(P),children:"编辑"}),I.jsx(Xe,{size:"small",icon:P.status==="active"?I.jsx(w6,{}):I.jsx(Nx,{}),onClick:async()=>{const T=P.status==="active"?"disabled":"active";if(T==="active"&&!P.role_id&&!(P.permissions||[]).length){i.warning("请先编辑并分配角色后再启用");return}r(!0);try{await uE(t,P.agent_id,{status:T}),i.success(`已设为 ${T}`),s(`已将「${P.name}」设为 ${T}`),await C()}catch(M){i.error(M.message||String(M)),o(M.message||String(M))}finally{r(!1)}},children:P.status==="active"?"停用":"启用"}),I.jsx(Xe,{size:"small",icon:I.jsx(_f,{}),onClick:async()=>{r(!0);try{const T=await MY(t,P.agent_id);l.success({title:"密钥已轮换",content:I.jsx(ot.Paragraph,{copyable:!0,code:!0,children:T.client_secret})}),s(`已轮换「${P.name}」的 secret`)}catch(T){i.error(T.message||String(T)),o(T.message||String(T))}finally{r(!1)}},children:"轮换密钥"}),I.jsx(lu,{title:`删除用户「${P.name}」?`,onConfirm:async()=>{r(!0);try{await OY(t,P.agent_id),i.success("已删除"),s(`已删除「${P.name}」`),await C()}catch(T){i.error(T.message||String(T)),o(T.message||String(T))}finally{r(!1)}},children:I.jsx(Xe,{size:"small",danger:!0,icon:I.jsx(u$,{}),children:"删除"})})]})}]}),I.jsx(Sn,{title:x?`编辑用户 #${x.agent_id}`:"新建用户",open:y,onCancel:()=>b(!1),confirmLoading:n,width:640,destroyOnClose:!0,footer:[I.jsx(Xe,{onClick:()=>b(!1),children:"取消"},"cancel"),x?I.jsx(Xe,{onClick:()=>E(!0),loading:n,children:"保存并启用"},"activate"):null,I.jsx(Xe,{type:"primary",onClick:()=>E(!1),loading:n,children:x?"保存修改":"创建用户"},"ok")],children:I.jsxs(We,{form:g,layout:"vertical",style:{marginTop:12},children:[I.jsx(We.Item,{label:"用户名称",name:"name",rules:[{required:!0,message:"请填写名称"}],children:I.jsx(Lt,{placeholder:"例如:产线宿主 A"})}),I.jsx(We.Item,{label:"角色",name:"role_id",rules:[{required:!0,message:"请选择角色"}],children:I.jsx(dn,{placeholder:"请选择角色",options:d.map(R=>({value:R.role_id,label:`${R.name}(${R.code})`}))})}),$&&I.jsx(tr,{size:"small",title:"角色权限预览",style:{marginBottom:16},children:I.jsx(Vt,{size:[4,4],wrap:!0,children:($.permissions||[]).map(R=>I.jsx(Yt,{children:Of(R)},R))})}),I.jsx(We.Item,{label:"可访问模块(可选)",name:"app_slugs",extra:"留空=可自由发布任意自建模块,无需再逐个授权。仅当需要限制范围时再填写白名单(或 * 表示全部)。",children:I.jsx(dn,{mode:"tags",placeholder:"可选限制:输入 slug 后回车;留空表示不限制",tokenSeparators:[","],allowClear:!0})})]})})]})}function DJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i}=Lo.useApp(),[l,c]=a.useState([]),[u,d]=a.useState([]),[m,f]=a.useState(!1),[p,y]=a.useState(!1),[b]=We.useForm();async function x(){f(!0);try{const[g,h]=await Promise.all([kY(t),g$(t).catch(()=>({items:[]}))]);c(g.items||[]),d(h.items||[])}catch(g){const h=g.message||String(g);o(h),i.error(h)}finally{f(!1)}}a.useEffect(()=>{x()},[t.accessToken]);async function v(){const g=await b.validateFields();r(!0);try{const h=await AY(t,{role:g.role,org_unit_id:g.org_unit_id||0,max_uses:g.max_uses,expires_in_hours:g.expires_in_hours||0});i.success(`邀请码已创建:${h.code}`),s(`邀请码 ${h.code}(角色 ${h.role})`),y(!1),await x()}catch(h){i.error(h.message||String(h)),o(h.message||String(h))}finally{r(!1)}}return I.jsxs(tr,{title:"邀请加入",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:x,loading:m,children:"刷新"}),I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:()=>{b.setFieldsValue({role:"编辑",max_uses:1,expires_in_hours:72,org_unit_id:void 0}),y(!0)},children:"生成邀请码"})]}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["把邀请码发给新用户。对方注册后处于「待加入」,输入邀请码即可成为本司成员。 成员角色:",I.jsx("strong",{children:"管理员"}),"(顶级,含数据同步)、",I.jsx("strong",{children:"编辑"}),"(读写数据)、",I.jsx("strong",{children:"只读"}),"(仅查看导出)。详见 docs/角色说明.md。"]}),I.jsx(Vn,{rowKey:"invite_id",loading:m||n,dataSource:l,pagination:{pageSize:10},columns:[{title:"邀请码",dataIndex:"code",render:g=>I.jsx(ot.Text,{copyable:!0,code:!0,children:g})},{title:"加入角色",dataIndex:"role",width:100,render:g=>I.jsx(Yt,{children:nc(g)})},{title:"组织",width:140,render:(g,h)=>{if(!h.org_unit_id)return"全公司";const $=u.find(C=>C.org_unit_id===h.org_unit_id);return($==null?void 0:$.name)||`#${h.org_unit_id}`}},{title:"使用",width:100,render:(g,h)=>`${h.used_count}/${h.max_uses}`},{title:"状态",dataIndex:"status",width:110,render:g=>g==="active"?I.jsx(Yt,{color:"success",children:"有效"}):g==="revoked"?I.jsx(Yt,{children:"已撤销"}):g==="exhausted"?I.jsx(Yt,{color:"warning",children:"已用尽"}):I.jsx(Yt,{children:g})},{title:"过期",dataIndex:"expires_at",render:g=>g?new Date(g).toLocaleString():"不过期"},{title:"操作",width:120,render:(g,h)=>h.status==="active"?I.jsx(lu,{title:"撤销该邀请码?",onConfirm:async()=>{r(!0);try{await DY(t,h.invite_id),i.success("已撤销"),await x()}catch($){i.error($.message||String($))}finally{r(!1)}},children:I.jsx(Xe,{size:"small",danger:!0,icon:I.jsx(w6,{}),children:"撤销"})}):"—"}]}),I.jsx(Sn,{title:"生成邀请码",open:p,onCancel:()=>y(!1),onOk:v,confirmLoading:n,okText:"生成",destroyOnClose:!0,children:I.jsxs(We,{form:b,layout:"vertical",style:{marginTop:12},children:[I.jsx(We.Item,{label:"加入后角色",name:"role",rules:[{required:!0}],children:I.jsx(dn,{options:[{value:"编辑",label:"编辑 — 可读写业务数据,不可发布/管组织"},{value:"只读",label:"只读 — 仅查看与导出"},{value:"管理员",label:"管理员 — 公司顶级(含数据同步)"}]})}),I.jsx(We.Item,{label:"绑定组织(可选)",name:"org_unit_id",children:I.jsx(dn,{allowClear:!0,placeholder:"空=不绑组织(可见全公司数据)",options:u.map(g=>({value:g.org_unit_id,label:`${"— ".repeat(Math.max(0,g.depth-1))}${g.name}`}))})}),I.jsx(We.Item,{label:"可用次数",name:"max_uses",rules:[{required:!0}],children:I.jsx(wf,{min:1,max:1e3,style:{width:"100%"}})}),I.jsx(We.Item,{label:"有效小时数(0=不过期)",name:"expires_in_hours",children:I.jsx(wf,{min:0,max:8760,style:{width:"100%"}})})]})})]})}function FJ(e){const{session:t,onOpenPublished:n,onContinueDraft:r}=e,[o,s]=a.useState([]),[i,l]=a.useState(""),[c,u]=a.useState(!1);async function d(){u(!0);try{const f=await Rd(t);s(f.items||[]),l(f.scope||"")}catch(f){JT.error(f.message||String(f))}finally{u(!1)}}a.useEffect(()=>{d()},[t.accessToken]);const m=f=>{const p=f.status_label||f.status;return f.status==="published"?I.jsx(Yt,{color:"success",children:p}):f.status==="failed"?I.jsx(Yt,{color:"error",children:p}):I.jsx(Yt,{color:"processing",children:p||"在建"})};return I.jsxs(tr,{title:"模块管理",extra:I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:d,loading:c,children:"刷新"}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["管理账号(如 owner)可查看本租户",I.jsx("strong",{children:"全部模块"}),",含",I.jsx("strong",{children:"在建"}),"与已发布。 智能体默认不限制模块(可自由发布自建 slug);仅当配置了白名单时才受限。",i==="all"?" 当前范围:管理全部。":i==="open"?" 当前范围:智能体开放。":i==="granted"?" 当前范围:白名单。":null]}),I.jsx(Vn,{rowKey:"app_id",loading:c,dataSource:o,pagination:{pageSize:20},columns:[{title:"模块名称",dataIndex:"name"},{title:"slug",dataIndex:"slug",render:f=>I.jsx(ot.Text,{code:!0,children:f})},{title:"状态",width:110,render:(f,p)=>m(p)},{title:"页面数",dataIndex:"page_count",width:80},{title:"实体数",dataIndex:"entity_count",width:80},{title:"更新时间",dataIndex:"updated_at",width:200},{title:"操作",width:200,render:(f,p)=>I.jsx(Vt,{children:p.status==="published"?I.jsx(Xe,{type:"link",onClick:()=>n(p.slug),children:"打开"}):I.jsx(Xe,{type:"link",onClick:()=>r(p.slug),children:"继续编辑"})})}],locale:{emptyText:"暂无模块。生成蓝图后会登记为「在建」,发布成功后变为「已发布」。"}})]})}function HJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i}=Lo.useApp(),[l,c]=a.useState([]),[u,d]=a.useState(5),[m,f]=a.useState(!1),[p,y]=a.useState(!1),[b,x]=a.useState(null),[v]=We.useForm();async function g(){f(!0);try{const S=await g$(t);c(S.items||[]),S.max_depth&&d(S.max_depth)}catch(S){const E=S.message||String(S);o(E),i.error(E)}finally{f(!1)}}a.useEffect(()=>{g()},[t.accessToken]);const h=a.useMemo(()=>(l||[]).filter(S=>S.depth({value:S.org_unit_id,label:`${"— ".repeat(Math.max(0,S.depth-1))}${S.name}(L${S.depth})`})),[l,u]);function $(S){x(null),v.resetFields(),v.setFieldsValue({parent_id:S||void 0,name:"",code:""}),y(!0)}function C(S){x(S),v.setFieldsValue({name:S.name,code:S.code||"",parent_id:S.parent_id||void 0}),y(!0)}async function N(){const S=await v.validateFields();r(!0);try{if(b)await HY(t,b.org_unit_id,{name:S.name.trim(),code:(S.code||"").trim()}),i.success("已保存"),s(`已保存组织「${S.name.trim()}」`);else{const E=await FY(t,{parent_id:S.parent_id||0,name:S.name.trim(),code:(S.code||"").trim()});i.success(`已创建「${E.name}」`),s(`已创建组织「${E.name}」(深度 ${E.depth}/${u})`)}y(!1),await g()}catch(E){i.error(E.message||String(E)),o(E.message||String(E))}finally{r(!1)}}return I.jsxs(tr,{title:"组织管理",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:g,loading:m,children:"刷新"}),I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:()=>$(),children:"新建顶级组织"})]}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["公司(租户)下可建多级组织,最多 ",u," 级。成员通过邀请码绑定组织后,只能访问本组织及下级数据;公司管理员(owner)可看全公司。"]}),I.jsx(Vn,{rowKey:"org_unit_id",loading:m||n,dataSource:l,pagination:{pageSize:20},columns:[{title:"名称",dataIndex:"name",render:(S,E)=>I.jsxs("span",{style:{paddingLeft:(E.depth-1)*16},children:[E.depth>1?"└ ":"",S]})},{title:"编码",dataIndex:"code",width:140,render:S=>S||"—"},{title:"层级",dataIndex:"depth",width:90,render:S=>I.jsxs(Yt,{children:["L",S,"/",u]})},{title:"路径",dataIndex:"path",ellipsis:!0},{title:"操作",width:260,render:(S,E)=>I.jsxs(Vt,{wrap:!0,children:[I.jsx(Xe,{size:"small",icon:I.jsx(zo,{}),disabled:E.depth>=u,onClick:()=>$(E.org_unit_id),children:"下级"}),I.jsx(Xe,{size:"small",icon:I.jsx(uu,{}),onClick:()=>C(E),children:"编辑"}),I.jsx(lu,{title:`删除「${E.name}」?需先删下级`,onConfirm:async()=>{r(!0);try{await VY(t,E.org_unit_id),i.success("已删除"),await g()}catch(w){i.error(w.message||String(w)),o(w.message||String(w))}finally{r(!1)}},children:I.jsx(Xe,{size:"small",danger:!0,icon:I.jsx(u$,{}),children:"删除"})})]})}]}),I.jsx(Sn,{title:b?`编辑组织 #${b.org_unit_id}`:"新建组织",open:p,onCancel:()=>y(!1),onOk:N,confirmLoading:n,destroyOnClose:!0,okText:"保存",children:I.jsxs(We,{form:v,layout:"vertical",style:{marginTop:12},children:[!b&&I.jsx(We.Item,{label:"上级组织",name:"parent_id",children:I.jsx(dn,{allowClear:!0,placeholder:"空=顶级组织",options:h})}),I.jsx(We.Item,{label:"名称",name:"name",rules:[{required:!0,message:"请填写名称"}],children:I.jsx(Lt,{placeholder:"如:华东事业部 / 项目一组"})}),I.jsx(We.Item,{label:"编码(可选,租户内唯一)",name:"code",children:I.jsx(Lt,{placeholder:"如 east / proj_a",disabled:!!b&&!!b.code})})]})})]})}async function zf(e,t,n,r){return Nr(t.role)?await new Promise(s=>{e.confirm({title:"超管写操作确认(1/2)",content:I.jsxs("div",{children:[I.jsxs("p",{children:["你正在以",I.jsx("strong",{children:"平台超级管理员"}),"身份修改某公司内部数据(不是该公司管理员账号)。"]}),I.jsxs("p",{children:["操作:",I.jsx("strong",{children:n})]}),r?I.jsx("p",{style:{color:"#666"},children:r}):null,I.jsx("p",{children:"日常应由该公司管理员处理。是否继续?"})]}),okText:"继续",cancelText:"取消",onOk:()=>s(!0),onCancel:()=>s(!1)})})?new Promise(s=>{e.confirm({title:"超管写操作确认(2/2)",content:I.jsxs("div",{children:[I.jsxs("p",{children:["请再次确认:将执行「",n,"」。"]}),I.jsx("p",{style:{color:"#b42318"},children:"此操作会影响该公司业务配置或成员权限,请确认无误。"})]}),okText:"确认执行",okButtonProps:{danger:!0},cancelText:"取消",onOk:()=>s(!0),onCancel:()=>s(!1)})}):!1:!0}async function VJ(e,t,n){return new Promise(r=>{e.confirm({title:"打开公司管理视图",content:I.jsxs("div",{children:[I.jsxs("p",{children:["以",I.jsx("strong",{children:"平台超级管理员"}),"身份打开「",I.jsx("strong",{children:t}),"」(#",n,")。"]}),I.jsx("p",{children:"这是你的平台管理能力,不是登录成该公司账号。写操作仍会再次确认。"})]}),okText:"打开",cancelText:"取消",onOk:()=>r(!0),onCancel:()=>r(!1)})})}function WJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i,modal:l}=Lo.useApp(),[c,u]=a.useState([]),[d,m]=a.useState(!1),[f,p]=a.useState(!1),[y,b]=a.useState(null),[x,v]=a.useState([]),[g,h]=a.useState([]),[$]=We.useForm();async function C(){m(!0);try{const[w,R]=await Promise.all([r6(t),o6(t)]);u(w.items||[]),h(R.modules||[])}catch(w){const R=w.message||String(w);o(R),i.error(R)}finally{m(!1)}}a.useEffect(()=>{C()},[t.accessToken,t.tenantId]);function N(){b(null),v([]),$.resetFields(),$.setFieldsValue({permissions:[]}),p(!0)}function S(w){b(w),v([...w.permissions||[]]),$.setFieldsValue({code:w.code,name:w.name,description:w.description,permissions:w.permissions||[]}),p(!0)}async function E(){const w=await $.validateFields();if(!x.length){i.warning("请至少勾选一项权限");return}const R=y?`保存智能体角色「${w.name.trim()}」及其权限`:`创建智能体角色「${w.name.trim()}」`;if(await zf(l,t,R)){r(!0);try{y?(await zY(t,y.role_id,{name:w.name.trim(),description:(w.description||"").trim(),permissions:x}),i.success(`已保存角色「${w.name.trim()}」`),s(`已保存角色「${w.name.trim()}」`)):(await _Y(t,{code:(w.code||"").trim(),name:w.name.trim(),description:(w.description||"").trim(),permissions:x}),i.success(`已创建角色「${w.name.trim()}」`),s(`已创建角色「${w.name.trim()}」`)),p(!1),await C()}catch(T){const M=T.message||String(T);o(M),i.error(M)}finally{r(!1)}}}return I.jsxs(tr,{title:"角色管理",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:C,loading:d,children:"刷新"}),I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:N,children:"新建角色"})]}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["此处配置",I.jsx("strong",{children:"智能体角色"}),"。可选权限以平台超级管理员授予本公司的额度为准,无法勾选未授权项。 默认角色:生成发布 / 只读 / 读写 / 运维。"]}),I.jsx(Vn,{rowKey:"role_id",loading:d||n,dataSource:c,pagination:{pageSize:10},columns:[{title:"名称",dataIndex:"name",width:140},{title:"编码",dataIndex:"code",width:140,render:w=>I.jsx(Yt,{children:w})},{title:"说明",dataIndex:"description",ellipsis:!0},{title:"权限",dataIndex:"permissions",render:w=>(w||[]).length?I.jsxs(Vt,{size:[4,4],wrap:!0,children:[(w||[]).slice(0,8).map(R=>I.jsx(Yt,{children:Of(R)},R)),(w||[]).length>8?I.jsxs(Yt,{children:["+",(w||[]).length-8]}):null]}):"—"},{title:"操作",width:180,render:(w,R)=>I.jsxs(Vt,{children:[I.jsx(Xe,{size:"small",icon:I.jsx(uu,{}),onClick:()=>S(R),children:"编辑"}),I.jsx(lu,{title:`删除角色「${R.name}」?`,onConfirm:async()=>{if(await zf(l,t,`删除智能体角色「${R.name}」`)){r(!0);try{await jY(t,R.role_id),i.success("已删除"),s(`已删除角色「${R.name}」`),await C()}catch(T){i.error(T.message||String(T)),o(T.message||String(T))}finally{r(!1)}}},children:I.jsx(Xe,{size:"small",danger:!0,icon:I.jsx(u$,{}),children:"删除"})})]})}]}),I.jsx(Sn,{title:y?`编辑角色 #${y.role_id}`:"新建角色",open:f,onCancel:()=>p(!1),onOk:E,confirmLoading:n,width:720,destroyOnClose:!0,okText:"保存",children:I.jsxs(We,{form:$,layout:"vertical",style:{marginTop:12},children:[I.jsx(We.Item,{label:"角色编码",name:"code",rules:y?[]:[{required:!0,message:"请填写中文编码,如 只读"}],children:I.jsx(Lt,{placeholder:"如 只读 / 读写 / 自定义运维",disabled:!!y})}),I.jsx(We.Item,{label:"角色名称",name:"name",rules:[{required:!0,message:"请填写名称"}],children:I.jsx(Lt,{placeholder:"与编码可相同,如:只读"})}),I.jsx(We.Item,{label:"说明",name:"description",children:I.jsx(Lt.TextArea,{rows:2,placeholder:"可选"})}),I.jsx(We.Item,{label:"权限分配",required:!0,extra:"仅显示本公司权限额度内的选项",children:I.jsx(uY,{value:x,onChange:v,allowedModules:g})})]})})]})}function KJ(e){const t={};for(const n of(e||"").split(",")){const[r,o]=n.split(":").map(s=>s.trim());r&&o&&(t[r]=o)}return t}function pE(e,t){return{id:t,name:e.name,direction:e.direction,conflict_policy:e.conflict_policy,poll_interval_ms:e.poll_interval_ms||500,local:{driver:e.local.driver,dsn:e.local.dsn,tables:(e.local.tables||"").split(",").map(n=>n.trim()).filter(Boolean)},remote:{driver:e.remote.driver,dsn:e.remote.dsn,tables:(e.remote.tables||"").split(",").map(n=>n.trim()).filter(Boolean)},pk_columns:KJ(e.pk_columns)}}function UJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i}=Lo.useApp(),[l,c]=a.useState([]),[u,d]=a.useState([]),[m,f]=a.useState(!1),[p,y]=a.useState(!1),[b,x]=a.useState(null),[v]=We.useForm();async function g(){f(!0);try{const[S,E]=await Promise.all([WY(t),QY(t,!0)]);c(S.items||[]),d(E.items||[])}catch(S){const E=S.message||String(S);o(E),i.error(E)}finally{f(!1)}}a.useEffect(()=>{g()},[t.accessToken]);function h(){x(null),v.setFieldsValue({name:"本地 B ↔ 线上 A",direction:"bidirectional",conflict_policy:"queue",poll_interval_ms:500,local:{driver:"sqlite",dsn:"file:./data/local.db",tables:"article"},remote:{driver:"mysql",dsn:"user:pass@tcp(127.0.0.1:3306)/app?parseTime=true&charset=utf8mb4",tables:"article"},pk_columns:"article:id"}),y(!0)}function $(S){x(S),v.setFieldsValue({name:S.name,direction:S.direction,conflict_policy:S.conflict_policy,poll_interval_ms:S.poll_interval_ms,local:{driver:S.local.driver,dsn:S.local.dsn,tables:(S.local.tables||[]).join(",")},remote:{driver:S.remote.driver,dsn:S.remote.dsn,tables:(S.remote.tables||[]).join(",")},pk_columns:Object.entries(S.pk_columns||{}).map(([E,w])=>`${E}:${w}`).join(",")}),y(!0)}async function C(){const S=await v.validateFields();r(!0);try{const E=pE(S,b==null?void 0:b.id);b?await UY(t,b.id,E):await KY(t,E),y(!1),s("同步通道已保存"),await g()}catch(E){i.error(E.message||String(E))}finally{r(!1)}}async function N(){var E,w;const S=await v.validateFields();r(!0);try{const R=pE(S),P=await GY(t,{local:R.local,remote:R.remote,side:"both"}),T=(E=P.local)==null?void 0:E.ok,M=(w=P.remote)==null?void 0:w.ok;T&&M?i.success("本地与线上数据库均可连接"):i.warning(JSON.stringify(P))}catch(R){i.error(R.message||String(R))}finally{r(!1)}}return I.jsxs("div",{className:"panel",children:[I.jsx(ot.Title,{level:4,style:{marginTop:0},children:"数据同步"}),I.jsxs(ot.Paragraph,{type:"secondary",children:["仅本",I.jsx("strong",{children:"公司顶级权限(管理员)"}),"可配置;通道按公司隔离,看不到其他公司的服务器。 典型拓扑:A 线上 ↔ B 本地(双向),额外源 C 写入 B 再推到 A。 防回声 + 版本幂等避免「多」;触发器与对账避免「漏」。"]}),I.jsxs(Vt,{style:{marginBottom:12},children:[I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:h,children:"新建通道"}),I.jsx(Xe,{icon:I.jsx(Gs,{}),loading:m,onClick:()=>void g(),children:"刷新"})]}),I.jsx(Vn,{rowKey:"id",loading:m,dataSource:l,pagination:!1,columns:[{title:"名称",dataIndex:"name"},{title:"方向",dataIndex:"direction",render:S=>I.jsx(Yt,{children:S})},{title:"本地",render:(S,E)=>`${E.local.driver}`},{title:"线上",render:(S,E)=>I.jsxs("span",{children:[E.remote.driver,I.jsx("br",{}),I.jsx(ot.Text,{type:"secondary",style:{fontSize:12},children:(E.remote.dsn||"").replace(/:[^:@/]+@/,":***@")})]})},{title:"状态",render:(S,E)=>E.enabled?I.jsx(Yt,{color:"green",children:"运行中"}):I.jsx(Yt,{children:"已停止"})},{title:"统计",render:(S,E)=>{var w,R,P;return`↑${((w=E.stats)==null?void 0:w.pushed_ok)||0} ↓${((R=E.stats)==null?void 0:R.pulled_ok)||0} 冲突${((P=E.stats)==null?void 0:P.conflicts)||0}`}},{title:"操作",render:(S,E)=>I.jsxs(Vt,{wrap:!0,children:[I.jsx(Xe,{size:"small",onClick:()=>$(E),children:"配置"}),E.enabled?I.jsx(Xe,{size:"small",icon:I.jsx(aJ,{}),onClick:async()=>{await YY(t,E.id),await g()},children:"停止"}):I.jsx(Xe,{size:"small",type:"primary",icon:I.jsx(dJ,{}),onClick:async()=>{try{await XY(t,E.id),s("同步已启动"),await g()}catch(w){i.error(w.message||String(w))}},children:"启动"}),I.jsx(Xe,{size:"small",onClick:async()=>{r(!0);try{const R=((await JY(t,E.id)).reports||[]).map(P=>`${P.table}: 补推${P.patched_push} 补拉${P.patched_pull}(仅本地${(P.only_local||[]).length} 仅线上${(P.only_remote||[]).length})`);i.success(R.length?R.join(";"):"对账完成,无差异"),s("对账完成"),await g()}catch(w){i.error(w.message||String(w))}finally{r(!1)}},children:"对账"}),I.jsx(Xe,{size:"small",danger:!0,onClick:async()=>{await qY(t,E.id),await g()},children:"删除"})]})}]}),I.jsx(ot.Title,{level:5,style:{marginTop:24},children:"冲突队列(未解决)"}),I.jsx(Vn,{rowKey:"id",dataSource:u,pagination:!1,columns:[{title:"通道",dataIndex:"channel_id",ellipsis:!0},{title:"表",dataIndex:"table"},{title:"主键",dataIndex:"row_pk"},{title:"来源",dataIndex:"source"},{title:"说明",dataIndex:"message",ellipsis:!0},{title:"操作",render:(S,E)=>I.jsxs(Vt,{children:[I.jsx(Xe,{size:"small",onClick:async()=>{await dE(t,E.id,"keep_target"),await g()},children:"保留目标"}),I.jsx(Xe,{size:"small",onClick:async()=>{await dE(t,E.id,"discard"),await g()},children:"丢弃"})]})}]}),I.jsx(Sn,{title:b?"编辑同步通道":"新建同步通道",open:p,onCancel:()=>y(!1),width:720,footer:I.jsxs(Vt,{children:[I.jsx(Xe,{icon:I.jsx(a6,{}),loading:n,onClick:()=>void N(),children:"测试连接"}),I.jsx(Xe,{onClick:()=>y(!1),children:"取消"}),I.jsx(Xe,{type:"primary",loading:n,onClick:()=>void C(),children:"保存"})]}),children:I.jsxs(We,{form:v,layout:"vertical",children:[I.jsx(We.Item,{name:"name",label:"名称",rules:[{required:!0}],children:I.jsx(Lt,{})}),I.jsx(We.Item,{name:"direction",label:"方向",rules:[{required:!0}],children:I.jsx(dn,{options:[{value:"local_to_remote",label:"本地 → 线上"},{value:"remote_to_local",label:"线上 → 本地"},{value:"bidirectional",label:"双向"}]})}),I.jsx(We.Item,{name:"conflict_policy",label:"冲突策略",children:I.jsx(dn,{options:[{value:"queue",label:"入冲突队列(推荐)"},{value:"lww_source",label:"源端覆盖"},{value:"lww_target",label:"保留目标"}]})}),I.jsx(We.Item,{name:"poll_interval_ms",label:"轮询间隔(ms)",children:I.jsx(wf,{min:100,max:6e4,style:{width:"100%"}})}),I.jsx(ot.Text,{strong:!0,children:"本地库"}),I.jsx(We.Item,{name:["local","driver"],label:"驱动",rules:[{required:!0}],children:I.jsx(dn,{options:[{value:"sqlite"},{value:"mysql"},{value:"postgres"}]})}),I.jsx(We.Item,{name:["local","dsn"],label:"DSN",rules:[{required:!0}],extra:"SQLite 例:file:./data/local.db",children:I.jsx(Lt.TextArea,{rows:2})}),I.jsx(We.Item,{name:["local","tables"],label:"表(逗号分隔)",rules:[{required:!0}],children:I.jsx(Lt,{placeholder:"article,order"})}),I.jsx(ot.Text,{strong:!0,children:"线上库(可随时改地址)"}),I.jsx(We.Item,{name:["remote","driver"],label:"驱动",rules:[{required:!0}],children:I.jsx(dn,{options:[{value:"mysql"},{value:"postgres"},{value:"sqlite"}]})}),I.jsx(We.Item,{name:["remote","dsn"],label:"线上 DSN",rules:[{required:!0}],extra:"MySQL 例:user:pass@tcp(host:3306)/dbname?parseTime=true",children:I.jsx(Lt.TextArea,{rows:2})}),I.jsx(We.Item,{name:["remote","tables"],label:"表(逗号分隔)",rules:[{required:!0}],children:I.jsx(Lt,{})}),I.jsx(We.Item,{name:"pk_columns",label:"主键映射",extra:"格式 table:pk,多个用逗号。默认每表 id",children:I.jsx(Lt,{placeholder:"article:id"})})]})})]})}function qJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s,onSession:i,onEnteredCompany:l}=e,{message:c,modal:u}=Lo.useApp(),d=(t.tenantId||0)>0,[m,f]=a.useState([]),[p,y]=a.useState(!1),[b,x]=a.useState(!1),[v,g]=a.useState(!1),[h,$]=a.useState(!1),[C,N]=a.useState(null),[S,E]=a.useState([]),[w,R]=a.useState(0),[P,T]=a.useState([]),[M,z]=a.useState(""),[B,F]=a.useState(null),[L]=We.useForm(),[j]=We.useForm();async function O(){y(!0);try{const k=await ZY(t);f(k.items||[])}catch(k){const _=k.message||String(k);o(_),c.error(_)}finally{y(!1)}}a.useEffect(()=>{O(),eQ(t).then(k=>{E(k.modules||[]),R((k.catalog||[]).length)}).catch(()=>{})},[t.accessToken]);async function A(k){N(k),r(!0);try{const _=await tQ(t,k.tenant_id);T(_.permissions||[]),_.total&&R(_.total),$(!0)}catch(_){c.error(_.message||String(_))}finally{r(!1)}}return I.jsxs("div",{className:"panel",children:[I.jsx(ot.Title,{level:4,style:{marginTop:0},children:"平台工作台 · 公司总览"}),I.jsxs(ot.Paragraph,{type:"secondary",children:["这是",I.jsx("strong",{children:"平台超级管理员自己的功能"}),":新建/改名公司、路径 slug、权限额度;新建时同步生成该公司管理员账号(随机唯一)。 路径约定:基础域名下 ",I.jsxs(ot.Text,{code:!0,children:["/","{slug}","/..."]}),"(例如 www.yuxinda.com/aaa/)。点「管理该公司」打开内部视图;身份始终是超管。"]}),d&&I.jsxs(ot.Paragraph,{children:[I.jsxs(Yt,{color:"orange",children:["正在管理:",t.tenantName||`公司 #${t.tenantId}`]}),I.jsx(Xe,{size:"small",icon:I.jsx(g6,{}),style:{marginLeft:8},loading:n,onClick:async()=>{r(!0);try{const k=await lQ(t);i==null||i(k),s("已回到平台工作台"),c.success("已回到平台工作台")}catch(k){c.error(k.message||String(k))}finally{r(!1)}},children:"返回平台工作台"})]}),B&&I.jsxs(ot.Paragraph,{className:"flash ok",children:["「",B.company,"」管理员账号(请立即保存,密码仅展示一次): 用户名 ",I.jsx(ot.Text,{copyable:!0,code:!0,children:B.username})," · ","密码 ",I.jsx(ot.Text,{copyable:!0,code:!0,children:B.password})]}),M&&I.jsxs(ot.Paragraph,{className:"flash ok",children:["管理员邀请码:",I.jsx(ot.Text,{copyable:!0,code:!0,children:M}),"(可选,发给客户自行注册)"]}),I.jsxs(Vt,{style:{marginBottom:12},children:[I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:()=>x(!0),children:"新建公司"}),I.jsx(Xe,{icon:I.jsx(Gs,{}),loading:p,onClick:()=>void O(),children:"刷新"})]}),I.jsx(Vn,{rowKey:"tenant_id",loading:p,dataSource:m,pagination:!1,columns:[{title:"ID",dataIndex:"tenant_id",width:70},{title:"公司名称",dataIndex:"name"},{title:"路径 slug",dataIndex:"slug",width:140,render:k=>k?I.jsxs(ot.Text,{code:!0,children:["/",k,"/"]}):I.jsx(Yt,{children:"未设置"})},{title:"成员",dataIndex:"user_count",width:70},{title:"模块",dataIndex:"app_count",width:70},{title:"创建时间",dataIndex:"created_at",render:k=>k?new Date(k).toLocaleString():"—"},{title:"操作",width:420,render:(k,_)=>I.jsxs(Vt,{wrap:!0,size:4,children:[I.jsx(Xe,{type:"primary",size:"small",icon:I.jsx(VQ,{}),loading:n,onClick:async()=>{if(await VJ(u,_.name,_.tenant_id)){r(!0);try{const V=await aQ(t,_.tenant_id);V.tenantName=_.name,i==null||i(V),s(`已打开「${_.name}」管理视图(身份仍是平台超管)`),c.success(`已打开「${_.name}」`),l==null||l()}catch(V){c.error(V.message||String(V))}finally{r(!1)}}},children:"管理该公司"}),I.jsx(Xe,{size:"small",icon:I.jsx(_f,{}),loading:n,onClick:async()=>{r(!0);try{const V=(await iQ(t,_.tenant_id)).admin_account;F({username:V.username,password:V.password,company:_.name}),u.info({title:`「${_.name}」管理员账号`,content:I.jsxs("div",{children:[I.jsxs("p",{children:["用户名:",I.jsx(ot.Text,{copyable:!0,code:!0,children:V.username})]}),I.jsxs("p",{children:["密码:",I.jsx(ot.Text,{copyable:!0,code:!0,children:V.password})]}),I.jsx(ot.Text,{type:"danger",children:"密码仅此一次展示;请立即保存并交给该公司管理员登录后自行改密。"})]}),okText:"已保存"}),s(`已为「${_.name}」生成管理员 ${V.username}`),await O()}catch(D){c.error(D.message||String(D))}finally{r(!1)}},children:_.user_count>0?"再发管理员账号":"生成管理员账号"}),I.jsx(Xe,{size:"small",icon:I.jsx(bJ,{}),onClick:()=>void A(_),children:"权限额度"}),I.jsx(Xe,{size:"small",icon:I.jsx(uu,{}),onClick:()=>{N(_),j.setFieldsValue({name:_.name,slug:_.slug||""}),g(!0)},children:"改名/slug"}),I.jsx(Xe,{size:"small",type:"link",loading:n,onClick:async()=>{r(!0);try{const D=await sQ(t,_.tenant_id);z(D.code),s(`已为「${_.name}」生成管理员邀请码`),c.success("邀请码已生成")}catch(D){c.error(D.message||String(D))}finally{r(!1)}},children:"邀请码"})]})}]}),I.jsx(Sn,{title:"新建公司",open:b,onCancel:()=>x(!1),onOk:async()=>{var _;const k=await L.validateFields();r(!0);try{const D=await rQ(t,k.name.trim(),!!k.with_invite,(k.slug||"").trim()),V=D.admin_account;if(!(V!=null&&V.username)||!(V!=null&&V.password)){c.error("公司已创建,但未返回管理员账号,请点「生成管理员账号」补发"),x(!1),L.resetFields(),await O();return}F({username:V.username,password:V.password,company:D.tenant.name}),u.info({title:"公司与管理员账号已就绪",content:I.jsxs("div",{children:[I.jsxs("p",{children:["公司 ",I.jsx(ot.Text,{strong:!0,children:D.tenant.name}),"(路径 ",I.jsxs(ot.Text,{code:!0,children:["/",D.tenant.slug,"/"]}),")"]}),I.jsxs("p",{children:["管理员用户名:",I.jsx(ot.Text,{copyable:!0,code:!0,children:V.username})]}),I.jsxs("p",{children:["管理员密码:",I.jsx(ot.Text,{copyable:!0,code:!0,children:V.password})]}),I.jsx(ot.Text,{type:"danger",children:"创建公司即附带此账号;密码仅展示一次,请立即保存。"})]}),okText:"已保存"});const W=(_=D.admin_invite)==null?void 0:_.code;W&&z(W),c.success(`已创建「${D.tenant.name}」并生成管理员账号`),s(`已创建「${D.tenant.name}」管理员 ${V.username}`),x(!1),L.resetFields(),await O()}catch(D){c.error(D.message||String(D))}finally{r(!1)}},confirmLoading:n,children:I.jsxs(We,{form:L,layout:"vertical",initialValues:{with_invite:!1},children:[I.jsx(We.Item,{name:"name",label:"公司名称",rules:[{required:!0,message:"请填写名称"}],children:I.jsx(Lt,{placeholder:"例如:某某科技"})}),I.jsx(We.Item,{name:"slug",label:"路径 slug",extra:"访问前缀,如 aaa → www.yuxinda.com/aaa/。小写字母开头,仅 a-z/0-9/-",rules:[{required:!0,message:"请填写 slug"},{pattern:/^[a-z][a-z0-9-]{1,31}$/,message:"格式:2–32 位,小写字母开头"}],children:I.jsx(Lt,{placeholder:"例如:aaa"})}),I.jsxs(ot.Paragraph,{type:"secondary",style:{marginBottom:8},children:[I.jsx("strong",{children:"创建后立即生成"}),"该公司管理员账号(用户名随机唯一 + 初始密码),弹窗展示一次。"]}),I.jsx(We.Item,{name:"with_invite",valuePropName:"checked",children:I.jsx(as,{children:"额外生成邀请码(可选,一般不需要)"})})]})}),I.jsx(Sn,{title:"修改公司名称 / 路径",open:v,onCancel:()=>g(!1),onOk:async()=>{if(!C)return;const k=await j.validateFields();r(!0);try{await oQ(t,C.tenant_id,k.name.trim(),(k.slug||"").trim()),c.success("已保存"),g(!1),await O()}catch(_){c.error(_.message||String(_))}finally{r(!1)}},confirmLoading:n,children:I.jsxs(We,{form:j,layout:"vertical",children:[I.jsx(We.Item,{name:"name",label:"公司名称",rules:[{required:!0}],children:I.jsx(Lt,{})}),I.jsx(We.Item,{name:"slug",label:"路径 slug",extra:"修改后,对外路径变为 /{slug}/(路由层后续阶段启用)",rules:[{required:!0,message:"请填写 slug"},{pattern:/^[a-z][a-z0-9-]{1,31}$/,message:"格式:2–32 位,小写字母开头"}],children:I.jsx(Lt,{})})]})}),I.jsxs(Sn,{title:C?`公司权限额度 · ${C.name}`:"公司权限额度",open:h,width:720,onCancel:()=>$(!1),onOk:()=>{if(!C)return;const k=async()=>{r(!0);try{const _=await nQ(t,C.tenant_id,P);T(_.permissions||[]),c.success(`已保存(${(_.permissions||[]).length}/${_.total||w})`),$(!1)}catch(_){c.error(_.message||String(_))}finally{r(!1)}};if(P.length===0){u.confirm({title:"确认清空该公司全部权限?",content:"保存后该公司将无法使用任何业务接口,直到重新授予。",onOk:()=>void k()});return}k()},confirmLoading:n,okText:"保存额度",children:[I.jsxs(Vt,{style:{marginBottom:12},children:[I.jsxs(Yt,{children:["已选 ",P.length,"/",w||"?"]}),I.jsx(Xe,{size:"small",onClick:()=>T(S.flatMap(k=>(k.items||[]).map(_=>_.perm))),children:"全选"}),I.jsx(Xe,{size:"small",onClick:()=>T([]),children:"全不选"})]}),I.jsx(Vt,{direction:"vertical",size:"middle",style:{width:"100%"},children:S.map(k=>{const _=(k.items||[]).map(D=>D.perm);return I.jsxs("div",{children:[I.jsx(ot.Text,{strong:!0,children:k.title}),I.jsx("div",{style:{marginTop:8},children:I.jsx(as.Group,{style:{width:"100%",display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:8},options:(k.items||[]).map(D=>({label:I.jsxs("span",{children:[D.perm,I.jsx("br",{}),I.jsx(ot.Text,{type:"secondary",style:{fontSize:12},children:D.desc})]}),value:D.perm})),value:P.filter(D=>_.includes(D)),onChange:D=>{const V=new Set(_),W=P.filter(K=>!V.has(K));T([...W,...D])}})})]},k.title)})})]})]})}function GJ(e){const{session:t,busy:n,setBusy:r,setError:o,setInfo:s}=e,{message:i,modal:l}=Lo.useApp(),[c,u]=a.useState([]),[d,m]=a.useState([]),[f,p]=a.useState(!1),[y,b]=a.useState(!1),[x]=We.useForm();async function v(){p(!0);try{const[h,$]=await Promise.all([cQ(t),g$(t).catch(()=>({items:[]}))]);u(h.items||[]),m($.items||[])}catch(h){const $=h.message||String(h);o($),i.error($)}finally{p(!1)}}a.useEffect(()=>{v()},[t.accessToken,t.tenantId]);async function g(h,$,C){if(await zf(l,t,$,`成员:${h.display_name||h.username}(#${h.user_id})`)){r(!0);try{await mQ(t,h.user_id,C),s(`${$}成功`),await v()}catch(S){i.error(S.message||String(S))}finally{r(!1)}}}return I.jsxs(tr,{title:"成员管理",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{type:"primary",icon:I.jsx(zo,{}),onClick:()=>{x.setFieldsValue({role:"编辑",password:void 0,display_name:void 0,org_unit_id:void 0}),b(!0)},children:"新建成员"}),I.jsx(Xe,{icon:I.jsx(Gs,{}),onClick:()=>void v(),loading:f,children:"刷新"})]}),children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:0},children:["用户名由系统随机生成且全局唯一;初始密码可留空自动生成,登录后可自行「修改密码」。也可走「邀请加入」。",t.role==="超级管理员"||t.role==="platform_admin"?" 超管代管修改时会要求两次确认。":""]}),I.jsx(Vn,{rowKey:"user_id",loading:f||n,dataSource:c,pagination:{pageSize:10},columns:[{title:"用户名",dataIndex:"username",width:140},{title:"手机号",dataIndex:"phone",width:120,render:h=>h||I.jsx(ot.Text,{type:"secondary",children:"未绑定"})},{title:"显示名",dataIndex:"display_name",width:120},{title:"角色",dataIndex:"role",width:160,render:(h,$)=>I.jsx(dn,{size:"small",style:{width:120},value:h,disabled:$.user_id===t.userId,options:[{value:"管理员",label:"管理员"},{value:"编辑",label:"编辑"},{value:"只读",label:"只读"}],onChange:C=>void g($,`将 ${$.username} 设为${nc(C)}`,{role:C})})},{title:"组织",width:160,render:(h,$)=>I.jsx(dn,{size:"small",allowClear:!0,style:{width:140},placeholder:"全公司",value:$.org_unit_id||void 0,options:d.map(C=>({value:C.org_unit_id,label:C.name})),onChange:C=>void g($,`调整 ${$.username} 的组织`,{org_unit_id:C||0})})},{title:"状态",dataIndex:"status",width:120,render:(h,$)=>I.jsx(dn,{size:"small",style:{width:100},value:h||"active",disabled:$.user_id===t.userId,options:[{value:"active",label:"正常"},{value:"disabled",label:"停用"}],onChange:C=>void g($,`${C==="disabled"?"停用":"启用"}成员 ${$.username}`,{status:C})})},{title:"角色标签",width:90,render:(h,$)=>I.jsx(Yt,{children:nc($.role)})}]}),I.jsx(Sn,{title:"新建成员",open:y,onCancel:()=>b(!1),onOk:async()=>{const h=await x.validateFields();if(await zf(l,t,"新建成员","将随机生成唯一用户名;初始密码可稍后由本人修改")){r(!0);try{const C=await uQ(t,{password:(h.password||"").trim(),display_name:(h.display_name||"").trim(),role:h.role,org_unit_id:h.org_unit_id||0});l.info({title:"成员已创建 — 请保存账号",content:I.jsxs("div",{children:[I.jsxs("p",{children:["用户名:",I.jsx(ot.Text,{copyable:!0,code:!0,children:C.username})]}),I.jsxs("p",{children:["初始密码:",I.jsx(ot.Text,{copyable:!0,code:!0,children:C.password})]}),I.jsxs("p",{children:["角色:",nc(C.role)]}),I.jsx(ot.Text,{type:"secondary",children:"用户名随机唯一不可改;密码请交给本人后由其登录并自行修改。"})]}),okText:"已保存"}),s(`已创建成员 ${C.username}`),b(!1),x.resetFields(),await v()}catch(C){i.error(C.message||String(C))}finally{r(!1)}}},confirmLoading:n,destroyOnHidden:!0,children:I.jsxs(We,{form:x,layout:"vertical",initialValues:{role:"编辑"},children:[I.jsx(ot.Paragraph,{type:"secondary",children:"用户名由系统随机生成(全局唯一)。初始密码可指定,留空则随机生成;登录后可自行修改。"}),I.jsx(We.Item,{name:"password",label:"初始密码",extra:"留空则随机生成",children:I.jsx(Lt.Password,{placeholder:"至少 6 位,可留空"})}),I.jsx(We.Item,{name:"display_name",label:"显示名",children:I.jsx(Lt,{placeholder:"可选"})}),I.jsx(We.Item,{name:"role",label:"角色",rules:[{required:!0}],children:I.jsx(dn,{options:[{value:"管理员",label:"管理员(公司顶级)"},{value:"编辑",label:"编辑"},{value:"只读",label:"只读"}]})}),I.jsx(We.Item,{name:"org_unit_id",label:"组织",children:I.jsx(dn,{allowClear:!0,placeholder:"全公司(不限组织)",options:d.map(h=>({value:h.org_unit_id,label:h.name}))})})]})})]})}const Es=["#1d4f91","#7c3aed","#c2410c","#0f766e","#b42318","#854d0e"];function XJ({title:e,value:t}){return I.jsxs("div",{className:"kpi",children:[I.jsx("div",{className:"kpi-title",children:e}),I.jsx("div",{className:"kpi-value",children:t})]})}function gE({title:e,buckets:t}){const n=Math.max(1,...t.map(r=>r.count));return I.jsxs("div",{className:"chart-card",children:[I.jsx("h4",{children:e}),I.jsx("div",{className:"bars",children:t.map((r,o)=>I.jsxs("div",{className:"bar-row",children:[I.jsx("span",{className:"bar-label",children:r.key}),I.jsx("div",{className:"bar-track",children:I.jsx("div",{className:"bar-fill",style:{width:`${r.count/n*100}%`,background:Es[o%Es.length]}})}),I.jsx("span",{className:"bar-num",children:r.count})]},r.key))})]})}function YJ({title:e,buckets:t}){const n=t.reduce((s,i)=>s+i.count,0)||1;let r=0;const o=t.map((s,i)=>{const l=r/n*100;r+=s.count;const c=r/n*100;return`${Es[i%Es.length]} ${l}% ${c}%`});return I.jsxs("div",{className:"chart-card",children:[I.jsx("h4",{children:e}),I.jsxs("div",{className:"pie-wrap",children:[I.jsx("div",{className:"pie",style:{background:`conic-gradient(${o.join(",")})`}}),I.jsx("ul",{className:"pie-legend",children:t.map((s,i)=>I.jsxs("li",{children:[I.jsx("i",{style:{background:Es[i%Es.length]}}),s.key," · ",s.count]},s.key))})]})]})}function R6({title:e,points:t,series:n,yUnit:r=""}){if(!t.length||!n.length)return I.jsxs("div",{className:"chart-card line-chart",children:[e?I.jsx("h4",{children:e}):null,I.jsx("div",{className:"empty",children:"暂无曲线数据"})]});const o=t.flatMap(w=>n.map(R=>w.values[R.key]).filter(R=>Number.isFinite(R))),s=[...o].sort((w,R)=>w-R),i=w=>{if(!s.length)return 0;const R=Math.min(s.length-1,Math.max(0,Math.floor((s.length-1)*w)));return s[R]};let l=s.length?Math.min(0,i(.05)):0,c=s.length?Math.max(1,i(.95)):1;c-l<1&&(l=Math.min(0,...o,-1),c=Math.max(1,...o,1));const u=(c-l)*.12||1,d=l-u,m=c+u,f=920,p=280,y=48,b=16,x=20,v=36,g=f-y-b,h=p-x-v,$=w=>y+(t.length<=1?g/2:w/(t.length-1)*g),C=w=>x+(m-w)/(m-d)*h,N=n.map((w,R)=>{const P=t.map((T,M)=>{var F;const z=T.values[w.key];return Number.isFinite(z)?`${M===0||!Number.isFinite((F=t[M-1])==null?void 0:F.values[w.key])?"M":"L"}${$(M).toFixed(1)},${C(z).toFixed(1)}`:null}).filter(Boolean).join(" ");return{...w,d:P,color:Es[R%Es.length]}}),S=5,E=Array.from({length:S},(w,R)=>d+(m-d)*R/(S-1));return I.jsxs("div",{className:"chart-card line-chart",children:[I.jsxs("div",{className:"line-chart-head",children:[e?I.jsx("h4",{children:e}):null,I.jsx("ul",{className:"line-legend",children:N.map(w=>I.jsxs("li",{children:[I.jsx("i",{style:{background:w.color}}),w.label]},w.key))})]}),I.jsx("div",{className:"line-chart-scroll",children:I.jsxs("svg",{viewBox:`0 0 ${f} ${p}`,width:"100%",height:p,preserveAspectRatio:"none",role:"img",children:[E.map(w=>I.jsxs("g",{children:[I.jsx("line",{x1:y,x2:f-b,y1:C(w),y2:C(w),className:"line-grid"}),I.jsx("text",{x:y-6,y:C(w)+4,className:"line-axis",textAnchor:"end",children:w.toFixed(0)})]},w)),I.jsx("text",{x:8,y:14,className:"line-axis",children:r}),N.map(w=>I.jsx("path",{d:w.d,fill:"none",stroke:w.color,strokeWidth:2.2},w.key)),t.map((w,R)=>I.jsx("text",{x:$(R),y:p-10,className:"line-axis",textAnchor:"middle",children:R%Math.ceil(t.length/8)===0?w.x:""},`${w.x}-${R}`))]})})]})}function QJ({points:e,yUnit:t="",yAxisLabel:n="",designColor:r="#e8590c",invertY:o=!1}){if(!e.length)return I.jsx("div",{className:"chart-card line-chart",children:I.jsx("div",{className:"empty",children:"暂无曲线数据"})});const s=e.map(w=>w.value).filter(w=>Number.isFinite(w)),i=e.map(w=>w.design).filter(w=>Number.isFinite(w)),l=[...s,...i];let c=l.length?Math.min(...l):0,u=l.length?Math.max(...l):1;u-c<1e-6&&(c-=1,u+=1);const d=(u-c)*.08||1;c-=d,u+=d;const m=Math.max(920,e.length*22),f=260,p=52,y=12,b=16,x=34,v=m-p-y,g=f-b-x,h=w=>p+(e.length<=1?v/2:w/(e.length-1)*v),$=w=>o?b+(w-c)/(u-c)*g:b+(u-w)/(u-c)*g,C=6,N=Array.from({length:C},(w,R)=>c+(u-c)*R/(C-1)),S=t?String(t):"",E=e.map((w,R)=>{var M;const P=w.design;return Number.isFinite(P)?`${R===0||!Number.isFinite((M=e[R-1])==null?void 0:M.design)?"M":"L"}${h(R).toFixed(1)},${$(P).toFixed(1)}`:null}).filter(Boolean).join(" ");return I.jsx("div",{className:"chart-card line-chart section-mark-chart",children:I.jsx("div",{className:"line-chart-scroll",children:I.jsxs("svg",{viewBox:`0 0 ${m} ${f}`,width:"100%",height:f,preserveAspectRatio:"none",role:"img",children:[N.map(w=>I.jsxs("g",{children:[I.jsx("line",{x1:p,x2:m-y,y1:$(w),y2:$(w),className:"line-grid"}),I.jsxs("text",{x:p-6,y:$(w)+4,className:"line-axis",textAnchor:"end",children:[Number.isInteger(w)?String(w):w.toFixed(1),S]})]},w)),n?I.jsx("text",{x:6,y:14,className:"line-axis",children:n}):null,E?I.jsx("path",{d:E,fill:"none",stroke:r,strokeWidth:2}):null,e.map((w,R)=>{const P=w.value;if(!Number.isFinite(P))return null;const T=h(R),M=$(P),z=$(0),B=Math.min(M,z),F=Math.max(3,Math.abs(M-z));return I.jsx("rect",{x:T-3,y:B,width:6,height:F,fill:w.color||"#214080",opacity:.9},`${w.x}-${R}`)}),e.map((w,R)=>I.jsx("text",{x:h(R),y:f-8,className:"line-axis",textAnchor:"middle",children:R%Math.ceil(e.length/8)===0?w.x:""},`lb-${R}`))]})})})}function T6({title:e,items:t,variant:n="cells"}){return n==="stacked"?I.jsxs("div",{className:"chart-card status-strip status-strip-stacked",children:[e?I.jsx("h4",{children:e}):null,I.jsx("div",{className:"status-strip-row stacked-row",children:t.map((r,o)=>{if(r.stop)return I.jsx("div",{className:"status-stack stop",title:r.label,children:I.jsx("span",{className:"stack-stop",children:"停"})},`${r.label}-${o}`);const s=Math.max(0,Number(r.value)||0),i=Math.max(0,Number(r.secondary)||0),l=s+i||1;if(i<=0&&s>0)return I.jsx("div",{className:"status-stack all-grey",title:r.label,children:I.jsx("span",{className:"stack-solo",children:s})},`${r.label}-${o}`);if(s<=0&&i>0)return I.jsx("div",{className:"status-stack all-green",title:r.label,children:I.jsx("span",{className:"stack-solo",children:i})},`${r.label}-${o}`);const c=s/l*100,u=i/l*100;return I.jsxs("div",{className:"status-stack",title:r.label,children:[I.jsx("div",{className:"stack-top",style:{flex:`${Math.max(c,8)} 1 0`},children:I.jsx("span",{children:s})}),I.jsx("div",{className:"stack-bot",style:{flex:`${Math.max(u,8)} 1 0`},children:I.jsx("span",{children:i})})]},`${r.label}-${o}`)})})]}):I.jsxs("div",{className:"chart-card status-strip",children:[e?I.jsx("h4",{children:e}):null,I.jsx("div",{className:"status-strip-row",children:t.map((r,o)=>I.jsxs("div",{className:`status-cell tone-${r.tone||"ok"}`,title:r.label,children:[I.jsx("span",{className:"status-top",children:r.label}),I.jsx("span",{className:"status-val",children:r.value})]},`${r.label}-${o}`))})]})}const JJ="AJZP1",M6="application/x-ajz-machine",ZJ="aijianzhan-page-machine-v1";function eZ(e,t){const n=new Uint8Array(e.length),r=new TextEncoder().encode(t);for(let o=0;og.entity===n.entity)||(((p=o==null?void 0:o.apis)==null?void 0:p.resources)||[]).find(g=>String(g.path||"").replace(/^\//,"")===r),c=(l==null?void 0:l.operations)||["list","get","create","update","delete","import","export"],u={};c.includes("list")&&(u.list={method:"GET",path:i}),c.includes("get")&&(u.get={method:"GET",path:`${i}/{id}`}),c.includes("create")&&(u.create={method:"POST",path:i}),c.includes("update")&&(u.update={method:"PUT",path:`${i}/{id}`}),c.includes("delete")&&(u.delete={method:"DELETE",path:`${i}/{id}`}),c.includes("import")&&(u.import={method:"POST",path:`${i}/import`}),c.includes("export")&&(u.export={method:"GET",path:`${i}/export`}),u.aggregate={method:"GET",path:`${i}/aggregate`},u.blueprint={method:"GET",path:`${s}/blueprint`};const d=((o==null?void 0:o.entities)||[]).find(g=>g.name===n.entity),m=((d==null?void 0:d.fields)||[]).filter(g=>g==null?void 0:g.name).slice(0,80).map(g=>({name:String(g.name),type:String(g.type||"string"),label:g.label?String(g.label):void 0}));return{v:1,kind:"ajz-page",slug:t,base_path:s,page:{id:n.id,type:n.type,route:n.route||"",title:n.title||""},entity:n.entity,resource:r,auth:{type:"bearer_jwt",header:"Authorization"},endpoints:u,filters:((y=n.layout)==null?void 0:y.filters)||((b=l==null?void 0:l.list)==null?void 0:b.allowed_filters)||[],fields:m,actions:((x=n.layout)==null?void 0:x.actions)||[],widgets:(((v=n.layout)==null?void 0:v.widgets)||[]).map(g=>({type:g.type,title:g.title,entity:g.entity})),notes:"list 支持 filter.=value;需 Authorization: Bearer "}}function rZ(e){var n,r,o;const t=a.useMemo(()=>{try{const s=nZ(e);return O6(s)}catch{return""}},[e.slug,e.resource,(n=e.page)==null?void 0:n.id,(r=e.page)==null?void 0:r.type,(o=e.page)==null?void 0:o.entity,e.blueprint]);return t?I.jsx("div",{className:"ajz-machine-tag",hidden:!0,"aria-hidden":"true","data-ajz-machine":t,"data-ajz-mime":M6,"data-ajz-codec":"AJZP1","data-ajz-kind":"page","data-ajz-slug":e.slug,"data-ajz-page":e.page.id,"data-ajz-entity":e.page.entity,"data-ajz-resource":e.resource}):null}function _6(e){const t=a.useMemo(()=>{try{return O6({v:1,kind:"ajz-block",slug:e.slug,resource:e.resource,block_id:e.blockId,endpoint:e.endpoint||{method:"GET",path:`/api/v1/apps/${e.slug}/${e.resource}`},...e.extra||{}})}catch{return""}},[e.slug,e.resource,e.blockId,e.endpoint,e.extra]);return t?I.jsx("div",{className:"ajz-machine-tag",hidden:!0,"aria-hidden":"true","data-ajz-machine":t,"data-ajz-mime":M6,"data-ajz-codec":"AJZP1","data-ajz-kind":"block","data-ajz-block":e.blockId,"data-ajz-slug":e.slug,"data-ajz-resource":e.resource}):null}function oZ(e,t){return t==null||t===""?"—":typeof t=="number"?Number.isInteger(t)?String(t):t.toFixed(2):String(t)}function sZ(e,t){if(!t.filter_field)return!0;const n=e[t.filter_field],r=Number(n),o=t.filter_op||"gt",s=Number(t.filter_value??0);return o==="gt"?Number.isFinite(r)&&r>s:o==="gte"?Number.isFinite(r)&&r>=s:o==="lt"?Number.isFinite(r)&&rme&&me!=="全部"),S=g.select_field||g.filter_select_field||["worksite","site","store","warehouse","dept"].find(me=>s[me])||"",E=!!(C&&N.length>0),w=!!S;a.useEffect(()=>{(async()=>{try{const me={};if(C&&f){const Te=g.filter_value_aliases||{};me[C]=Te[f]||f}S&&y&&(me[S]=y);const Re=await ep(t,n,r,me,1,2e3);u(Re.items||[]),m(Re.total||(Re.items||[]).length)}catch(me){l(me.message||String(me))}})()},[t,n,r,C,f,S,y,l,g.filter_value_aliases]);const R=me=>{var Fe;const Re=g.table_headers||[],Te=o.find(et=>et.type==="table"),Ge=((Te==null?void 0:Te.columns)||[]).indexOf(me);return Ge>=0&&Re[Ge]?Re[0]==="序号"&&Re[Ge+1]?Re[Ge+1]:Re[Ge]:((Fe=s[me])==null?void 0:Fe.label)||me},P=o.find(me=>me.type==="line_chart"||me.type==="bar_chart"),T=o.find(me=>me.type==="status_strip"),M=o.find(me=>me.type==="table"),z=g.chart_side_label||(P==null?void 0:P.title)||"主图",B=g.strip_side_label||(T==null?void 0:T.title)||"状态",F=g.table_side_label||"明细",L=g.table_title||(M==null?void 0:M.title)||"明细表",j=g.stats_left_label||"",O=g.stats_right_label||"",A=a.useMemo(()=>{const me=(P==null?void 0:P.x_field)||(P==null?void 0:P.group_by)||Object.keys(s)[0]||"id";return[...c].sort((Re,Te)=>String(Re[me]??"").localeCompare(String(Te[me]??""),"zh"))},[c,P,s]),k=g.value_field||(P==null?void 0:P.metric)||((Ee=P==null?void 0:P.metrics)==null?void 0:Ee[0])||["value","cjl_value","cum_value","cum_settlement_mm","amount","qty"].find(me=>s[me])||"",_=g.color_field||["color","cjl_color","mark_color"].find(me=>s[me])||"",D=g.design_field||["design_value","design_settlement_mm","target","design"].find(me=>s[me])||"",V=g.category_colors||{},W=g.category_field||C||"",K=String(g.chart_style||"").toLowerCase(),q=K==="line"||K==="multi_series"||K==="line_chart",Y=K==="section_marks"||K==="colored_marks",ee=!!P&&!!k&&!!(P!=null&&P.x_field||P!=null&&P.group_by||Object.keys(s)[0]),ie=Y||!q&&ee,ae=a.useMemo(()=>{if(!ie)return[];const me=(P==null?void 0:P.x_field)||(P==null?void 0:P.group_by)||Object.keys(s)[0]||"",Re=["#1d4f91","#7c3aed","#868e96","#0f766e","#c2410c"],Te=new Map;return A.map(Ue=>{const Ge=wl(k?Ue[k]:void 0),Fe=D?wl(Ue[D]):NaN;let et=_?String(Ue[_]||"").trim():"";if(!et&&W){const ve=String(Ue[W]??"");V[ve]?et=V[ve]:ve&&(Te.has(ve)||Te.set(ve,Te.size),et=Re[Te.get(ve)%Re.length])}return et||(et=Re[0]),{x:String(Ue[me]??""),value:Number.isFinite(Ge)?Ge:0,design:Number.isFinite(Fe)?Fe:void 0,color:et}})},[ie,A,P,k,_,D,W,V,s]),U=a.useMemo(()=>{var Ge;if(!P||ie)return null;const me=P.x_field||P.group_by||"";let Re=((Ge=P.metrics)!=null&&Ge.length?P.metrics:P.metric&&P.metric!=="count"?[P.metric]:[]).filter(Boolean);if((!Re.length||!Re.some(Fe=>s[Fe]))&&(Re=Object.values(s).filter(Fe=>["int","bigint","decimal","number","float"].includes(String(Fe.type))).map(Fe=>Fe.name).filter(Fe=>!["id","tenant_id"].includes(Fe)).slice(0,4)),!me||!Re.length)return null;const Te=A.map(Fe=>{const et={};for(const ve of Re){const je=wl(Fe[ve]);Number.isFinite(je)&&(et[ve]=je)}return{x:String(Fe[me]??""),values:et}}),Ue=g.series_labels||{};return{title:P.title||"图表",points:Te,series:Re.map(Fe=>({key:Fe,label:Ue[Fe]||R(Fe)})),yUnit:P.y_unit||g.y_unit||""}},[P,A,ie,g.series_labels,g.y_unit,s]),Q=(Ve=g.legend_items)!=null&&Ve.length?g.legend_items:ie&&Object.keys(V).length?Object.entries(V).map(([me,Re])=>({label:me,color:Re})):((U==null?void 0:U.series)||[]).map((me,Re)=>({label:me.label,color:["#1d4f91","#7c3aed","#6b7280","#c2410c"][Re%4]})),Z=a.useMemo(()=>{if(!T)return[];const me=T.label_field||"",Re=T.value_field||"",Te=T.secondary_field||"";if(!me||!Re)return[];const Ue=Number(T.cycle_days)||Number(g.cycle_days)||30,Ge=T.variant==="stacked"||T.variant==="stacked_days"||!!Te;return A.map(Fe=>{const et=String(Fe[me]??"");let ve=wl(Fe[Re]),je=Te?wl(Fe[Te]):NaN;if(Number.isFinite(ve)||(ve=0),Number.isFinite(je)||(je=0),Ge&&ve+je>50&&ve+je<120){const Pe=Math.round(ve/100*Ue),pe=Math.max(0,Ue-Pe);ve=Pe,je=pe}const ce=!!Fe.stop_flag||String(Fe.status||"").includes("停")||Ge&&ve===0&&je===0;return{label:et,value:ve,secondary:Ge?je:void 0,stop:ce}})},[T,A,g.cycle_days]),ne=(T==null?void 0:T.variant)==="stacked"||(T==null?void 0:T.variant)==="stacked_days"||T!=null&&T.secondary_field?"stacked":"cells",oe=(qe=M==null?void 0:M.columns)!=null&&qe.length?M.columns:Object.keys(s).filter(me=>!["id","tenant_id"].includes(me)).slice(0,6),le=M?A.filter(me=>sZ(me,M)):[],re=a.useMemo(()=>{var Re,Te;if(!S)return[];if((Te=(Re=s[S])==null?void 0:Re.enum_values)!=null&&Te.length)return s[S].enum_values;const me=new Set;for(const Ue of c){const Ge=String(Ue[S]??"");Ge&&me.add(Ge)}return Array.from(me).sort()},[S,s,c]),X=a.useMemo(()=>{const me=(P==null?void 0:P.x_field)||(P==null?void 0:P.group_by)||"";return me&&new Set(c.map(Re=>String(Re[me]??""))).size||d},[c,d,P]),se=Number(g.chart_page_size)||28,ge=ie?ae.length:(U==null?void 0:U.points.length)||Z.length,de=Math.max(0,ge-se),Se=ae.slice(x,x+se),ue=(U==null?void 0:U.points.slice(x,x+se))||[],be=Z.slice(x,x+se),Ne=(()=>{if(h&&(j||O)&&h.includes(j||"\0"))return h;const me=[];return h&&me.push(h),j&&me.push(`${j}:${X}个`),O&&me.push(`${O}: ${d}个`),me.join(" ")||(d?`共 ${d} 条`:"")})(),we=g.select_label||(S?R(S):"")||"",ze=(P==null?void 0:P.y_unit)||g.y_unit||"",he=g.y_axis_label||ze||"",ke=!!g.invert_y,Oe=g.design_series_color||"#e8590c",Ce=g.empty_table_text||"暂无数据",Me=[C,S].filter(Boolean);return I.jsxs("div",{className:"sf-root",children:[I.jsx(_6,{slug:n,resource:r,blockId:"sf-dashboard",endpoint:{method:"GET",path:`/api/v1/apps/${n}/${r}`},extra:{page_type:"dashboard",filters:Me,list_query:"filter.=value&page=1&page_size=2000"}}),I.jsxs("div",{className:"sf-filterbar",children:[I.jsx("div",{className:"sf-context",children:I.jsx("strong",{children:Ne})}),E&&I.jsx("div",{className:"sf-radios",children:["",...N].map(me=>I.jsxs("label",{className:`sf-radio ${(f||"")===me?"on":""}`,children:[I.jsx("input",{type:"radio",name:"sf-radio",checked:(f||"")===me,onChange:()=>p(me)}),me||g.radio_all_label||"全部"]},me||"all"))}),w&&I.jsxs("label",{className:"sf-select",children:[we?`${we}:`:null,I.jsxs("select",{value:y,onChange:me=>b(me.target.value),children:[I.jsx("option",{value:"",children:"--请选择--"}),re.map(me=>I.jsx("option",{value:me,children:me},me))]})]}),$?I.jsx("span",{className:"sf-hint",children:$}):null]}),Q.length>0&&P&&I.jsx("div",{className:"sf-legend",children:Q.map((me,Re)=>I.jsx("span",{className:`sf-leg-item${/曲线|折线|line/i.test(me.label)?" leg-line":""}`,style:{"--leg":me.color},children:me.label},`${me.label}-${Re}`))}),I.jsxs("div",{className:"sf-body",children:[I.jsxs("div",{className:"sf-rail",children:[P?I.jsx("div",{className:"sf-rail-seg chart",children:z}):null,T?I.jsx("div",{className:"sf-rail-seg strip",children:B}):null,M?I.jsx("div",{className:"sf-rail-seg table",children:F}):null]}),I.jsxs("div",{className:"sf-main",children:[P&&I.jsxs("div",{className:"sf-line-wrap",children:[ie?I.jsx(QJ,{points:Se.length?Se:ae,yUnit:ze,yAxisLabel:he,invertY:ke,designColor:Oe}):U?I.jsx(R6,{title:"",points:ue.length?ue:U.points,series:U.series,yUnit:U.yUnit}):I.jsx("div",{className:"empty",style:{padding:"2rem",textAlign:"center"},children:"暂无曲线数据(请检查蓝图 x_field / metrics 与已导入数据)"}),de>0&&I.jsx("input",{className:"sf-scroll",type:"range",min:0,max:de,value:x,onChange:me=>v(Number(me.target.value))})]}),T&&I.jsx("div",{className:"sf-strip-wrap",children:I.jsx(T6,{title:"",variant:ne,items:be})}),M&&I.jsxs("div",{className:"sf-table-panel",children:[I.jsx("div",{className:"sf-table-title",children:L}),I.jsx("div",{className:"table-wrap",children:I.jsxs("table",{className:"sf-table",children:[I.jsx("thead",{children:I.jsxs("tr",{children:[I.jsx("th",{children:"序号"}),oe.map(me=>I.jsx("th",{children:R(me)},me))]})}),I.jsxs("tbody",{children:[le.map((me,Re)=>I.jsxs("tr",{children:[I.jsx("td",{children:Re+1}),oe.map(Te=>I.jsx("td",{children:oZ(Te,me[Te])},Te))]},Re)),!le.length&&I.jsx("tr",{children:I.jsx("td",{colSpan:oe.length+1,className:"empty",children:Ce})})]})]})})]})]})]})]})}function z6(e){if(typeof e=="string")return e.trim();if(e&&typeof e=="object"){const t=e;for(const n of["label","title","name","text"])if(typeof t[n]=="string"&&String(t[n]).trim())return String(t[n]).trim()}return""}function aZ(e){if(!e||typeof e!="object")return"";const t=e;for(const n of["route","path","href","url"])if(typeof t[n]=="string"&&String(t[n]).trim())return String(t[n]).trim();return""}function hE(e,t=[]){return(Array.isArray(e)?e:t).map(z6).filter(Boolean)}function Zu(e,t){return e.title||e.id}function lZ(e,t){var r,o,s;const n=(((r=e==null?void 0:e.apis)==null?void 0:r.resources)||[]).find(i=>i.entity===t)||((s=(o=e==null?void 0:e.apis)==null?void 0:o.resources)==null?void 0:s[0]);return String((n==null?void 0:n.path)||`/${t}`).replace(/^\//,"")}function yE(e,t){var r;const n=((e==null?void 0:e.entities)||[]).find(o=>o.name===t)||((r=e==null?void 0:e.entities)==null?void 0:r[0]);return((n==null?void 0:n.fields)||[]).map(o=>{var s;return{name:o.name,label:o.label||o.name,type:o.type||"string",enum_values:o.enum_values||o.enumValues,widget:(s=o.ui)==null?void 0:s.widget}})}const cZ=new Set(["id","tenant_id","org_unit_id","created_by","created_at","updated_at"]),vE=/(status|type|category|state|分类|类型|状态)$/i;function jf(e){return e.type==="enum"||e.widget==="select"||e.enum_values&&e.enum_values.length>0?!0:vE.test(e.name)||vE.test(e.label)}function B0(e){return e.type==="datetime"||Bf(e)?!1:e.type==="date"||e.widget==="datepicker"?!0:/日期/.test(e.label)||/(^|_)date$/i.test(e.name)}function Bf(e){return e.type==="datetime"||e.widget==="datetime"||/(_at$|时间|datetime)/i.test(e.name)}function L0(e,t){if(!e)return"";const n=new Date(e);if(Number.isNaN(n.getTime()))return t==="date"?e.slice(0,10):e.length>=16?e.slice(0,16):e;const r=l=>String(l).padStart(2,"0"),o=n.getFullYear(),s=r(n.getMonth()+1),i=r(n.getDate());return t==="date"?`${o}-${s}-${i}`:`${o}-${s}-${i}T${r(n.getHours())}:${r(n.getMinutes())}`}function uZ(e,t){if(!e)return"";const n=new Date(e);return Number.isNaN(n.getTime())?e:n.toISOString()}function dZ(e){return/status_check|_status_check/i.test(e)?"状态值不合法:请从下拉框选择蓝图允许的枚举值。":/violates check constraint/i.test(e)||/检查约束/.test(e)?`字段值不符合约束:${e}`:/null value|非空|not-null/i.test(e)?`必填字段为空:${e}`:e}function fZ({meta:e,value:t,options:n,onChange:r}){if(jf(e)||n.length>0){const o=n.length?n:e.enum_values||[];return I.jsxs("select",{value:t,onChange:s=>r(s.target.value),children:[I.jsx("option",{value:"",children:"请选择"}),o.map(s=>I.jsx("option",{value:s,children:s},s))]})}return e.type==="boolean"||e.widget==="checkbox"?I.jsxs("select",{value:t,onChange:o=>r(o.target.value),children:[I.jsx("option",{value:"",children:"请选择"}),I.jsx("option",{value:"true",children:"是"}),I.jsx("option",{value:"false",children:"否"})]}):Bf(e)?I.jsx("input",{type:"datetime-local",value:L0(t,"datetime"),onChange:o=>r(uZ(o.target.value))}):B0(e)?I.jsx("input",{type:"date",value:L0(t,"date"),onChange:o=>r(o.target.value)}):e.type==="int"||e.type==="bigint"||e.type==="decimal"||e.widget==="number"?I.jsx("input",{type:"number",step:e.type==="decimal"?"0.01":"1",value:t,onChange:o=>r(o.target.value)}):e.type==="text"||e.widget==="textarea"?I.jsx("textarea",{rows:3,value:t,onChange:o=>r(o.target.value)}):I.jsx("input",{type:"text",value:t,onChange:o=>r(o.target.value)})}function j6(e,t){if(t==null||t==="")return"—";if(String(e).endsWith("_at")){const n=new Date(String(t));if(!Number.isNaN(n.getTime()))return n.toLocaleString("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}return String(t)}function mZ(e){return/在售|正常|启用|合格/.test(e)?"ok":/停售|禁用|缺货|超限|异常|逾期/.test(e)?"bad":"warn"}function pZ({session:e,blueprint:t,onBack:n}){var z,B,F,L,j,O,A,k,_,D,V,W,K,q,Y,ee,ie,ae,U,Q,Z,ne,oe,le,re,X,se,ge,de,Se;const r=(z=t==null?void 0:t.meta)==null?void 0:z.slug,o=(t==null?void 0:t.pages)||[],s=o.find(ue=>ue.type==="list")||o[0],[i,l]=a.useState((s==null?void 0:s.id)||""),[c,u]=a.useState(null),[d,m]=a.useState(""),[f,p]=a.useState(""),[y,b]=a.useState(!1),x=a.useMemo(()=>{var ue,be;if(i.startsWith("__edit__")){const Ne=i.replace("__edit__",""),we=o.find(he=>he.type==="list"&&he.entity===Ne),ze=yE(t,Ne);return{id:i,title:"编辑",route:`/${Ne}/edit`,type:"form_edit",entity:Ne,layout:{form_fields:((be=(ue=we==null?void 0:we.layout)==null?void 0:ue.columns)==null?void 0:be.filter(he=>he!=="id"))||ze.map(he=>he.name).filter(he=>he!=="id"),actions:["edit"]}}}return o.find(Ne=>Ne.id===i)||s},[i,o,s,t]),v=x?lZ(t,x.entity):"",g=a.useMemo(()=>x?yE(t,x.entity):[],[x,t]),h=a.useMemo(()=>Object.fromEntries(g.map(ue=>[ue.name,ue])),[g]),$=a.useMemo(()=>{const ue=/^(欢迎您|退出|帮助|登录|注册|返回|系统首页|修改密码)$/,be=o.filter(we=>we.type!=="form_edit"&&we.type!=="detail"&&!ue.test(String(we.title||""))),Ne=we=>we.type==="list"?0:we.type==="form_create"?1:we.type==="dashboard"?2:3;return[...be].sort((we,ze)=>Ne(we)-Ne(ze))},[o]),C=a.useMemo(()=>{var we,ze;const ue=Array.isArray((ze=(we=t==null?void 0:t.meta)==null?void 0:we.ui)==null?void 0:ze.nav_items)?t.meta.ui.nav_items:[];if(!ue.length)return[];const be=$.find(he=>he.type==="dashboard"),Ne=he=>he.replace(/\/+$/,"").toLowerCase();return ue.map(he=>{const ke=z6(he),Oe=aZ(he);if(!ke&&!Oe)return null;let Ce=(Oe?$.find(Me=>Ne(String(Me.route||""))===Ne(Oe)):void 0)||$.find(Me=>Me.title===ke||String(Me.title||"").includes(ke)||ke&&ke.includes(String(Me.title||"")));return!Ce&&be&&ke&&(be.title===ke||String(be.title||"").includes(ke))&&(Ce=be),{label:ke||(Ce==null?void 0:Ce.title)||Oe,pageId:Ce==null?void 0:Ce.id}}).filter(Boolean)},[(F=(B=t==null?void 0:t.meta)==null?void 0:B.ui)==null?void 0:F.nav_items,$]),N=a.useMemo(()=>{var ue,be;return hE((be=(ue=t==null?void 0:t.meta)==null?void 0:ue.ui)==null?void 0:be.shell_links,["欢迎您","退出","帮助"])},[(j=(L=t==null?void 0:t.meta)==null?void 0:L.ui)==null?void 0:j.shell_links]),S=((A=(O=t==null?void 0:t.meta)==null?void 0:O.ui)==null?void 0:A.platform_subtitle)||((k=t==null?void 0:t.meta)==null?void 0:k.platform_subtitle)||"",E=a.useMemo(()=>{var ue,be;return hE((be=(ue=t==null?void 0:t.meta)==null?void 0:ue.ui)==null?void 0:be.float_actions,["客服","电话","点击隐藏"])},[(D=(_=t==null?void 0:t.meta)==null?void 0:_.ui)==null?void 0:D.float_actions]),w=((V=t==null?void 0:t.meta)==null?void 0:V.ui_preset)||((K=(W=o.find(ue=>{var be;return(be=ue.layout)==null?void 0:be.preset}))==null?void 0:W.layout)==null?void 0:K.preset)||"default",R=((q=t==null?void 0:t.meta)==null?void 0:q.project_context)||"",P=ue=>{var we,ze,he;if(!ue||ue.type!=="dashboard")return!1;if(((we=ue.layout)==null?void 0:we.preset)==="screenshot_faithful"||((ze=ue.layout)==null?void 0:ze.preset)==="ops_monitor")return!0;const be=((he=ue.layout)==null?void 0:he.widgets)||[],Ne=new Set(be.map(ke=>ke.type));return(Ne.has("line_chart")||Ne.has("bar_chart"))&&(Ne.has("status_strip")||Ne.has("table"))},T=w==="screenshot_faithful"||w==="ops_monitor"||!!((ee=(Y=t==null?void 0:t.meta)==null?void 0:Y.source)!=null&&ee.screenshot_faithful)||!!((U=(ae=(ie=t==null?void 0:t.meta)==null?void 0:ie.source)==null?void 0:ae.image_refs)!=null&&U.length)||!!((ne=(Z=(Q=t==null?void 0:t.meta)==null?void 0:Q.source)==null?void 0:Z.layout_refs)!=null&&ne.length)||P(o.find(ue=>ue.type==="dashboard")),M=((le=(oe=t==null?void 0:t.meta)==null?void 0:oe.ui)==null?void 0:le.platform_title)||((re=t==null?void 0:t.meta)==null?void 0:re.platform_title)||"";return a.useEffect(()=>{if(!T)return;const ue=o.find(be=>be.type==="dashboard");ue&&(!i||i===(s==null?void 0:s.id))&&(s==null?void 0:s.type)!=="dashboard"&&l(ue.id)},[T,(X=t==null?void 0:t.meta)==null?void 0:X.slug]),I.jsxs("div",{className:`gen-app ${T?"gen-app-ops":""}`,children:[T?I.jsxs("header",{className:"sf-shell-bar",children:[I.jsxs("div",{className:"sf-shell-left",children:[M||"业务管理平台",S?I.jsx("span",{className:"sf-shell-en",children:S}):null]}),I.jsx("div",{className:"sf-shell-center",children:((se=t==null?void 0:t.meta)==null?void 0:se.name)||r}),I.jsxs("div",{className:"sf-shell-right",children:[N.map(ue=>I.jsx("span",{className:"sf-shell-link",children:ue},ue)),I.jsx("button",{type:"button",className:"sf-shell-back",onClick:n,children:"返回控制台"})]})]}):I.jsxs("header",{className:"gen-header",children:[I.jsxs("div",{children:[I.jsx("p",{className:"gen-eyebrow",children:"业务模块"}),I.jsx("h1",{className:"gen-title",children:((ge=t==null?void 0:t.meta)==null?void 0:ge.name)||r}),I.jsx("p",{className:"gen-desc",children:((de=t==null?void 0:t.meta)==null?void 0:de.description)||"由蓝图自动生成的业务页面"})]}),I.jsx("button",{type:"button",className:"btn secondary",onClick:n,children:"返回控制台"})]}),I.jsxs("nav",{className:`gen-nav ${T?"gen-nav-ops":""}`,children:[(T&&C.length>0?C:$.map(ue=>({label:Zu(ue),pageId:ue.id}))).map((ue,be)=>{const Ne=ue.pageId?(x==null?void 0:x.id)===ue.pageId&&!c:!1;return I.jsx("button",{type:"button",className:`gen-nav-item ${Ne?"active":""} ${ue.pageId?"":"is-chrome"}`,onClick:()=>{ue.pageId&&(u(null),l(ue.pageId),m(""),p(""))},children:ue.label},`${ue.label}-${be}`)}),T&&C.length>0?$.filter(ue=>ue.type==="list"||ue.type==="dashboard").filter(ue=>!C.some(be=>be.pageId===ue.id||be.label===ue.title)).map(ue=>I.jsx("button",{type:"button",className:`gen-nav-item ${(x==null?void 0:x.id)===ue.id&&!c?"active":""}`,onClick:()=>{u(null),l(ue.id),m(""),p("")},children:Zu(ue)},ue.id)):null]}),T&&R&&(x==null?void 0:x.type)!=="dashboard"&&I.jsx("div",{className:"sf-mini-context",children:R}),d&&I.jsx("p",{className:"flash err",children:d}),f&&I.jsx("p",{className:"flash ok",children:f}),x&&v&&I.jsx(rZ,{slug:r,resource:v,page:x,blueprint:t}),(x==null?void 0:x.type)==="list"&&!c&&I.jsx(gZ,{session:e,slug:r,resource:v,page:x,fieldMap:h,busy:y,setBusy:b,setError:m,setInfo:p,onCreate:()=>{const ue=o.find(be=>be.type==="form_create"&&be.entity===x.entity);ue&&l(ue.id)},onEdit:ue=>{u(ue);const be=o.find(Ne=>Ne.type==="form_edit"&&Ne.entity===x.entity);l(be?be.id:`__edit__${x.entity}`)}}),((x==null?void 0:x.type)==="form_create"||(x==null?void 0:x.type)==="form_edit")&&I.jsx(hZ,{session:e,slug:r,resource:v,page:x,title:Zu(x),fields:g,editId:x.type==="form_edit"?c:null,busy:y,setBusy:b,setError:m,setInfo:p,onDone:()=>{u(null);const ue=o.find(be=>be.type==="list"&&be.entity===x.entity);ue&&l(ue.id)}}),(x==null?void 0:x.type)==="dashboard"&&!c&&(T||P(x)?I.jsx(iZ,{session:e,slug:r,resource:v,title:Zu(x),widgets:(((Se=x.layout)==null?void 0:Se.widgets)||[]).filter(ue=>ue.type!=="kpi"),fieldMap:h,meta:(t==null?void 0:t.meta)||{},setError:m}):I.jsx(yZ,{session:e,slug:r,resource:v,page:x,fieldMap:h,setError:m})),T&&E.length>0?I.jsx("aside",{className:"sf-float-dock","aria-label":"快捷操作",children:E.map(ue=>I.jsx("button",{type:"button",tabIndex:-1,children:ue},ue))}):null]})}function gZ(e){var T,M,z,B,F,L,j;const{session:t,slug:n,resource:r,page:o,fieldMap:s,busy:i,setBusy:l,setError:c,setInfo:u,onCreate:d,onEdit:m}=e,f=(M=(T=o.layout)==null?void 0:T.columns)!=null&&M.length?o.layout.columns:Object.keys(s).filter(O=>!["id","tenant_id"].includes(O)).slice(0,8),p=a.useMemo(()=>{var O;return((O=o.layout)==null?void 0:O.filters)||[]},[(z=o.layout)==null?void 0:z.filters]),y=((B=o.layout)==null?void 0:B.actions)||["refresh"],b=((F=o.layout)==null?void 0:F.action_labels)||{},x=(O,A)=>b[O]||A,v=y.filter(O=>!["edit","delete"].includes(O)),[g,h]=a.useState({}),[$,C]=a.useState({}),[N,S]=a.useState([]),[E,w]=a.useState(0),R=a.useCallback(async()=>{var O;l(!0),c("");try{const A=await ep(t,n,r,g);S(A.items||[]),w(A.total||0);const k={};for(const _ of p){const D=s[_];if((O=D==null?void 0:D.enum_values)!=null&&O.length){k[_]=D.enum_values;continue}const V=new Set;for(const W of A.items||[]){const K=W[_];K!=null&&String(K).trim()!==""&&V.add(String(K))}k[_]=Array.from(V).sort()}C(_=>{let D=!1;const V={..._};for(const[W,K]of Object.entries(k)){const q=_[W]||[];(q.length!==K.length||q.some((Y,ee)=>Y!==K[ee]))&&(V[W]=K,D=!0)}return D?V:_})}catch(A){c(A.message||String(A))}finally{l(!1)}},[t,n,r,g,l,c,p,s]);a.useEffect(()=>{R()},[R]);async function P(O){if(confirm("确认删除该行?")){l(!0);try{await CY(t,n,r,O),u("已删除"),await R()}catch(A){c(A.message||String(A))}finally{l(!1)}}}return I.jsxs("section",{className:"panel gen-panel",children:[I.jsx(_6,{slug:n,resource:r,blockId:`list:${o.id}`,endpoint:{method:"GET",path:`/api/v1/apps/${n}/${r}`},extra:{page_type:"list",filters:p,columns:f}}),I.jsxs("div",{className:"gen-toolbar",children:[I.jsx("h2",{className:"section-title",style:{margin:0},children:o.title}),I.jsx("div",{className:"actions",style:{margin:0},children:v.map(O=>{if(O==="create")return I.jsx("button",{type:"button",className:"btn",disabled:i,onClick:d,children:x("create","新增")},O);if(O==="refresh")return I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:()=>void R(),children:x("refresh","刷新")},O);if(O==="import")return I.jsxs("label",{className:"file-field btn secondary",style:{margin:0,padding:"8px 12px",cursor:"pointer"},children:[x("import","导入 Excel"),I.jsx("input",{type:"file",accept:".xlsx,.xlsm,.csv",hidden:!0,onChange:async k=>{var D,V;const _=(D=k.target.files)==null?void 0:D[0];if(_){l(!0);try{const W=await IY(t,n,r,_),K=W.inserted||0,q=W.skipped||0,Y=(W.errors||[]).length;u(`导入完成:成功 ${K} 行`+(q?`,跳过 ${q}`:"")+(Y?`,错误 ${Y} 条(仅展示部分)`:"")),Y&&((V=W.errors)!=null&&V[0])&&c(String(W.errors[0])),await R()}catch(W){c(W.message||String(W))}finally{l(!1),k.target.value=""}}}})]},O);if(O==="export")return I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:async()=>{l(!0);try{await PY(t,n,r,"xlsx"),u("已导出 Excel")}catch(k){c(k.message||String(k))}finally{l(!1)}},children:x("export","导出 Excel")},O);if(O==="search")return I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:()=>void R(),children:x("search","查询")},O);const A=x(O,O.replace(/^custom_/,""));return I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:()=>u(`按键「${A}」已按说明展示(业务逻辑可后续绑定)`),children:A},O)})})]}),p.length>0&&(((L=o.layout)==null?void 0:L.filter_style)==="section_radios"||((j=o.layout)==null?void 0:j.filter_style)==="radios")?I.jsx("div",{className:"ops-section-radios",children:(()=>{const O=p.find(_=>{var D;return(((D=s[_])==null?void 0:D.enum_values)||[]).length>0})||p[0],A=s[O]||{label:O},k=$[O]||A.enum_values||[];return I.jsxs(I.Fragment,{children:[I.jsx("span",{className:"ops-radio-label",children:A.label||"类型"}),["",...k].map(_=>I.jsx("button",{type:"button",className:`ops-radio ${(g[O]||"")===_?"active":""}`,onClick:()=>{h(D=>({...D,[O]:_})),setTimeout(()=>void R(),0)},children:_||"全部"},_||"all")),p.filter(_=>_!==O).map(_=>{const D=s[_]||{label:_};return I.jsxs("label",{className:"ops-extra-filter",children:[D.label||_,I.jsx("input",{value:g[_]||"",placeholder:"筛选…",onChange:V=>h(W=>({...W,[_]:V.target.value}))})]},_)}),I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:()=>void R(),children:"应用筛选"})]})})()}):p.length>0?I.jsxs("div",{className:"row compact gen-filters",children:[p.map(O=>{const A=s[O]||{name:O,label:O,type:"string"},k=$[O]||A.enum_values||[],_=jf(A)||k.length>0;return I.jsxs("label",{children:[A.label||O,_?I.jsxs("select",{value:g[O]||"",onChange:D=>h(V=>({...V,[O]:D.target.value})),children:[I.jsx("option",{value:"",children:"全部"}),k.map(D=>I.jsx("option",{value:D,children:D},D))]}):I.jsx("input",{value:g[O]||"",placeholder:"筛选…",onChange:D=>h(V=>({...V,[O]:D.target.value}))})]},O)}),I.jsx("div",{className:"actions",style:{marginTop:0},children:I.jsx("button",{type:"button",className:"btn secondary",disabled:i,onClick:()=>void R(),children:"应用筛选"})})]}):null,I.jsxs("p",{className:"pages-line",children:["共 ",E," 条"]}),N.length===0?I.jsx("div",{className:"empty",children:"暂无数据,可点「新增」或导入 Excel"}):I.jsx("div",{className:"table-wrap",children:I.jsxs("table",{children:[I.jsx("thead",{children:I.jsxs("tr",{children:[f.map(O=>{var A;return I.jsx("th",{children:((A=s[O])==null?void 0:A.label)||O},O)}),(y.includes("edit")||y.includes("delete"))&&I.jsx("th",{children:"操作"})]})}),I.jsx("tbody",{children:N.map((O,A)=>I.jsxs("tr",{children:[f.map(k=>{const _=j6(k,O[k]);return k==="status"?I.jsx("td",{className:"cell-status",children:I.jsx("span",{className:`badge ${mZ(_)}`,children:_})},k):I.jsx("td",{className:k.endsWith("_at")?"cell-muted":void 0,children:_},k)}),(y.includes("edit")||y.includes("delete"))&&I.jsx("td",{children:I.jsxs("div",{className:"actions",style:{margin:0,gap:6},children:[y.includes("edit")&&I.jsx("button",{type:"button",className:"btn secondary",style:{padding:"6px 10px"},onClick:()=>m(String(O.id)),children:x("edit","编辑")}),y.includes("delete")&&I.jsx("button",{type:"button",className:"btn secondary",style:{padding:"6px 10px"},onClick:()=>void P(String(O.id)),children:x("delete","删除")})]})})]},String(O.id??A)))})]})})]})}function hZ(e){var C,N;const{session:t,slug:n,resource:r,page:o,title:s,fields:i,editId:l,busy:c,setBusy:u,setError:d,setInfo:m,onDone:f}=e,p=(((C=o.layout)==null?void 0:C.form_fields)||i.map(S=>S.name)).filter(S=>!cZ.has(S)),y=((N=o.layout)==null?void 0:N.action_labels)||{},b=(S,E)=>y[S]||E,[x,v]=a.useState({}),[g,h]=a.useState({});a.useEffect(()=>{const S={};for(const E of p)S[E]="";v(S),(async()=>{var w,R;const E={};for(const P of p){const T=i.find(M=>M.name===P);T&&(w=T.enum_values)!=null&&w.length&&(E[P]=T.enum_values)}try{const T=(await ep(t,n,r)).items||[];for(const M of p){const z=i.find(F=>F.name===M);if(!z||!jf(z)||(R=E[M])!=null&&R.length)continue;const B=new Set;for(const F of T){const L=F[M];L!=null&&String(L).trim()!==""&&B.add(String(L))}B.size>0&&B.size<=50&&(E[M]=Array.from(B).sort())}if(l){const M=T.find(z=>String(z.id)===l);if(M){const z={};for(const B of p){const F=i.find(j=>j.name===B),L=M[B]==null?"":String(M[B]);F&&Bf(F)?z[B]=L:F&&B0(F)?z[B]=L0(L,"date"):z[B]=L}v(z)}}}catch(P){l&&d(P.message||String(P))}h(E)})()},[l,n,r]);async function $(){u(!0),d("");try{const S={};for(const E of p){const w=i.find(P=>P.name===E),R=x[E];if((w==null?void 0:w.type)==="int"||(w==null?void 0:w.type)==="bigint"||(w==null?void 0:w.type)==="decimal"?S[E]=R===""?null:Number(R):(w==null?void 0:w.type)==="boolean"?S[E]=R==="true":w&&Bf(w)||w&&B0(w)?S[E]=R===""?null:R:S[E]=R,jf(w||{name:E,label:E,type:"string"})&&!R)throw new Error(`请选择「${(w==null?void 0:w.label)||E}」`)}l?(await SY(t,n,r,l,S),m("已更新")):(await $Y(t,n,r,S),m("已创建")),f()}catch(S){d(dZ(S.message||String(S)))}finally{u(!1)}}return I.jsxs("section",{className:"panel gen-panel",style:{maxWidth:560},children:[I.jsx("h2",{className:"section-title",children:s}),I.jsx("div",{className:"row",children:p.map(S=>{const E=i.find(R=>R.name===S)||{name:S,label:S,type:"string"},w=g[S]||E.enum_values||[];return I.jsxs("label",{children:[E.label||S,I.jsx(fZ,{meta:E,value:x[S]||"",options:w,onChange:R=>v(P=>({...P,[S]:R}))})]},S)})}),I.jsxs("div",{className:"actions",children:[I.jsx("button",{type:"button",className:"btn",disabled:c,onClick:()=>void $(),children:l?b("save","保存"):b("create","创建")}),I.jsx("button",{type:"button",className:"btn secondary",disabled:c,onClick:f,children:b("cancel","取消")})]})]})}function yZ(e){var g,h,$,C,N,S,E,w,R,P;const{session:t,slug:n,resource:r,page:o,fieldMap:s={},setError:i}=e,l=((g=o.layout)==null?void 0:g.widgets)||[],[c,u]=a.useState(null),[d,m]=a.useState([]),f=((h=l.find(T=>T.type==="bar_chart"||T.type==="pie_chart"))==null?void 0:h.group_by)||(($=l.find(T=>T.group_by))==null?void 0:$.group_by)||"",p=((C=l.find(T=>T.metric&&T.metric!=="count"))==null?void 0:C.metric)||((S=(N=l.find(T=>{var M;return(M=T.metrics)==null?void 0:M.length}))==null?void 0:N.metrics)==null?void 0:S[0])||"",y=a.useMemo(()=>JSON.stringify(l),[(E=o.layout)==null?void 0:E.widgets]);if(a.useEffect(()=>{(async()=>{try{if(f){const M=await wY(t,n,r,f,p||void 0);u(M)}if(l.some(M=>["line_chart","table","status_strip"].includes(M.type))||!f){const M=await ep(t,n,r);m(M.items||[]),f||u({total:M.total||(M.items||[]).length})}}catch(T){i(T.message||String(T))}})()},[t,n,r,f,p,i,y]),!c)return I.jsx("div",{className:"panel gen-panel",children:I.jsx("div",{className:"empty",children:"加载看板…"})});function b(T){var M;return((M=s[T])==null?void 0:M.label)||T}function x(T){const M=T.x_field||T.group_by||"",z=(T.metrics&&T.metrics.length?T.metrics:T.metric&&T.metric!=="count"?[T.metric]:[]).filter(Boolean);if(!M||!z.length||!d.length)return null;const F=[...d].sort((j,O)=>String(j[M]??"").localeCompare(String(O[M]??""),"zh")).slice(0,100).map(j=>{const O={};for(const A of z){const k=Number(j[A]);Number.isFinite(k)&&(O[A]=k)}return{x:String(j[M]??""),values:O}}),L=z.map(j=>({key:j,label:b(j)}));return I.jsx(R6,{title:T.title||"趋势图",points:F,series:L,yUnit:T.y_unit||""},`line-${T.title}-${M}`)}function v(T,M){if(!M.filter_field)return!0;const z=T[M.filter_field],B=Number(z),F=M.filter_op||"gt",L=Number(M.filter_value??0);return F==="gt"?Number.isFinite(B)&&B>L:F==="gte"?Number.isFinite(B)&&B>=L:F==="lt"?Number.isFinite(B)&&BT.type==="kpi").length?l.filter(T=>T.type==="kpi"):[{type:"kpi",title:"记录数",metric:"count"}]).map((T,M)=>I.jsx(XJ,{title:T.title||"KPI",value:T.metric==="count"||!T.metric?c.total:Number(c.sum||0).toFixed(2)},`${T.title}-${M}`))}),l.filter(T=>T.type==="line_chart").map(T=>x(T)),l.filter(T=>T.type==="status_strip").map((T,M)=>{const z=T.label_field||"",B=T.value_field||"",F=T.secondary_field||"",L=T.warn_field||"",j=Number(T.cycle_days)||30,O=T.variant==="stacked"||T.variant==="stacked_days"||!!F;if(!z||!B)return null;const A=d.slice(0,48).map(k=>{let _=Number(k[B]??0),D=F?Number(k[F]??0):0;if(O&&_+D>50&&_+D<120){const V=Math.round(_/100*j);D=Math.max(0,j-V),_=V}return{label:String(k[z]??""),value:_,secondary:O?D:void 0,tone:L&&Number(k[L]??0)>0?"warn":"ok",stop:String(k.status||"").includes("停")||O&&_===0&&D===0}});return I.jsx(T6,{title:T.title||"状态条",variant:O?"stacked":"cells",items:A},`strip-${M}`)}),l.filter(T=>T.type==="table").map((T,M)=>{var F;const z=(F=T.columns)!=null&&F.length?T.columns:Object.keys(d[0]||{}).slice(0,6),B=d.filter(L=>v(L,T)).slice(0,50);return I.jsxs("div",{className:"chart-card",children:[I.jsx("h4",{children:T.title||"明细表"}),I.jsx("div",{className:"table-wrap",children:I.jsxs("table",{children:[I.jsx("thead",{children:I.jsx("tr",{children:z.map(L=>I.jsx("th",{children:b(L)},L))})}),I.jsxs("tbody",{children:[B.map((L,j)=>I.jsx("tr",{children:z.map(O=>I.jsx("td",{children:j6(O,L[O])},O))},j)),!B.length&&I.jsx("tr",{children:I.jsx("td",{colSpan:z.length,className:"empty",children:"暂无数据"})})]})]})})]},`tbl-${M}`)}),I.jsxs("div",{className:"chart-grid",children:[l.filter(T=>T.type==="bar_chart").map((T,M)=>I.jsx(gE,{title:T.title||"分布",buckets:c.buckets||[]},`bar-${M}`)),l.filter(T=>T.type==="pie_chart").map((T,M)=>I.jsx(YJ,{title:T.title||"占比",buckets:c.buckets||[]},`pie-${M}`)),!l.some(T=>T.type==="bar_chart"||T.type==="pie_chart")&&(((P=c.buckets)==null?void 0:P.length)||0)>0&&I.jsx(gE,{title:"分布",buckets:c.buckets||[]})]})]})}async function _g(e){const t=`${Lc}/api/v1/demo/file/${e.split("/").map(encodeURIComponent).join("/")}`,n=await fetch(t);if(!n.ok)return null;const r=await n.blob(),o=e.split("/").pop()||"file";return{blob:r,name:o}}function zg(e,t,n){return new File([e],t,{type:n||e.type||"application/octet-stream"})}async function vZ(){var d,m,f,p,y,b;const e=[],t=await fetch(`${Lc}/api/v1/demo/fixtures`);if(!t.ok)throw new Error(`无法读取测试素材清单 HTTP ${t.status}`);const n=await t.json();if(!n.available)throw new Error(`测试目录不可用:${n.root||"test/"}`);const o=await(await fetch(`${Lc}/api/v1/demo/prompt`)).json(),s=String(o.prompt||"");s||e.push(n.prompt||"prompt");const i=n.data||((m=(d=n.files)==null?void 0:d.data)==null?void 0:m.path);let l=null;if(i){const x=await _g(i);x?l=zg(x.blob,x.name,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"):e.push(i)}const c=[];for(const x of n.images||((p=(f=n.files)==null?void 0:f.images)==null?void 0:p.map(v=>v.path))||[]){const v=await _g(x);v?c.push(zg(v.blob,v.name,"image/png")):e.push(x)}const u=[];for(const x of n.layouts||((b=(y=n.files)==null?void 0:y.layouts)==null?void 0:b.map(v=>v.path))||[]){const v=await _g(x);v?u.push(zg(v.blob,v.name,"text/html")):e.push(x)}return{prompt:s,slug:n.slug||"settlement_observation_system",excel:l,images:c,layouts:u,label:n.label||"演示包",missing:e}}const{Header:bZ,Sider:xZ,Content:$Z}=Ds,SZ={platform:"平台管理",generate:"生成与发布",modules:"模块管理",sync:"数据同步",agent:"智能体胶囊",roles:"角色管理",agents:"智能体账号",orgs:"组织管理",invites:"邀请加入",members:"成员管理",audit:"审计"};function CZ({session:e,entitlements:t,tab:n,onTabChange:r,onOpenApp:o,onLogout:s,onSession:i,busy:l,banner:c,children:u}){const[d,m]=a.useState(!1),[f,p]=a.useState(!1),[y,b]=a.useState(!1),[x,v]=a.useState(!1),[g,h]=a.useState(!1),[$]=We.useForm(),[C]=We.useForm(),{token:N}=tX.useToken(),{message:S}=Lo.useApp(),E=a.useMemo(()=>{const w=[],R=Nr(e.role),P=(e.tenantId||0)>0;if(R&&!P)return w.push({key:"platform",icon:I.jsx(fE,{}),label:"平台管理"}),w;if(!P)return w;R&&w.push({key:"platform",icon:I.jsx(fE,{}),label:"平台管理"});const T=z=>dY(t,z),M=zl(e.role)||R;return(T("发布模块")||T("写入模块")||T("读取模块"))&&w.push({key:"generate",icon:I.jsx(TJ,{}),label:"生成与发布"}),T("读取模块")&&w.push({key:"modules",icon:I.jsx(EQ,{}),label:"模块管理"}),M&&T("数据同步")&&w.push({key:"sync",icon:I.jsx(BQ,{}),label:"数据同步"}),T("读取模块")&&w.push({key:"agent",icon:I.jsx(a6,{}),label:"智能体胶囊"}),T("管理智能体")&&(w.push({key:"roles",icon:I.jsx(_f,{}),label:"角色管理"}),w.push({key:"agents",icon:I.jsx(gJ,{}),label:"智能体账号"})),M&&T("管理组织")&&w.push({key:"orgs",icon:I.jsx(vQ,{}),label:"组织管理"}),M&&T("邀请成员")&&(w.push({key:"invites",icon:I.jsx(zJ,{}),label:"邀请加入"}),w.push({key:"members",icon:I.jsx(IJ,{}),label:"成员管理"})),T("查看审计")&&w.push({key:"audit",icon:I.jsx(RQ,{}),label:"审计"}),w},[e,t]);return I.jsxs(Ds,{className:"console-root",style:{minHeight:"100vh"},children:[I.jsxs(xZ,{collapsible:!0,collapsed:d,trigger:null,width:232,className:"console-sider",theme:"light",children:[I.jsxs("div",{className:`console-brand ${d?"is-collapsed":""}`,children:[I.jsx("span",{className:"console-brand-mark",children:"宇"}),!d&&I.jsxs("div",{className:"console-brand-text",children:[I.jsx("strong",{children:"宇信达智建"}),I.jsx("span",{children:Nr(e.role)?"平台超管工作台":"AI 建站控制台"})]})]}),I.jsx(Fi,{mode:"inline",selectedKeys:[n],items:E,onClick:({key:w})=>r(w),style:{borderInlineEnd:0,padding:"8px 8px 24px"}})]}),I.jsxs(Ds,{children:[I.jsxs(bZ,{className:"console-header",style:{background:N.colorBgContainer},children:[I.jsxs(Vt,{size:"middle",children:[I.jsx(Xe,{type:"text",icon:d?I.jsx(eJ,{}):I.jsx(YQ,{}),onClick:()=>m(w=>!w)}),I.jsx(ot.Title,{level:4,style:{margin:0},children:SZ[n]||"控制台"})]}),I.jsxs(Vt,{size:"middle",wrap:!0,children:[I.jsxs(Vt,{size:8,children:[I.jsx(f4,{size:"small",icon:I.jsx(kJ,{}),style:{background:N.colorPrimary}}),I.jsx("span",{className:"console-user",children:e.displayName||e.username}),e.username?I.jsx(ot.Text,{type:"secondary",code:!0,style:{fontSize:12},children:e.username}):null,e.phone?I.jsx(Yt,{icon:I.jsx(mE,{}),children:e.phone}):I.jsx(Yt,{children:"未绑手机"}),I.jsx(Yt,{color:"green",children:nc(e.role||"管理员")}),Nr(e.role)&&e.tenantId>0?I.jsxs(Yt,{color:"orange",children:["管理:",e.tenantName||`公司 #${e.tenantId}`]}):e.tenantId>0?I.jsxs(Yt,{children:["公司 #",e.tenantId]}):null]}),I.jsx(Xe,{icon:I.jsx(mE,{}),onClick:()=>{C.setFieldsValue({phone:e.phone||""}),v(!0)},children:e.phone?"更换手机":"绑定手机"}),I.jsx(Xe,{icon:I.jsx(_f,{}),onClick:()=>{$.resetFields(),p(!0)},children:"修改密码"}),I.jsx(Xe,{onClick:o,disabled:l,children:"打开模块"}),I.jsx(Xe,{icon:I.jsx(g6,{}),onClick:s,children:"退出"})]})]}),I.jsxs($Z,{className:"console-content",children:[c,I.jsx("div",{className:"console-panel",children:u})]})]}),I.jsx(Sn,{title:"修改密码",open:f,onCancel:()=>p(!1),confirmLoading:y,onOk:async()=>{const w=await $.validateFields();b(!0);try{await dQ(e,w.old_password,w.new_password),S.success("密码已更新"),p(!1),$.resetFields()}catch(R){S.error(R.message||String(R))}finally{b(!1)}},destroyOnHidden:!0,children:I.jsxs(We,{form:$,layout:"vertical",children:[I.jsxs(ot.Paragraph,{type:"secondary",children:["账号 ",I.jsx(ot.Text,{code:!0,children:e.username})," 为系统分配、不可更改;仅可修改密码。 绑定手机后也可用手机号 + 密码登录。"]}),I.jsx(We.Item,{name:"old_password",label:"当前密码",rules:[{required:!0,message:"请输入当前密码"}],children:I.jsx(Lt.Password,{autoComplete:"current-password"})}),I.jsx(We.Item,{name:"new_password",label:"新密码",rules:[{required:!0,message:"请输入新密码"},{min:6,message:"至少 6 位"}],children:I.jsx(Lt.Password,{autoComplete:"new-password"})}),I.jsx(We.Item,{name:"confirm",label:"确认新密码",dependencies:["new_password"],rules:[{required:!0,message:"请再次输入"},({getFieldValue:w})=>({validator(R,P){return!P||w("new_password")===P?Promise.resolve():Promise.reject(new Error("两次输入不一致"))}})],children:I.jsx(Lt.Password,{autoComplete:"new-password"})})]})}),I.jsx(Sn,{title:e.phone?"更换手机号":"绑定手机号",open:x,onCancel:()=>v(!1),confirmLoading:g,onOk:async()=>{const w=await C.validateFields();h(!0);try{const R=await fQ(e,(w.phone||"").trim()),P={...e,phone:R.phone||void 0};n6(P),i==null||i(P),S.success(R.phone?`已绑定 ${R.phone}`:"已解除绑定"),v(!1)}catch(R){S.error(R.message||String(R))}finally{h(!1)}},destroyOnHidden:!0,children:I.jsxs(We,{form:C,layout:"vertical",children:[I.jsx(ot.Paragraph,{type:"secondary",children:"绑定后可用手机号 + 密码登录。留空并保存可解除绑定。"}),I.jsx(We.Item,{name:"phone",label:"手机号",rules:[{validator:async(w,R)=>{const P=String(R||"").trim();if(P&&!/^1[3-9]\d{9}$/.test(P)&&!/^\+?86\s*1[3-9]\d{9}$/.test(P.replace(/\s/g,"")))throw new Error("请输入 11 位大陆手机号")}}],children:I.jsx(Lt,{placeholder:"例如 13800138000",maxLength:20,allowClear:!0})})]})})]})}function h$({children:e,subtitle:t}){return I.jsxs("div",{className:"auth-shell",children:[I.jsx("div",{className:"auth-backdrop","aria-hidden":!0}),I.jsxs(tr,{className:"auth-card",bordered:!1,children:[I.jsx(ot.Title,{level:2,className:"auth-brand",children:"宇信达智建"}),I.jsx(ot.Paragraph,{type:"secondary",style:{marginTop:-8},children:t}),e]})]})}function ro(e,t){var r;const n=(r=e.current)==null?void 0:r.input;return n&&typeof n.value=="string"?n.value:t}function wZ(e){const[t,n]=a.useState("account_password"),[r,o]=a.useState(""),[s,i]=a.useState(""),[l,c]=a.useState(0),[u,d]=a.useState(!1),m=a.useRef(null),f=a.useRef(null),p=a.useRef(null),y=a.useRef(null),b=a.useRef(null);a.useEffect(()=>{if(l<=0)return;const h=window.setTimeout(()=>c($=>$-1),1e3);return()=>window.clearTimeout(h)},[l]);const x=()=>{if(t==="phone_sms"){const C=ro(y,e.username).trim(),N=ro(b,r).trim();e.onUsername(C),e.onLogin({mode:t,phone:C,sms_code:N});return}if(t==="phone_password"){const C=ro(y,e.username).trim(),N=ro(f,e.password);e.onUsername(C),e.onPassword(N),e.onLogin({mode:t,phone:C,password:N});return}const h=ro(m,e.username).trim(),$=ro(f,e.password);e.onUsername(h),e.onPassword($),e.onLogin({mode:t,username:h,password:$})},v=()=>{const h=ro(m,e.username).trim()||ro(y,e.username).trim(),$=ro(f,e.password),C=ro(p,e.displayName).trim();e.onUsername(h),e.onPassword($),e.onDisplayName(C),e.onRegister(h,$,C)};async function g(){const h=ro(y,e.username).trim();if(!/^1[3-9]\d{9}$/.test(h)){i("请先填写正确的 11 位手机号");return}d(!0),i("");try{const $=await gY(h);c($.retry_after&&$.retry_after>0?$.retry_after:60),$.debug_code?(o($.debug_code),i(`开发模式验证码:${$.debug_code}(正式环境将发短信)`)):i($.message||"验证码已发送")}catch($){i($.message||String($))}finally{d(!1)}}return I.jsxs(h$,{subtitle:"描述 + Excel/CSV/JSON → 自动建库建 API → 生成可操作的前端页面",children:[I.jsx(tV,{block:!0,style:{marginBottom:16},value:t,onChange:h=>n(h),options:[{label:"账号密码",value:"account_password"},{label:"手机密码",value:"phone_password"},{label:"短信登录",value:"phone_sms"}]}),I.jsxs(We,{layout:"vertical",requiredMark:!1,onFinish:x,children:[t==="account_password"?I.jsx(We.Item,{label:"用户名",children:I.jsx(Lt,{ref:m,size:"large",value:e.username,onChange:h=>e.onUsername(h.target.value),autoComplete:"username",placeholder:"系统分配的随机用户名",allowClear:!0})}):I.jsx(We.Item,{label:"手机号",children:I.jsx(Lt,{ref:y,size:"large",value:e.username,onChange:h=>e.onUsername(h.target.value),autoComplete:"tel",placeholder:"已绑定的 11 位手机号",allowClear:!0})}),t==="phone_sms"?I.jsx(We.Item,{label:"短信验证码",children:I.jsxs(Vt.Compact,{style:{width:"100%"},children:[I.jsx(Lt,{ref:b,size:"large",value:r,onChange:h=>o(h.target.value),placeholder:"6 位验证码",maxLength:8}),I.jsx(Xe,{size:"large",loading:u,disabled:l>0||e.busy,onClick:()=>void g(),children:l>0?`${l}s`:"获取验证码"})]})}):I.jsx(We.Item,{label:"密码",children:I.jsx(Lt.Password,{ref:f,size:"large",value:e.password,onChange:h=>e.onPassword(h.target.value),autoComplete:"current-password"})}),t==="account_password"?I.jsx(We.Item,{label:"显示名(仅注册用,登录可忽略)",children:I.jsx(Lt,{ref:p,size:"large",value:e.displayName,onChange:h=>e.onDisplayName(h.target.value)})}):null,e.error?I.jsx(Mo,{type:"error",showIcon:!0,message:e.error,style:{marginBottom:16}}):null,s?I.jsx(Mo,{type:"info",showIcon:!0,message:s,style:{marginBottom:16}}):null,e.info&&!e.info.startsWith("已填入测试默认")?I.jsx(Mo,{type:"success",showIcon:!0,message:e.info,style:{marginBottom:16}}):null,t==="account_password"?I.jsxs(Vt,{wrap:!0,style:{marginBottom:12},children:[I.jsx(Xe,{type:"link",size:"small",onClick:()=>{e.onUsername("demo"),e.onPassword("demo123"),e.onDisplayName("演示用户")},children:"填入 demo"}),I.jsx(Xe,{type:"link",size:"small",onClick:()=>{e.onUsername("ljk_admin"),e.onPassword("ljk_admin"),e.onDisplayName("平台超级管理员")},children:"填入 ljk_admin"})]}):null,I.jsxs("div",{className:"auth-actions",children:[I.jsx(Xe,{type:"primary",htmlType:"submit",size:"large",block:!0,loading:e.busy,children:"登录"}),t==="account_password"?I.jsx(Xe,{htmlType:"button",size:"large",block:!0,disabled:e.busy,onClick:v,children:"注册"}):null]}),I.jsxs(ot.Paragraph,{type:"secondary",style:{marginTop:20,marginBottom:0,fontSize:13},children:["支持三种登录:用户名+密码、手机号+密码、手机号+短信验证码。",I.jsx("br",{}),"短信登录前请先在控制台「绑定手机」。开发环境验证码会直接显示在页面上。"]})]})]})}function EZ(e){return I.jsxs(h$,{subtitle:"账号已注册,但尚未加入任何公司。未入驻前不能查看未公布数据与业务页面。",children:[I.jsxs(ot.Text,{type:"secondary",children:[e.displayLabel," · pending"]}),e.error&&I.jsx(Mo,{type:"error",showIcon:!0,message:e.error,style:{margin:"16px 0"}}),e.info&&I.jsx(Mo,{type:"success",showIcon:!0,message:e.info,style:{margin:"16px 0"}}),I.jsxs(We,{layout:"vertical",style:{marginTop:16},requiredMark:!1,children:[I.jsx(We.Item,{label:"邀请码",children:I.jsx(Lt,{size:"large",value:e.inviteCode,onChange:t=>e.onInviteCode(t.target.value),placeholder:"粘贴公司管理员发来的邀请码"})}),I.jsx(Xe,{type:"primary",size:"large",block:!0,loading:e.busy,disabled:!e.inviteCode.trim(),onClick:e.onAcceptInvite,children:"加入已有公司"}),I.jsx(H9,{children:"或"}),I.jsx(We.Item,{label:"公司名称",children:I.jsx(Lt,{size:"large",value:e.companyName,onChange:t=>e.onCompanyName(t.target.value),placeholder:"创建自己的公司(成为管理员)"})}),I.jsxs(Vt,{direction:"vertical",style:{width:"100%"},children:[I.jsx(Xe,{size:"large",block:!0,loading:e.busy,onClick:e.onCreateCompany,children:"创建公司"}),I.jsx(Xe,{size:"large",block:!0,onClick:e.onLogout,children:"退出"})]})]})]})}function IZ(e){return I.jsx(h$,{subtitle:e.message,children:e.error?I.jsx(Mo,{type:"error",showIcon:!0,message:e.error}):null})}function jg(){const t=(window.location.hash||"").replace(/^#/,"").match(/^\/?app\/([a-zA-Z][a-zA-Z0-9_]{0,63})\/?$/);return t?t[1]:null}function PZ(e){const t=`#/app/${encodeURIComponent(e)}`;window.location.hash===t?window.dispatchEvent(new Event("hashchange")):window.location.hash=t}function ed(){!window.location.hash||window.location.hash==="#/"||window.location.hash==="#"||(window.location.hash="#/")}function ia(e,t){if(e)try{const n=new DataTransfer;for(const r of t)n.items.add(r);e.files=n.files}catch{}}function NZ(){var Ie,Be;const[e,t]=a.useState("generate"),[n,r]=a.useState("console"),[o,s]=a.useState(null),[i,l]=a.useState(""),[c,u]=a.useState(""),[d,m]=a.useState("demo"),[f,p]=a.useState("demo123"),[y,b]=a.useState("演示用户"),[x,v]=a.useState(""),[g,h]=a.useState(""),[$,C]=a.useState(""),[N,S]=a.useState(null),[E,w]=a.useState([]),[R,P]=a.useState([]),T=a.useRef(null),M=a.useRef(null),z=a.useRef(null),[B,F]=a.useState("schema_per_app"),[L,j]=a.useState("deepseek"),[O,A]=a.useState("deepseek-chat"),[k,_]=a.useState([]),[D,V]=a.useState(null),[W,K]=a.useState([]),[q,Y]=a.useState(null),[ee,ie]=a.useState(null),[ae,U]=a.useState("myapp"),[Q,Z]=a.useState("__new__"),[ne,oe]=a.useState([]),[le,re]=a.useState(null),[X,se]=a.useState("records"),[ge,de]=a.useState(""),[Se,ue]=a.useState(!0),[be,Ne]=a.useState(!1),[we,ze]=a.useState(!1),[he,ke]=a.useState([]),[Oe,Ce]=a.useState(null),[Me,xe]=a.useState(!1),[Ee,Ve]=a.useState("");async function qe(te=!1){if(!Og()&&!o){te||l("请先登录后再填入测试默认");return}Ne(!0),te||l("");try{const ye=await vZ();C(ye.prompt),S(ye.excel),w(ye.images),P(ye.layouts),U(ye.slug),xe(!0),ia(T.current,ye.excel?[ye.excel]:[]),ia(M.current,ye.images),ia(z.current,ye.layouts);const Ae=[ye.excel?ye.excel.name:null,ye.images.length?`${ye.images.length} 张截图`:null,ye.layouts.length?`${ye.layouts.length} 个 HTML`:null].filter(Boolean);Ve(Ae.join(" · ")||"仅需求文本");const Je=ye.missing.length?`(缺:${ye.missing.join(", ")})`:"";u(`已填入测试默认:${ye.label}${Je}`)}catch(ye){te||l(ye.message||String(ye))}finally{Ne(!1)}}a.useEffect(()=>{const te=Og();te&&s(te);const ye=()=>{s(null),!(Ju()||jg())&&(l("登录已失效,请重新登录"),ed(),r("console"),re(null))};return window.addEventListener("ajz:session-expired",ye),yY().then(Ae=>{var ht,Nt,yt;_(Ae.providers||[]);const Je=Ae.default||"deepseek",St=(Ae.providers||[]).find(at=>at.id===Je)||((ht=Ae.providers)==null?void 0:ht[0]);St&&(j(St.id),A(St.default_model||((yt=(Nt=St.models)==null?void 0:Nt[0])==null?void 0:yt.id)||""))}).catch(()=>{}),()=>window.removeEventListener("ajz:session-expired",ye)},[]),a.useEffect(()=>{!(o!=null&&o.accessToken)||Mg(o)||Nr(o.role)&&!(o.tenantId>0)||localStorage.getItem("ajz_skip_demo_autoload")==="1"||Me||qe(!0).catch(()=>{})},[o==null?void 0:o.accessToken,o==null?void 0:o.tenantId]),a.useEffect(()=>{if(!(o!=null&&o.accessToken)||Mg(o)){oe([]),Ce(null);return}if(Nr(o.role)&&!(o.tenantId>0)){t("platform"),oe([]),Ce(null);return}Rd(o).then(te=>oe(te.items||[])).catch(()=>oe([])),o.tenantId>0?o6(o).then(te=>Ce(te.permissions||[])).catch(()=>Ce(Nr(o.role)?null:[])):Ce(null)},[o]),a.useEffect(()=>{let te=!1;async function ye(){var St,ht,Nt,yt,at,Ze;const Ae=Ju();if(Ae){te||(Ne(!0),l(""));try{const De=await lY(Ae);if(te)return;re(De.blueprint),V(De.blueprint),U(((ht=(St=De.blueprint)==null?void 0:St.meta)==null?void 0:ht.slug)||"preview"),se(De.resource||"records"),r("app")}catch(De){te||(Tg(),l(De.message||String(De)),r("console"),re(null),ed())}finally{te||Ne(!1)}return}Tg();const Je=jg();if(!Je){te||(r("console"),re(null));return}te||(Ne(!0),l(""),U(Je));try{const De=o||Og(),Le=await cE(De,Je);if(te)return;re(Le),V(Le);const Ke=((Ze=(at=(yt=(Nt=Le==null?void 0:Le.apis)==null?void 0:Nt.resources)==null?void 0:yt[0])==null?void 0:at.path)==null?void 0:Ze.replace(/^\//,""))||X;se(Ke),r("app")}catch(De){te||(l(De.message||String(De)),r("console"),re(null),ed())}finally{te||Ne(!1)}}return ye(),window.addEventListener("hashchange",ye),()=>{te=!0,window.removeEventListener("hashchange",ye)}},[o]),a.useEffect(()=>{!Me&&!N&&!E.length&&!R.length||(ia(T.current,N?[N]:[]),ia(M.current,E),ia(z.current,R))},[Me,N,E,R,e,o,n]);function me(te){s(te),n6(te)}async function Re(te){ze(!0),l(""),u("");try{const ye=te.mode==="phone_sms"?{phone:String(te.phone||"").trim(),sms_code:String(te.sms_code||"").trim()}:te.mode==="phone_password"?{phone:String(te.phone||"").trim(),password:String(te.password||"")}:{username:String(te.username||"").trim(),password:String(te.password||"")},Ae=await pY(ye);(Nr(Ae.role)||Ae.username==="ljk_admin")&&t("platform"),me(Ae),u(`已登录 ${Ae.displayName||Ae.username}(${Ae.role||"用户"})`)}catch(ye){l(ye.message||String(ye))}finally{ze(!1)}}async function Te(te=d,ye=f,Ae=y){ze(!0),l(""),u("");try{const Je=await hY(String(te||"").trim(),String(ye||""),String(Ae||"").trim());me(Je),u("注册成功:待加入公司(pending)。请输入邀请码,或创建自己的公司。")}catch(Je){l(Je.message||String(Je))}finally{ze(!1)}}async function Ue(){if(o){ze(!0),l("");try{const te=await BY(o,x.trim());me(te),u(te.displayName?`已加入租户 #${te.tenantId}`:`已加入租户 #${te.tenantId}`),v("")}catch(te){l(te.message||String(te))}finally{ze(!1)}}}async function Ge(){if(o){ze(!0),l("");try{const te=await LY(o,g.trim()||y||d);me(te),u(`已创建公司(租户 #${te.tenantId}),你是管理员`),h("")}catch(te){l(te.message||String(te))}finally{ze(!1)}}}async function Fe(){var te,ye,Ae,Je,St,ht,Nt,yt,at,Ze;Ne(!0),l(""),Y(null),ie(null);try{const De=await vY($,N,E,B,L,O,R);V(De.draft),K(De.warnings||[]),Y(De.fidelity||null),ie(De.generate_log||null),(ye=(te=De.draft)==null?void 0:te.meta)!=null&&ye.slug&&U(De.draft.meta.slug);const Le=(Nt=(ht=(St=(Je=(Ae=De.draft)==null?void 0:Ae.apis)==null?void 0:Je.resources)==null?void 0:St[0])==null?void 0:ht.path)==null?void 0:Nt.replace(/^\//,"");Le&&se(Le);const Ke=De.fidelity;let lt="";Ke&&!Ke.skipped&&(lt=Ke.passed?`还原度达标 ${Ke.final_score}%(目标 ${Ke.target}%)。`:`还原度 ${Ke.final_score??"—"}%(目标 ${Ke.target}%),请查看差异后继续微调或再生成。`);const _t=(yt=De.generate_log)!=null&&yt.run_id?` run=${De.generate_log.run_id}`:"";let ft="";if(o&&((Ze=(at=De.draft)==null?void 0:at.meta)!=null&&Ze.slug))try{const xt=String(De.draft.meta.slug).trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"").slice(0,48);if(xt){await bY(o,xt,De.draft);const jt=await Rd(o);oe(jt.items||[]),Z("__new__"),ft=`已登记为在建模块「${xt}」。`}}catch(xt){ft=`(在建登记未成功:${xt.message||xt})`}u(`草稿置信度 ${De.confidence}。${lt}${ft}确认发布后将打开模块页。${_t}`)}catch(De){l(De.message||String(De))}finally{Ne(!1)}}function et(te){U(te),PZ(te)}async function ve(){var te,ye,Ae,Je,St,ht,Nt,yt;if(!(!o||!D)){Ne(!0),l("");try{const at=JSON.parse(JSON.stringify(D)),Ze=Q!=="__new__";let De=Ze?Q:String(((te=at==null?void 0:at.meta)==null?void 0:te.slug)||"app").trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");Ze||((!De||!/^[a-z]/.test(De))&&(De=`app_${De||"draft"}`.replace(/_+/g,"_")),De=De.slice(0,48).replace(/_+$/g,"")||"app");const Le=Ze?ne.find(pt=>pt.slug===De):null;at.meta={...at.meta||{},slug:De,name:(Le==null?void 0:Le.name)||((ye=at.meta)==null?void 0:ye.name)||De},at.meta.name||(at.meta.name=De),at.version||(at.version="1.0"),at.storage||(at.storage={mode:"schema_per_app",engine:"postgres"}),at.security||(at.security={visibility:"private",roles:[],row_policies:[]}),at.apis&&(at.apis={...at.apis,base_path:`/api/v1/apps/${De}`});const Ke=pt=>String(pt||"").replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().replace(/[^a-z0-9_]+/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,""),lt={};for(const pt of at.entities||[]){const qt=String(pt.name||"");pt.name&&(pt.name=Ke(pt.name)||"record"),pt.table&&(pt.table=Ke(pt.table)||pt.name),qt&&(lt[qt]=pt.name,lt[Ke(qt)]=pt.name);for(const cn of pt.fields||[]){cn.name&&(cn.name=Ke(String(cn.name).replace(/\./g,"_"))||"field");const hn=String(cn.type||"string").toLowerCase();["float","float64","double","number","numeric"].includes(hn)?cn.type="decimal":["integer","int32"].includes(hn)?cn.type="int":["int64","long"].includes(hn)?cn.type="bigint":hn==="bool"?cn.type="boolean":["varchar","str"].includes(hn)&&(cn.type="string")}}for(const pt of((Ae=at.apis)==null?void 0:Ae.resources)||[]){const qt=String(pt.entity||"");pt.entity=lt[qt]||lt[Ke(qt)]||Ke(qt)||pt.entity,pt.path&&(pt.path="/"+(Ke(String(pt.path).replace(/^\//,""))||"items"))}for(const pt of at.pages||[]){const qt=String(pt.entity||"");pt.entity=lt[qt]||lt[Ke(qt)]||Ke(qt)||pt.entity}const _t=((Nt=(ht=(St=(Je=at==null?void 0:at.apis)==null?void 0:Je.resources)==null?void 0:St[0])==null?void 0:ht.path)==null?void 0:Nt.replace(/^\//,""))||X;se(_t);const xt=await xY(o,De,at,Ze?"add_pages":"create");V(at),U(De);try{const pt=await Rd(o);oe(pt.items||[])}catch{}const jt=xt.publish_mode==="pages_added"||xt.publish_mode==="merged"?`已发布新页面${(yt=xt.added_pages)!=null&&yt.length?`(${xt.added_pages.join(", ")})`:""}`:"已新建模块";u(`${jt} ${De} → ${xt.schema_name}。正在跳转模块页…`),et(De)}catch(at){l(at.message||String(at))}finally{Ne(!1)}}}function je(){if(!o)return;if(l(""),!ae.trim()){l("请填写已发布模块 slug");return}const te=ne.find(ye=>ye.slug===ae.trim());if(te&&te.status!=="published"){l(`模块「${ae}」状态为${te.status_label||te.status},请先发布后再打开;可在「模块管理」继续编辑`);return}u(`正在跳转模块:/#/app/${ae}`),et(ae.trim())}async function ce(){if(o){Ne(!0),l("");try{const te=await EY(o,ae);de(te.capsule),u(te.hint),t("agent")}catch(te){l(te.message||String(te))}finally{Ne(!1)}}}if(n==="app"&&le)return I.jsx(pZ,{session:o,blueprint:le,onBack:()=>{Tg(),ed()}});const Pe=async te=>{if(t(te),l(""),te==="audit"&&o)try{const ye=await NY(o);ke(ye.items||[])}catch(ye){l(ye.message||String(ye))}};if((Ju()||jg())&&!le)return I.jsx(IZ,{message:`正在打开${Ju()?"草稿预览":"模块"}…`,error:i||void 0});if(!o)return I.jsx(wZ,{username:d,password:f,displayName:y,busy:we,error:i,info:c,onUsername:m,onPassword:p,onDisplayName:b,onLogin:Re,onRegister:Te});if(Mg(o))return I.jsx(EZ,{displayLabel:o.displayName||o.username||"",inviteCode:x,companyName:g,busy:we,error:i,info:c,onInviteCode:v,onCompanyName:h,onAcceptInvite:Ue,onCreateCompany:Ge,onLogout:()=>{Oa(),s(null)}});const $e=(k.length?k:[{id:"deepseek",label:"DeepSeek",configured:!0,default_model:"",models:[]},{id:"minimax",label:"MiniMax",configured:!0,default_model:"",models:[]},{id:"heuristic",label:"本地启发式",configured:!0,default_model:"",models:[]}]).map(te=>({value:te.id,label:`${te.label}${te.configured===!1?"(未配置 Key)":""}`})),_e=(((Ie=k.find(te=>te.id===L))==null?void 0:Ie.models)||[]).map(te=>({value:te.id,label:te.label}));return!_e.length&&O&&_e.push({value:O,label:O||"默认"}),I.jsxs(CZ,{session:o,entitlements:Oe,tab:e,busy:be,onTabChange:Pe,onOpenApp:je,onSession:te=>s(te),onLogout:()=>{Oa(),s(null)},banner:I.jsxs(I.Fragment,{children:[i?I.jsx(Mo,{type:"error",showIcon:!0,closable:!0,message:i,onClose:()=>l(""),style:{marginBottom:12}}):null,c?I.jsx(Mo,{type:"success",showIcon:!0,closable:!0,message:c,onClose:()=>u(""),style:{marginBottom:12}}):null]}),children:[e==="generate"&&I.jsxs(Nv,{gutter:[16,16],children:[I.jsx(si,{xs:24,lg:14,children:I.jsx(tr,{title:"需求与素材",extra:I.jsxs(Vt,{children:[I.jsx(Xe,{disabled:be,onClick:()=>qe(!1),children:"填入测试默认"}),Me&&I.jsxs(ot.Text,{type:"secondary",children:["已加载:",Ee]})]}),children:I.jsxs(We,{layout:"vertical",requiredMark:!1,children:[I.jsx(We.Item,{label:"需求描述",extra:"可写业务要求、按键,以及截图关系。上传截图后,未点名修改的部分按原图还原。",children:I.jsx(Lt.TextArea,{rows:6,placeholder:`示例: +两张截图为同一界面:一张展开监督条,一张为收起态;以展开态为准还原。 +平台抬头:…… +操作:新增、刷新、导入 Excel、导出 Excel`,value:$,onChange:te=>C(te.target.value)})}),I.jsx(We.Item,{label:"数据文件(xlsx / csv / json)",extra:N?`当前:${N.name}`:void 0,children:I.jsx("input",{ref:T,type:"file",accept:".xlsx,.xls,.csv,.json",onChange:te=>{var ye;return S(((ye=te.target.files)==null?void 0:ye[0])||null)}})}),I.jsx(We.Item,{label:"界面截图",extra:E.length?`当前:${E.map(te=>te.name).join("、")}`:"上传后自动走还原闭环",children:I.jsx("input",{ref:M,type:"file",accept:"image/*",multiple:!0,onChange:te=>w(Array.from(te.target.files||[]))})}),I.jsx(We.Item,{label:"页面源码(Ctrl+S 另存为)",extra:R.length?`当前:${R.map(te=>te.name).join("、")}`:"支持 .html / .htm / .mhtml",children:I.jsx("input",{ref:z,type:"file",accept:".html,.htm,.mhtml,.mht,.xhtml,text/html,multipart/related",multiple:!0,onChange:te=>P(Array.from(te.target.files||[]))})}),I.jsxs(Nv,{gutter:12,children:[I.jsx(si,{xs:24,md:8,children:I.jsx(We.Item,{label:"大模型",children:I.jsx(dn,{value:L,options:$e,onChange:te=>{var Ae,Je;j(te);const ye=k.find(St=>St.id===te);A((ye==null?void 0:ye.default_model)||((Je=(Ae=ye==null?void 0:ye.models)==null?void 0:Ae[0])==null?void 0:Je.id)||"")}})})}),I.jsx(si,{xs:24,md:8,children:I.jsx(We.Item,{label:"模型",children:I.jsx(dn,{value:O,options:_e,onChange:A})})}),I.jsx(si,{xs:24,md:8,children:I.jsx(We.Item,{label:"存储模式",children:I.jsx(dn,{value:B,onChange:te=>F(te),options:[{value:"schema_per_app",label:"共享库 · schema"},{value:"database_per_app",label:"独立库"}]})})})]}),I.jsx(We.Item,{label:"发布目标",children:I.jsx(dn,{value:Q,onChange:te=>{Z(te),te!=="__new__"&&U(te)},options:[{value:"__new__",label:"新建模块(草稿 slug)"},...ne.map(te=>({value:te.slug,label:`${te.name||te.slug}(${te.slug} · ${te.status_label||te.status} · ${te.page_count} 页)`}))]})}),I.jsx(ot.Paragraph,{type:"secondary",style:{marginTop:-8},children:Q==="__new__"?"将创建新模块;slug 取自蓝图 meta.slug。不同用户可访问的模块由授权决定。":"将把草稿里新生成的页面发布到该模块(page id / route 不可与现有重复)。"}),I.jsx(We.Item,{label:"已发布 slug(打开模块用)",children:I.jsx(Lt,{value:ae,onChange:te=>U(te.target.value)})}),I.jsx(We.Item,{label:"持久化上传(对象存储)",children:I.jsx("input",{type:"file",onChange:async te=>{var Ae;const ye=(Ae=te.target.files)==null?void 0:Ae[0];if(!(!ye||!o))try{const Je=await pQ(o,ye);u(`已上传 ${Je.filename} → ${Je.url}`)}catch(Je){l(Je.message||String(Je))}}})}),I.jsxs(Vt,{wrap:!0,children:[I.jsx(Xe,{type:"primary",loading:be,onClick:Fe,children:"生成蓝图草稿"}),I.jsx(Xe,{disabled:be||!D,onClick:ve,children:Q==="__new__"?"确认发布(新建模块)":"确认发布(新页面)"}),I.jsx(Xe,{disabled:be,onClick:je,children:"打开已有模块"})]}),W.length>0&&I.jsx(Mo,{style:{marginTop:16},type:"warning",showIcon:!0,message:"生成警告",description:I.jsx("ul",{style:{margin:0,paddingLeft:18},children:W.map(te=>I.jsx("li",{children:te},te))})}),q&&!q.skipped&&I.jsx(tr,{size:"small",style:{marginTop:16},title:`还原度迭代 ${q.passed?"已达标":"未达标"} · ${q.final_score??"—"}% / 目标 ${q.target}%`,children:I.jsx("ul",{style:{margin:0,paddingLeft:18},children:(q.rounds||[]).map(te=>I.jsxs("li",{children:["第",te.round,"轮:",te.score??"—","%",te.pass?" ✓":"",(te.fails||[]).length?` · 差异:${(te.fails||[]).slice(0,5).join(";")}${(te.fails||[]).length>5?"…":""}`:""]},te.round))})}),((Be=ee==null?void 0:ee.lines)==null?void 0:Be.length)>0&&I.jsxs(tr,{size:"small",style:{marginTop:16},title:`生成日志 · ${ee.run_id||"—"}`,children:[I.jsxs(ot.Paragraph,{type:"secondary",style:{marginBottom:8},children:["服务端同步写入 .runtime/logs/generate/",ee.log_file?`(本次:${String(ee.log_file).replace(/^.*[\\/]/,"")})`:""]}),I.jsx("pre",{className:"generate-log-body",children:ee.lines.map(te=>`${(te.level||"info").toUpperCase().padEnd(7)} ${te.message||""}`).join(` +`)})]})]})})}),I.jsx(si,{xs:24,lg:10,children:I.jsxs(tr,{title:"蓝图 JSON",extra:I.jsx(ot.Text,{type:"secondary",children:"可微调后发布"}),children:[I.jsx(ot.Paragraph,{type:"secondary",children:"含 entities / apis / pages。发布后 pages 会渲染成真正的业务前端。"}),I.jsx(Lt.TextArea,{className:"json-editor",rows:22,value:D?JSON.stringify(D,null,2):"",onChange:te=>{try{V(JSON.parse(te.target.value))}catch{}},placeholder:"生成后在此显示蓝图…"})]})})]}),e==="agent"&&I.jsxs(tr,{title:"智能体胶囊",children:[I.jsx(ot.Paragraph,{type:"secondary",children:"前端不展示明文 API 契约,只展示加密胶囊;智能体用 agent_key 解密后请求。"}),I.jsxs(We,{layout:"vertical",requiredMark:!1,style:{maxWidth:480},children:[I.jsx(We.Item,{label:"模块 slug",children:I.jsx(Lt,{value:ae,onChange:te=>U(te.target.value)})}),I.jsxs(Vt,{wrap:!0,children:[I.jsx(Xe,{type:"primary",loading:be,onClick:ce,children:"生成加密胶囊"}),I.jsx(Xe,{onClick:()=>ue(te=>!te),children:Se?"临时查看 agent_key":"隐藏 agent_key"})]})]}),!Se&&I.jsx("pre",{className:"mono-block",children:o.agentKey}),ge&&I.jsxs("div",{style:{marginTop:16},children:[I.jsx("pre",{className:"mono-block",children:ge}),I.jsx(Xe,{style:{marginTop:8},onClick:()=>navigator.clipboard.writeText(ge),children:"复制密文"})]})]}),e==="modules"&&o&&I.jsx(FJ,{session:o,onOpenPublished:te=>{U(te),et(te)},onContinueDraft:async te=>{Ne(!0),l("");try{const ye=await cE(o,te);V(ye),U(te),Z(te),t("generate"),u(`已加载在建模块「${te}」,可继续编辑后发布`)}catch(ye){l(ye.message||String(ye))}finally{Ne(!1)}}}),e==="sync"&&o&&(zl(o.role)||Nr(o.role)&&o.tenantId>0)&&I.jsx(UJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="roles"&&o&&I.jsx(WJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="agents"&&o&&I.jsx(AJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="platform"&&o&&Nr(o.role)&&I.jsx(qJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u,onSession:me,onEnteredCompany:()=>t("members")}),e==="orgs"&&o&&(zl(o.role)||Nr(o.role)&&o.tenantId>0)&&I.jsx(HJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="invites"&&o&&(zl(o.role)||Nr(o.role)&&o.tenantId>0)&&I.jsx(DJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="members"&&o&&(zl(o.role)||Nr(o.role)&&o.tenantId>0)&&I.jsx(GJ,{session:o,busy:be,setBusy:Ne,setError:l,setInfo:u}),e==="audit"&&I.jsx(tr,{title:"审计",children:I.jsx(Vn,{rowKey:"id",size:"middle",pagination:{pageSize:20},dataSource:he,locale:{emptyText:"暂无审计记录"},columns:[{title:"时间",dataIndex:"created_at",width:180,render:te=>String(te)},{title:"用户",dataIndex:"user_id",width:120},{title:"动作",dataIndex:"action",width:140},{title:"详情",dataIndex:"detail",ellipsis:!0}]})})]})}Bg.createRoot(document.getElementById("root")).render(I.jsx(J.StrictMode,{children:I.jsx(to,{locale:sY,theme:{token:{colorPrimary:"#0f5c45",colorInfo:"#0f5c45",colorSuccess:"#0f5c45",colorWarning:"#c45c26",colorError:"#b42318",borderRadius:8,fontFamily:'"Manrope", "Segoe UI", sans-serif',colorBgLayout:"#eef3f0",colorBgContainer:"#ffffff",controlHeight:36},components:{Layout:{siderBg:"#ffffff",headerBg:"#ffffff",bodyBg:"#eef3f0"},Menu:{itemBorderRadius:8,itemMarginInline:8},Card:{borderRadiusLG:12}}},children:I.jsx(Lo,{children:I.jsx(NZ,{})})})})); diff --git a/web/dist/index.html b/web/dist/index.html new file mode 100644 index 0000000..1ded4b8 --- /dev/null +++ b/web/dist/index.html @@ -0,0 +1,16 @@ + + + + + + 宇信达智建 + + + + + + + +
+ + diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..9352d91 --- /dev/null +++ b/web/index.html @@ -0,0 +1,15 @@ + + + + + + 宇信达智建 + + + + + +
+ + + diff --git a/web/nginx.conf b/web/nginx.conf new file mode 100644 index 0000000..cb9f965 --- /dev/null +++ b/web/nginx.conf @@ -0,0 +1,34 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://gateway:8180; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Authorization $http_authorization; + client_max_body_size 64m; + } + + location /ai/ { + proxy_pass http://gateway:8180; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Authorization $http_authorization; + client_max_body_size 64m; + } + + location /gateway/ { + proxy_pass http://gateway:8180; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..3125377 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,2685 @@ +{ + "name": "aijianzhan-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aijianzhan-web", + "version": "0.1.0", + "dependencies": { + "@ant-design/icons": "^6.3.2", + "antd": "^6.5.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.3.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.3.2.tgz", + "integrity": "sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.5.0", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz", + "integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-6.0.0.tgz", + "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/@rc-component/cascader/-/cascader-1.17.0.tgz", + "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-2.0.2.tgz", + "integrity": "sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/@rc-component/dialog/-/dialog-1.10.0.tgz", + "integrity": "sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rc-component/dropdown/-/dropdown-1.0.3.tgz", + "integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.5", + "resolved": "https://registry.npmmirror.com/@rc-component/form/-/form-1.8.5.tgz", + "integrity": "sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^6.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@rc-component/image/-/image-1.9.0.tgz", + "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/@rc-component/input/-/input-1.3.1.tgz", + "integrity": "sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==", + "license": "MIT", + "dependencies": { + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mentions/-/mentions-1.10.0.tgz", + "integrity": "sha512-CI1njYUVY0NjHtLhNoVmXlJyy568Sfep9Wsak6vmGjtT6uazx98djGYlCXz2xkHhEm73g91Y3MTvzUyE5avI7w==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.3.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/@rc-component/menu/-/menu-1.4.1.tgz", + "integrity": "sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@rc-component/motion/-/motion-1.3.3.tgz", + "integrity": "sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@rc-component/notification/-/notification-2.0.7.tgz", + "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/overflow/-/overflow-1.0.1.tgz", + "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/pagination/-/pagination-1.4.0.tgz", + "integrity": "sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/@rc-component/picker/-/picker-1.11.0.tgz", + "integrity": "sha512-6qXGKtoJvO8sUd17m5cyNEbEJub0zflCHnaZTBBmj63DPRZYc0WEHN8rp6hFSl+yMCJS/dJY5G+1fQ8bLCuD7A==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-2.2.1.tgz", + "integrity": "sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-2.0.0.tgz", + "integrity": "sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/@rc-component/select/-/select-1.8.2.tgz", + "integrity": "sha512-HQ9zuYqjfZTlcEMWlU1GAPBajd2OHIMVHyjZSGVTCVARwkfCgvXZMTEn0cduy3L+ejAKkaZluOQvxovZoaJaQw==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/slider/-/slider-1.1.1.tgz", + "integrity": "sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.10.4", + "resolved": "https://registry.npmmirror.com/@rc-component/table/-/table-1.10.4.tgz", + "integrity": "sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tabs/-/tabs-1.11.0.tgz", + "integrity": "sha512-hA/drZYOVa/MMIb4M2fWf3yaTyTG4qVuIABmghvEhyfw2nBob5VTH69lMCDjSVKmgODjO6nWlCV+gVn3xBrj5Q==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.4.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-2.4.0.tgz", + "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@rc-component/tree/-/tree-1.3.2.tgz", + "integrity": "sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tree-select/-/tree-select-1.11.0.tgz", + "integrity": "sha512-EhS0X0wtUhBfK4S5TlpSY3MR9ndPMGgujtt1PJW3Ej+ToAlnS/6ohYURtCoXBYGqazUwHmgQGVUDsfpVwhWPkg==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.8.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-3.10.1.tgz", + "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.3.3", + "@rc-component/portal": "^2.2.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/upload/-/upload-1.1.1.tgz", + "integrity": "sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.12.0", + "resolved": "https://registry.npmmirror.com/@rc-component/util/-/util-1.12.0.tgz", + "integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^19.2.7" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/virtual-list/-/virtual-list-1.4.0.tgz", + "integrity": "sha512-qoyNStkTJQDezPjBibGA5HNxS9NiKJvemD1bLp7qfyxDlwy7ofPLUP0ZqJ47hR8AKcFaizd0AP/7QWLTLpudKQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^8.0.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list/node_modules/@babel/runtime": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-8.0.0.tgz", + "integrity": "sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/antd": { + "version": "6.5.1", + "resolved": "https://registry.npmmirror.com/antd/-/antd-6.5.1.tgz", + "integrity": "sha512-VZVVF9zYI6S0NHqboVhCoY9Iiqj6dphW1NPB+sEaAf2HuIQ0haXWXj7ZvAXTRDzusktV6+cvvrSZEdRi4twATg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.3.2", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.29.2", + "@rc-component/cascader": "~1.17.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.10.0", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.3", + "@rc-component/form": "~1.8.5", + "@rc-component/image": "~1.9.0", + "@rc-component/input": "~1.3.1", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.10.0", + "@rc-component/menu": "~1.4.1", + "@rc-component/motion": "^1.3.3", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~2.0.7", + "@rc-component/pagination": "~1.4.0", + "@rc-component/picker": "~1.11.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~2.0.0", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.8.2", + "@rc-component/slider": "~1.1.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.10.4", + "@rc-component/tabs": "~1.11.0", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.4.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/tree-select": "~1.11.0", + "@rc-component/trigger": "^3.10.0", + "@rc-component/upload": "~1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "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.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..bfbf103 --- /dev/null +++ b/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "aijianzhan-web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^6.3.2", + "antd": "^6.5.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.6.3", + "vite": "^5.4.11" + } +} diff --git a/web/src/AgentUsersPage.tsx b/web/src/AgentUsersPage.tsx new file mode 100644 index 0000000..7d4568f --- /dev/null +++ b/web/src/AgentUsersPage.tsx @@ -0,0 +1,370 @@ +import { useEffect, useMemo, useState } from "react"; +import { + App as AntApp, + Button, + Card, + Form, + Input, + Modal, + Popconfirm, + Select, + Space, + Table, + Tag, + Typography, +} from "antd"; +import { + EditOutlined, + PlusOutlined, + ReloadOutlined, + DeleteOutlined, + KeyOutlined, + StopOutlined, + CheckOutlined, +} from "@ant-design/icons"; +import { + AgentAccount, + Role, + Session, + createAgent, + deleteAgent, + listAgents, + listRoles, + rotateAgentSecret, + updateAgent, +} from "./api"; +import { permLabel } from "./agentPerms"; + +type FormValues = { + name: string; + role_id: number; + app_slugs: string[]; +}; + +export function AgentUsersPage(props: { + session: Session; + busy: boolean; + setBusy: (v: boolean) => void; + setError: (v: string) => void; + setInfo: (v: string) => void; +}) { + const { session, busy, setBusy, setError, setInfo } = props; + const { message, modal } = AntApp.useApp(); + const [agents, setAgents] = useState([]); + const [roles, setRoles] = useState([]); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [form] = Form.useForm(); + const roleId = Form.useWatch("role_id", form); + + const selectedRole = useMemo( + () => roles.find((r) => r.role_id === roleId), + [roles, roleId] + ); + + async function refresh() { + setLoading(true); + try { + const [a, r] = await Promise.all([listAgents(session), listRoles(session)]); + setAgents(a.items || []); + setRoles(r.items || []); + } catch (e: any) { + const msg = e.message || String(e); + setError(msg); + message.error(msg); + } finally { + setLoading(false); + } + } + + useEffect(() => { + refresh(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [session.accessToken]); + + function openCreate() { + setEditing(null); + const pref = + roles.find((x) => x.code === "生成发布" || x.code === "publisher") || + roles.find((x) => x.code === "运维" || x.code === "operator") || + roles[0]; + form.resetFields(); + form.setFieldsValue({ + name: "", + role_id: pref?.role_id, + app_slugs: [], + }); + setOpen(true); + } + + function openEdit(a: AgentAccount) { + setEditing(a); + form.setFieldsValue({ + name: a.name || "", + role_id: a.role_id || undefined, + app_slugs: a.app_slugs || [], + }); + setOpen(true); + } + + async function onSubmit(activate = false) { + const values = await form.validateFields(); + setBusy(true); + try { + if (!editing) { + const res = await createAgent(session, { + name: values.name.trim(), + role_id: values.role_id, + app_slugs: values.app_slugs || [], + }); + modal.success({ + title: "用户已创建", + content: ( +
+

请立即保存 client_secret(只显示一次):

+ + {res.client_secret} + +
+ ), + }); + setInfo(`已创建用户「${values.name.trim()}」`); + } else { + await updateAgent(session, editing.agent_id, { + name: values.name.trim(), + role_id: values.role_id, + app_slugs: values.app_slugs || [], + ...(activate ? { status: "active" } : {}), + }); + message.success(activate ? "已保存并启用" : "已保存"); + setInfo(activate ? `已保存并启用「${values.name.trim()}」` : `已保存「${values.name.trim()}」`); + } + setOpen(false); + await refresh(); + } catch (e: any) { + const msg = e.message || String(e); + setError(msg); + message.error(msg); + } finally { + setBusy(false); + } + } + + const statusTag = (s: string) => { + if (s === "active") return active; + if (s === "pending") return pending; + return {s}; + }; + + return ( + + + + + } + > + + 当前租户 #{session.tenantId}。用户挂在本租户下;同角色跨公司靠租户隔离,不靠再拆一套角色。 + 角色请在「角色管理」中维护。 + + + ( +
+ {v || "—"} + {r.host_key ? ( +
+ + host: {r.host_key} + +
+ ) : null} +
+ ), + }, + { + title: "client_id", + dataIndex: "client_id", + render: (v) => {v}, + }, + { title: "状态", dataIndex: "status", width: 110, render: statusTag }, + { + title: "角色", + render: (_, r) => r.role_name || r.role_code || "—", + }, + { + title: "可访问模块", + dataIndex: "app_slugs", + render: (slugs: string[]) => + (slugs || []).length ? ( + + {(slugs || []).map((s) => ( + {s} + ))} + + ) : ( + "—" + ), + }, + { + title: "操作", + width: 320, + render: (_, a) => ( + + + + + { + setBusy(true); + try { + await deleteAgent(session, a.agent_id); + message.success("已删除"); + setInfo(`已删除「${a.name}」`); + await refresh(); + } catch (e: any) { + message.error(e.message || String(e)); + setError(e.message || String(e)); + } finally { + setBusy(false); + } + }} + > + + + + ), + }, + ]} + /> + + setOpen(false)} + confirmLoading={busy} + width={640} + destroyOnClose + footer={[ + , + editing ? ( + + ) : null, + , + ]} + > +
+ + + + + + + +
+ + ); +} diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..4e425da --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,1025 @@ +import { useEffect, useRef, useState } from "react"; +import { + Session, + acceptInvite, + clearSession, + createTenant, + generateDraft, + getBlueprint, + getCapsule, + getCompanyEntitlements, + isPendingMembership, + listApps, + listAudit, + listLlmProviders, + loadSession, + loginWith, + publish, + register, + saveDraft, + saveSession, + uploadFile, +} from "./api"; +import { AgentUsersPage } from "./AgentUsersPage"; +import { InvitesPage } from "./InvitesPage"; +import { ModulesPage } from "./ModulesPage"; +import { OrgUnitsPage } from "./OrgUnitsPage"; +import { RolesPage } from "./RolesPage"; +import { SyncPage } from "./SyncPage"; +import { PlatformTenantsPage } from "./PlatformTenantsPage"; +import { MembersPage } from "./MembersPage"; +import { isCompanyAdmin, isPlatformAdmin } from "./agentPerms"; +import { GeneratedApp } from "./GeneratedApp"; +import { loadDemoFixtures } from "./demoFixtures"; +import { clearActivePreview, fetchPreview, previewRouteId } from "./preview"; +import { ConsoleLayout, type ConsoleTab } from "./ConsoleLayout"; +import { LoadingShell, LoginShell, PendingShell } from "./AuthShell"; +import { Alert, Button, Card, Col, Form, Input, Row, Select, Space, Table, Typography } from "antd"; + +type Tab = ConsoleTab; +type View = "console" | "app"; + +function appRouteSlug(): string | null { + const h = (window.location.hash || "").replace(/^#/, ""); + const m = h.match(/^\/?app\/([a-zA-Z][a-zA-Z0-9_]{0,63})\/?$/); + return m ? m[1] : null; +} + +function navigateToApp(appSlug: string) { + const next = `#/app/${encodeURIComponent(appSlug)}`; + if (window.location.hash === next) { + window.dispatchEvent(new Event("hashchange")); + } else { + window.location.hash = next; + } +} + +function navigateToConsole() { + if (!window.location.hash || window.location.hash === "#/" || window.location.hash === "#") { + return; + } + window.location.hash = "#/"; +} + +/** 把 File 写回原生 file input,使「选择文件」旁显示文件名(非仅下方提示)。 */ +function assignInputFiles(input: HTMLInputElement | null, files: File[]) { + if (!input) return; + try { + const dt = new DataTransfer(); + for (const f of files) dt.items.add(f); + input.files = dt.files; + } catch { + /* 个别环境不支持赋值,仍靠 React state 提交 */ + } +} + +export default function App() { + const [tab, setTab] = useState("generate"); + const [view, setView] = useState("console"); + const [session, setSession] = useState(null); + const [error, setError] = useState(""); + const [info, setInfo] = useState(""); + + const [username, setUsername] = useState("demo"); + const [password, setPassword] = useState("demo123"); + const [displayName, setDisplayName] = useState("演示用户"); + const [inviteCode, setInviteCode] = useState(""); + const [companyName, setCompanyName] = useState(""); + + const [prompt, setPrompt] = useState(""); + const [excel, setExcel] = useState(null); + const [images, setImages] = useState([]); + const [layoutFiles, setLayoutFiles] = useState([]); + const excelInputRef = useRef(null); + const imagesInputRef = useRef(null); + const layoutsInputRef = useRef(null); + const [storageMode, setStorageMode] = useState<"schema_per_app" | "database_per_app">("schema_per_app"); + const [llmProvider, setLlmProvider] = useState("deepseek"); + const [llmModel, setLlmModel] = useState("deepseek-chat"); + const [llmProviders, setLlmProviders] = useState< + { id: string; label: string; configured: boolean; default_model: string; models: { id: string; label: string }[] }[] + >([]); + const [draft, setDraft] = useState(null); + const [warnings, setWarnings] = useState([]); + const [fidelity, setFidelity] = useState(null); + const [generateLog, setGenerateLog] = useState(null); + const [slug, setSlug] = useState("myapp"); + /** 发布目标:__new__ = 新建模块;否则为已有模块 slug(发布新生成的页面) */ + const [publishTarget, setPublishTarget] = useState("__new__"); + const [appList, setAppList] = useState< + { slug: string; name: string; status: string; status_label?: string; building?: boolean; page_count: number }[] + >([]); + const [blueprint, setBlueprint] = useState(null); + const [resource, setResource] = useState("records"); + const [capsule, setCapsule] = useState(""); + const [agentKeyHidden, setAgentKeyHidden] = useState(true); + const [busy, setBusy] = useState(false); + const [authBusy, setAuthBusy] = useState(false); + const [audits, setAudits] = useState([]); + const [entitlements, setEntitlements] = useState(null); + const [demoLoaded, setDemoLoaded] = useState(false); + const [demoSummary, setDemoSummary] = useState(""); + + async function applyDemoFixtures(silent = false) { + // 未登录时不要抢 busy / info,否则会锁死登录按钮并出现误导绿条 + if (!loadSession() && !session) { + if (!silent) setError("请先登录后再填入测试默认"); + return; + } + setBusy(true); + if (!silent) setError(""); + try { + const pack = await loadDemoFixtures(); + setPrompt(pack.prompt); + setExcel(pack.excel); + setImages(pack.images); + setLayoutFiles(pack.layouts); + setSlug(pack.slug); + setDemoLoaded(true); + assignInputFiles(excelInputRef.current, pack.excel ? [pack.excel] : []); + assignInputFiles(imagesInputRef.current, pack.images); + assignInputFiles(layoutsInputRef.current, pack.layouts); + const parts = [ + pack.excel ? pack.excel.name : null, + pack.images.length ? `${pack.images.length} 张截图` : null, + pack.layouts.length ? `${pack.layouts.length} 个 HTML` : null, + ].filter(Boolean); + setDemoSummary(parts.join(" · ") || "仅需求文本"); + const miss = pack.missing.length ? `(缺:${pack.missing.join(", ")})` : ""; + setInfo(`已填入测试默认:${pack.label}${miss}`); + } catch (e: any) { + if (!silent) setError(e.message || String(e)); + } finally { + setBusy(false); + } + } + + useEffect(() => { + const saved = loadSession(); + if (saved) setSession(saved); + const onExpired = () => { + setSession(null); + // 草稿预览 / 公开应用 URL 不踢回控制台(否则保真截图永远截到登录页) + if (previewRouteId() || appRouteSlug()) { + return; + } + setError("登录已失效,请重新登录"); + navigateToConsole(); + setView("console"); + setBlueprint(null); + }; + window.addEventListener("ajz:session-expired", onExpired); + listLlmProviders() + .then((res) => { + setLlmProviders(res.providers || []); + const def = res.default || "deepseek"; + const p = (res.providers || []).find((x) => x.id === def) || res.providers?.[0]; + if (p) { + setLlmProvider(p.id); + setLlmModel(p.default_model || p.models?.[0]?.id || ""); + } + }) + .catch(() => { + /* ignore */ + }); + return () => window.removeEventListener("ajz:session-expired", onExpired); + }, []); + + // 已登录后才自动填入 test/ 演示包(避免干扰登录页);超管未进公司时不填 + useEffect(() => { + if (!session?.accessToken || isPendingMembership(session)) return; + if (isPlatformAdmin(session.role) && !(session.tenantId > 0)) return; + const skip = localStorage.getItem("ajz_skip_demo_autoload") === "1"; + if (skip || demoLoaded) return; + applyDemoFixtures(true).catch(() => { + /* ignore */ + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [session?.accessToken, session?.tenantId]); + + useEffect(() => { + if (!session?.accessToken || isPendingMembership(session)) { + setAppList([]); + setEntitlements(null); + return; + } + if (isPlatformAdmin(session.role) && !(session.tenantId > 0)) { + setTab("platform"); + setAppList([]); + setEntitlements(null); + return; + } + listApps(session) + .then((res) => setAppList(res.items || [])) + .catch(() => setAppList([])); + if (session.tenantId > 0) { + // 超管进入公司后 entitlements 接口会返回全量;普通账号按公司额度 + getCompanyEntitlements(session) + .then((r) => setEntitlements(r.permissions || [])) + .catch(() => setEntitlements(isPlatformAdmin(session.role) ? null : [])); + } else { + setEntitlements(null); + } + }, [session]); + + // 按 URL 打开:/#/preview/{id}(草稿预览,免登录)或 /#/app/{slug} + useEffect(() => { + let cancelled = false; + async function syncRoute() { + const pid = previewRouteId(); + if (pid) { + if (!cancelled) { + setBusy(true); + setError(""); + } + try { + const pack = await fetchPreview(pid); + if (cancelled) return; + setBlueprint(pack.blueprint); + setDraft(pack.blueprint); + setSlug(pack.blueprint?.meta?.slug || "preview"); + setResource(pack.resource || "records"); + setView("app"); + } catch (e: any) { + if (!cancelled) { + clearActivePreview(); + setError(e.message || String(e)); + setView("console"); + setBlueprint(null); + navigateToConsole(); + } + } finally { + if (!cancelled) setBusy(false); + } + return; + } + + clearActivePreview(); + const routeSlug = appRouteSlug(); + if (!routeSlug) { + if (!cancelled) { + setView("console"); + setBlueprint(null); + } + return; + } + if (!cancelled) { + setBusy(true); + setError(""); + setSlug(routeSlug); + } + try { + const sess = session || loadSession(); + const bp = await getBlueprint(sess, routeSlug); + if (cancelled) return; + setBlueprint(bp); + setDraft(bp); + const r0 = bp?.apis?.resources?.[0]?.path?.replace(/^\//, "") || resource; + setResource(r0); + setView("app"); + } catch (e: any) { + if (!cancelled) { + setError(e.message || String(e)); + setView("console"); + setBlueprint(null); + navigateToConsole(); + } + } finally { + if (!cancelled) setBusy(false); + } + } + void syncRoute(); + window.addEventListener("hashchange", syncRoute); + return () => { + cancelled = true; + window.removeEventListener("hashchange", syncRoute); + }; + }, [session]); + + // 登录后 / 切到生成页时,把已加载的 File 写回原生选择框(避免一直显示「未选择任何文件」) + useEffect(() => { + if (!demoLoaded && !excel && !images.length && !layoutFiles.length) return; + assignInputFiles(excelInputRef.current, excel ? [excel] : []); + assignInputFiles(imagesInputRef.current, images); + assignInputFiles(layoutsInputRef.current, layoutFiles); + }, [demoLoaded, excel, images, layoutFiles, tab, session, view]); + + function persist(s: Session) { + setSession(s); + saveSession(s); + } + + async function onLogin(payload: { + mode: import("./AuthShell").LoginMode; + username?: string; + phone?: string; + password?: string; + sms_code?: string; + }) { + setAuthBusy(true); + setError(""); + setInfo(""); + try { + const body = + payload.mode === "phone_sms" + ? { phone: String(payload.phone || "").trim(), sms_code: String(payload.sms_code || "").trim() } + : payload.mode === "phone_password" + ? { phone: String(payload.phone || "").trim(), password: String(payload.password || "") } + : { username: String(payload.username || "").trim(), password: String(payload.password || "") }; + const s = await loginWith(body); + if (isPlatformAdmin(s.role) || s.username === "ljk_admin") { + setTab("platform"); + } + persist(s); + setInfo(`已登录 ${s.displayName || s.phone || s.username}(${s.role || "用户"})`); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setAuthBusy(false); + } + } + + async function onRegister(user = username, pass = password, name = displayName) { + setAuthBusy(true); + setError(""); + setInfo(""); + try { + const s = await register(String(user || "").trim(), String(pass || ""), String(name || "").trim()); + persist(s); + setInfo(`注册成功:待加入公司(pending)。请输入邀请码,或创建自己的公司。`); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setAuthBusy(false); + } + } + + async function onAcceptInvite() { + if (!session) return; + setAuthBusy(true); + setError(""); + try { + const s = await acceptInvite(session, inviteCode.trim()); + persist(s); + setInfo(s.displayName ? `已加入租户 #${s.tenantId}` : `已加入租户 #${s.tenantId}`); + setInviteCode(""); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setAuthBusy(false); + } + } + + async function onCreateCompany() { + if (!session) return; + setAuthBusy(true); + setError(""); + try { + const s = await createTenant(session, companyName.trim() || displayName || username); + persist(s); + setInfo(`已创建公司(租户 #${s.tenantId}),你是管理员`); + setCompanyName(""); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setAuthBusy(false); + } + } + + async function onGenerate() { + setBusy(true); + setError(""); + setFidelity(null); + setGenerateLog(null); + try { + const res = await generateDraft(prompt, excel, images, storageMode, llmProvider, llmModel, layoutFiles); + setDraft(res.draft); + setWarnings(res.warnings || []); + setFidelity(res.fidelity || null); + setGenerateLog(res.generate_log || null); + if (res.draft?.meta?.slug) setSlug(res.draft.meta.slug); + const r0 = res.draft?.apis?.resources?.[0]?.path?.replace(/^\//, ""); + if (r0) setResource(r0); + const fid = res.fidelity; + let fidMsg = ""; + if (fid && !fid.skipped) { + fidMsg = fid.passed + ? `还原度达标 ${fid.final_score}%(目标 ${fid.target}%)。` + : `还原度 ${fid.final_score ?? "—"}%(目标 ${fid.target}%),请查看差异后继续微调或再生成。`; + } + const runId = res.generate_log?.run_id ? ` run=${res.generate_log.run_id}` : ""; + let draftNote = ""; + if (session && res.draft?.meta?.slug) { + try { + const s = String(res.draft.meta.slug) + .trim() + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "_") + .replace(/_+/g, "_") + .replace(/^_|_$/g, "") + .slice(0, 48); + if (s) { + await saveDraft(session, s, res.draft); + const listed = await listApps(session); + setAppList(listed.items || []); + setPublishTarget("__new__"); + draftNote = `已登记为在建模块「${s}」。`; + } + } catch (de: any) { + draftNote = `(在建登记未成功:${de.message || de})`; + } + } + setInfo( + `草稿置信度 ${res.confidence}。${fidMsg}${draftNote}确认发布后将打开模块页。${runId}` + ); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setBusy(false); + } + } + + function openPublishedApp(appSlug: string) { + setSlug(appSlug); + navigateToApp(appSlug); + } + + async function onPublish() { + if (!session || !draft) return; + setBusy(true); + setError(""); + try { + const normalized = JSON.parse(JSON.stringify(draft)); + const intoExisting = publishTarget !== "__new__"; + // slug:新建用草稿;已有模块用所选目标 + let s = intoExisting + ? publishTarget + : String(normalized?.meta?.slug || "app") + .trim() + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "_") + .replace(/_+/g, "_") + .replace(/^_|_$/g, ""); + if (!intoExisting) { + if (!s || !/^[a-z]/.test(s)) s = `app_${s || "draft"}`.replace(/_+/g, "_"); + s = s.slice(0, 48).replace(/_+$/g, "") || "app"; + } + const existingMeta = intoExisting ? appList.find((a) => a.slug === s) : null; + normalized.meta = { + ...(normalized.meta || {}), + slug: s, + name: existingMeta?.name || normalized.meta?.name || s, + }; + if (!normalized.meta.name) normalized.meta.name = s; + if (!normalized.version) normalized.version = "1.0"; + if (!normalized.storage) normalized.storage = { mode: "schema_per_app", engine: "postgres" }; + if (!normalized.security) normalized.security = { visibility: "private", roles: [], row_policies: [] }; + if (normalized.apis) { + normalized.apis = { ...normalized.apis, base_path: `/api/v1/apps/${s}` }; + } + // 标识符 / type 粗规范,并同步 apis、pages 引用(避免 unknown entity) + const snake = (v: string) => + String(v || "") + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "_") + .replace(/_+/g, "_") + .replace(/^_|_$/g, ""); + const entityMap: Record = {}; + for (const ent of normalized.entities || []) { + const oldName = String(ent.name || ""); + if (ent.name) ent.name = snake(ent.name) || "record"; + if (ent.table) ent.table = snake(ent.table) || ent.name; + if (oldName) { + entityMap[oldName] = ent.name; + entityMap[snake(oldName)] = ent.name; + } + for (const f of ent.fields || []) { + if (f.name) f.name = snake(String(f.name).replace(/\./g, "_")) || "field"; + const t = String(f.type || "string").toLowerCase(); + if (["float", "float64", "double", "number", "numeric"].includes(t)) f.type = "decimal"; + else if (["integer", "int32"].includes(t)) f.type = "int"; + else if (["int64", "long"].includes(t)) f.type = "bigint"; + else if (t === "bool") f.type = "boolean"; + else if (["varchar", "str"].includes(t)) f.type = "string"; + } + } + for (const r of normalized.apis?.resources || []) { + const key = String(r.entity || ""); + r.entity = entityMap[key] || entityMap[snake(key)] || snake(key) || r.entity; + if (r.path) r.path = "/" + (snake(String(r.path).replace(/^\//, "")) || "items"); + } + for (const p of normalized.pages || []) { + const key = String(p.entity || ""); + p.entity = entityMap[key] || entityMap[snake(key)] || snake(key) || p.entity; + } + const r0 = normalized?.apis?.resources?.[0]?.path?.replace(/^\//, "") || resource; + setResource(r0); + const mode = intoExisting ? "add_pages" : "create"; + const resp = await publish(session, s, normalized, mode); + setDraft(normalized); + setSlug(s); + // 刷新模块列表 + try { + const listed = await listApps(session); + setAppList(listed.items || []); + } catch { + /* ignore */ + } + const modeLabel = + resp.publish_mode === "pages_added" || resp.publish_mode === "merged" + ? `已发布新页面${resp.added_pages?.length ? `(${(resp.added_pages as string[]).join(", ")})` : ""}` + : "已新建模块"; + setInfo(`${modeLabel} ${s} → ${resp.schema_name}。正在跳转模块页…`); + openPublishedApp(s); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setBusy(false); + } + } + + function onOpenApp() { + if (!session) return; + setError(""); + if (!slug.trim()) { + setError("请填写已发布模块 slug"); + return; + } + const hit = appList.find((a) => a.slug === slug.trim()); + if (hit && hit.status !== "published") { + setError(`模块「${slug}」状态为${hit.status_label || hit.status},请先发布后再打开;可在「模块管理」继续编辑`); + return; + } + setInfo(`正在跳转模块:/#/app/${slug}`); + openPublishedApp(slug.trim()); + } + async function onCapsule() { + if (!session) return; + setBusy(true); + setError(""); + try { + const res = await getCapsule(session, slug); + setCapsule(res.capsule); + setInfo(res.hint); + setTab("agent"); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setBusy(false); + } + } + + // 公开展示 / 草稿预览优先:路由加载中不闪登录页 + if (view === "app" && blueprint) { + return ( + { + clearActivePreview(); + navigateToConsole(); + }} + /> + ); + } + + const onConsoleTabChange = async (next: Tab) => { + setTab(next); + setError(""); + if (next === "audit" && session) { + try { + const res = await listAudit(session); + setAudits(res.items || []); + } catch (e: any) { + setError(e.message || String(e)); + } + } + }; + + const pendingRoute = previewRouteId() || appRouteSlug(); + if (pendingRoute && !blueprint) { + return ( + + ); + } + + if (!session) { + return ( + + ); + } + + if (isPendingMembership(session)) { + return ( + { + clearSession(); + setSession(null); + }} + /> + ); + } + + const providerOptions = (llmProviders.length + ? llmProviders + : [ + { id: "deepseek", label: "DeepSeek", configured: true, default_model: "", models: [] as { id: string; label: string }[] }, + { id: "minimax", label: "MiniMax", configured: true, default_model: "", models: [] }, + { id: "heuristic", label: "本地启发式", configured: true, default_model: "", models: [] }, + ] + ).map((p) => ({ + value: p.id, + label: `${p.label}${p.configured === false ? "(未配置 Key)" : ""}`, + })); + + const modelOptions = (llmProviders.find((p) => p.id === llmProvider)?.models || []).map((m) => ({ + value: m.id, + label: m.label, + })); + if (!modelOptions.length && llmModel) { + modelOptions.push({ value: llmModel, label: llmModel || "默认" }); + } + + return ( + setSession(s)} + onLogout={() => { + clearSession(); + setSession(null); + }} + banner={ + <> + {error ? setError("")} style={{ marginBottom: 12 }} /> : null} + {info ? setInfo("")} style={{ marginBottom: 12 }} /> : null} + + } + > + {tab === "generate" && ( + + + + + {demoLoaded && 已加载:{demoSummary}} + + }> +
+ + setPrompt(e.target.value)} + /> + + + setExcel(e.target.files?.[0] || null)} + /> + + f.name).join("、")}` : "上传后自动走还原闭环"} + > + setImages(Array.from(e.target.files || []))} + /> + + f.name).join("、")}` : "支持 .html / .htm / .mhtml"} + > + setLayoutFiles(Array.from(e.target.files || []))} + /> + + +
+ + + + + + + { + setPublishTarget(v); + if (v !== "__new__") setSlug(v); + }} + options={[ + { value: "__new__", label: "新建模块(草稿 slug)" }, + ...appList.map((a) => ({ + value: a.slug, + label: `${a.name || a.slug}(${a.slug} · ${a.status_label || a.status} · ${a.page_count} 页)`, + })), + ]} + /> + + + {publishTarget === "__new__" + ? "将创建新模块;slug 取自蓝图 meta.slug。不同用户可访问的模块由授权决定。" + : "将把草稿里新生成的页面发布到该模块(page id / route 不可与现有重复)。"} + + + setSlug(e.target.value)} /> + + + { + const f = e.target.files?.[0]; + if (!f || !session) return; + try { + const up = await uploadFile(session, f); + setInfo(`已上传 ${up.filename} → ${up.url}`); + } catch (err: any) { + setError(err.message || String(err)); + } + }} + /> + + + + + + + {warnings.length > 0 && ( + {warnings.map((w) =>
  • {w}
  • )}} + /> + )} + {fidelity && !fidelity.skipped && ( + +
      + {(fidelity.rounds || []).map((r: any) => ( +
    • + 第{r.round}轮:{r.score ?? "—"}%{r.pass ? " ✓" : ""} + {(r.fails || []).length + ? ` · 差异:${(r.fails || []).slice(0, 5).join(";")}${(r.fails || []).length > 5 ? "…" : ""}` + : ""} +
    • + ))} +
    +
    + )} + {generateLog?.lines?.length > 0 && ( + + + 服务端同步写入 .runtime/logs/generate/ + {generateLog.log_file + ? `(本次:${String(generateLog.log_file).replace(/^.*[\\/]/, "")})` + : ""} + +
    +                      {(generateLog.lines as any[])
    +                        .map((l) => `${(l.level || "info").toUpperCase().padEnd(7)} ${l.message || ""}`)
    +                        .join("\n")}
    +                    
    +
    + )} + + + +
    + 可微调后发布}> + + 含 entities / apis / pages。发布后 pages 会渲染成真正的业务前端。 + + { try { setDraft(JSON.parse(e.target.value)); } catch { /* */ } }} + placeholder="生成后在此显示蓝图…" + /> + + + + )} + + {tab === "agent" && ( + + + 前端不展示明文 API 契约,只展示加密胶囊;智能体用 agent_key 解密后请求。 + +
    + + setSlug(e.target.value)} /> + + + + + + + {!agentKeyHidden &&
    {session.agentKey}
    } + {capsule && ( +
    +
    {capsule}
    + +
    + )} +
    + )} + + {tab === "modules" && session && ( + { + setSlug(s); + openPublishedApp(s); + }} + onContinueDraft={async (s) => { + setBusy(true); + setError(""); + try { + const bp = await getBlueprint(session, s); + setDraft(bp); + setSlug(s); + setPublishTarget(s); + setTab("generate"); + setInfo(`已加载在建模块「${s}」,可继续编辑后发布`); + } catch (e: any) { + setError(e.message || String(e)); + } finally { + setBusy(false); + } + }} + /> + )} + + {tab === "sync" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && ( + + )} + + {tab === "roles" && session && ( + + )} + + {tab === "agents" && session && ( + + )} + + {tab === "platform" && session && isPlatformAdmin(session.role) && ( + setTab("members")} + /> + )} + {tab === "orgs" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && ( + + )} + + {tab === "invites" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && ( + + )} + {tab === "members" && session && (isCompanyAdmin(session.role) || (isPlatformAdmin(session.role) && session.tenantId > 0)) && ( + + )} + + {tab === "audit" && ( + +
    String(v) }, + { title: "用户", dataIndex: "user_id", width: 120 }, + { title: "动作", dataIndex: "action", width: 140 }, + { title: "详情", dataIndex: "detail", ellipsis: true }, + ]} + /> + + )} + + ); +} diff --git a/web/src/AuthShell.tsx b/web/src/AuthShell.tsx new file mode 100644 index 0000000..b761a32 --- /dev/null +++ b/web/src/AuthShell.tsx @@ -0,0 +1,348 @@ +import { ReactNode, useEffect, useRef, useState } from "react"; +import { Alert, Button, Card, Divider, Form, Input, Segmented, Space, Typography } from "antd"; +import type { InputRef } from "antd"; +import { sendLoginSMS } from "./api"; + +export type LoginMode = "account_password" | "phone_password" | "phone_sms"; + +type LoginProps = { + username: string; + password: string; + displayName: string; + busy: boolean; + error: string; + info: string; + onUsername: (v: string) => void; + onPassword: (v: string) => void; + onDisplayName: (v: string) => void; + onLogin: (payload: { + mode: LoginMode; + username?: string; + phone?: string; + password?: string; + sms_code?: string; + }) => void; + onRegister: (username: string, password: string, displayName: string) => void; +}; + +type PendingProps = { + displayLabel: string; + inviteCode: string; + companyName: string; + busy: boolean; + error: string; + info: string; + onInviteCode: (v: string) => void; + onCompanyName: (v: string) => void; + onAcceptInvite: () => void; + onCreateCompany: () => void; + onLogout: () => void; +}; + +function Shell({ children, subtitle }: { children: ReactNode; subtitle: string }) { + return ( +
    +
    + + + 宇信达智建 + + + {subtitle} + + {children} + +
    + ); +} + +function readInputValue(ref: React.RefObject, fallback: string): string { + const el = ref.current?.input; + if (el && typeof el.value === "string") return el.value; + return fallback; +} + +export function LoginShell(props: LoginProps) { + const [mode, setMode] = useState("account_password"); + const [smsCode, setSmsCode] = useState(""); + const [smsHint, setSmsHint] = useState(""); + const [cooldown, setCooldown] = useState(0); + const [sending, setSending] = useState(false); + const userRef = useRef(null); + const passRef = useRef(null); + const nameRef = useRef(null); + const phoneRef = useRef(null); + const codeRef = useRef(null); + + useEffect(() => { + if (cooldown <= 0) return; + const t = window.setTimeout(() => setCooldown((c) => c - 1), 1000); + return () => window.clearTimeout(t); + }, [cooldown]); + + const doLogin = () => { + if (mode === "phone_sms") { + const phone = readInputValue(phoneRef, props.username).trim(); + const code = readInputValue(codeRef, smsCode).trim(); + props.onUsername(phone); + props.onLogin({ mode, phone, sms_code: code }); + return; + } + if (mode === "phone_password") { + const phone = readInputValue(phoneRef, props.username).trim(); + const p = readInputValue(passRef, props.password); + props.onUsername(phone); + props.onPassword(p); + props.onLogin({ mode, phone, password: p }); + return; + } + const u = readInputValue(userRef, props.username).trim(); + const p = readInputValue(passRef, props.password); + props.onUsername(u); + props.onPassword(p); + props.onLogin({ mode, username: u, password: p }); + }; + + const doRegister = () => { + const u = readInputValue(userRef, props.username).trim() || readInputValue(phoneRef, props.username).trim(); + const p = readInputValue(passRef, props.password); + const n = readInputValue(nameRef, props.displayName).trim(); + props.onUsername(u); + props.onPassword(p); + props.onDisplayName(n); + props.onRegister(u, p, n); + }; + + async function onSendSMS() { + const phone = readInputValue(phoneRef, props.username).trim(); + if (!/^1[3-9]\d{9}$/.test(phone)) { + setSmsHint("请先填写正确的 11 位手机号"); + return; + } + setSending(true); + setSmsHint(""); + try { + const res = await sendLoginSMS(phone); + setCooldown(res.retry_after && res.retry_after > 0 ? res.retry_after : 60); + if (res.debug_code) { + setSmsCode(res.debug_code); + setSmsHint(`开发模式验证码:${res.debug_code}(正式环境将发短信)`); + } else { + setSmsHint(res.message || "验证码已发送"); + } + } catch (e: any) { + setSmsHint(e.message || String(e)); + } finally { + setSending(false); + } + } + + return ( + + setMode(v as LoginMode)} + options={[ + { label: "账号密码", value: "account_password" }, + { label: "手机密码", value: "phone_password" }, + { label: "短信登录", value: "phone_sms" }, + ]} + /> +
    + {mode === "account_password" ? ( + + props.onUsername(e.target.value)} + autoComplete="username" + placeholder="系统分配的用户名" + allowClear + /> + + ) : ( + + props.onUsername(e.target.value)} + autoComplete="tel" + placeholder="已绑定的 11 位手机号" + allowClear + /> + + )} + + {mode === "phone_sms" ? ( + + + setSmsCode(e.target.value)} + placeholder="6 位验证码" + maxLength={8} + /> + + + + ) : ( + + props.onPassword(e.target.value)} + autoComplete="current-password" + /> + + )} + + {mode === "account_password" ? ( + + props.onDisplayName(e.target.value)} + /> + + ) : null} + + {props.error ? ( + + ) : null} + {smsHint ? : null} + {props.info && !props.info.startsWith("已填入测试默认") ? ( + + ) : null} + + {mode === "account_password" ? ( + + + + + ) : ( + + + + + )} + +
    + + {mode === "account_password" ? ( + + ) : null} +
    + + 支持用户名+密码、手机号+密码、手机号+短信。外公司部署用 License 控期限时,不必强制手机登录。 + + +
    + ); +} + +export function PendingShell(props: PendingProps) { + return ( + + {props.displayLabel} · pending + {props.error && } + {props.info && } +
    + + props.onInviteCode(e.target.value)} + placeholder="粘贴公司管理员发来的邀请码" + /> + + + + + props.onCompanyName(e.target.value)} + placeholder="创建自己的公司(成为管理员)" + /> + + + + + + +
    + ); +} + +export function LoadingShell(props: { message: string; error?: string }) { + return ( + + {props.error ? : null} + + ); +} diff --git a/web/src/Charts.tsx b/web/src/Charts.tsx new file mode 100644 index 0000000..d4822b9 --- /dev/null +++ b/web/src/Charts.tsx @@ -0,0 +1,374 @@ +type Bucket = { key: string; count: number; sum?: number }; + +export type LinePoint = { x: string; values: Record }; + +const COLORS = ["#1d4f91", "#7c3aed", "#c2410c", "#0f766e", "#b42318", "#854d0e"]; + +export function KpiCard({ title, value }: { title: string; value: string | number }) { + return ( +
    +
    {title}
    +
    {value}
    +
    + ); +} + +export function BarChart({ title, buckets }: { title: string; buckets: Bucket[] }) { + const max = Math.max(1, ...buckets.map((b) => b.count)); + return ( +
    +

    {title}

    +
    + {buckets.map((b, i) => ( +
    + {b.key} +
    +
    +
    + {b.count} +
    + ))} +
    +
    + ); +} + +export function PieChart({ title, buckets }: { title: string; buckets: Bucket[] }) { + const total = buckets.reduce((s, b) => s + b.count, 0) || 1; + let acc = 0; + const stops = buckets.map((b, i) => { + const start = (acc / total) * 100; + acc += b.count; + const end = (acc / total) * 100; + return `${COLORS[i % COLORS.length]} ${start}% ${end}%`; + }); + return ( +
    +

    {title}

    +
    +
    +
      + {buckets.map((b, i) => ( +
    • + + {b.key} · {b.count} +
    • + ))} +
    +
    +
    + ); +} + +/** 折线:X 为类目/序号/里程,Y 为多系列数值 */ +export function LineChart({ + title, + points, + series, + yUnit = "", +}: { + title: string; + points: LinePoint[]; + series: { key: string; label: string }[]; + yUnit?: string; +}) { + if (!points.length || !series.length) { + return ( +
    + {title ?

    {title}

    : null} +
    暂无曲线数据
    +
    + ); + } + + const nums = points.flatMap((p) => series.map((s) => p.values[s.key]).filter((n) => Number.isFinite(n))) as number[]; + // 用分位裁掉极端尖刺,避免一根异常值把曲线压成一条线 + const sorted = [...nums].sort((a, b) => a - b); + const q = (p: number) => { + if (!sorted.length) return 0; + const i = Math.min(sorted.length - 1, Math.max(0, Math.floor((sorted.length - 1) * p))); + return sorted[i]; + }; + let minY = sorted.length ? Math.min(0, q(0.05)) : 0; + let maxY = sorted.length ? Math.max(1, q(0.95)) : 1; + if (maxY - minY < 1) { + minY = Math.min(0, ...nums, -1); + maxY = Math.max(1, ...nums, 1); + } + const pad = (maxY - minY) * 0.12 || 1; + const y0 = minY - pad; + const y1 = maxY + pad; + const W = 920; + const H = 280; + const L = 48; + const R = 16; + const T = 20; + const B = 36; + const plotW = W - L - R; + const plotH = H - T - B; + + const xAt = (i: number) => L + (points.length <= 1 ? plotW / 2 : (i / (points.length - 1)) * plotW); + const yAt = (v: number) => T + ((y1 - v) / (y1 - y0)) * plotH; + + const paths = series.map((s, si) => { + const d = points + .map((p, i) => { + const v = p.values[s.key]; + if (!Number.isFinite(v)) return null; + const cmd = i === 0 || !Number.isFinite(points[i - 1]?.values[s.key]) ? "M" : "L"; + return `${cmd}${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`; + }) + .filter(Boolean) + .join(" "); + return { ...s, d, color: COLORS[si % COLORS.length] }; + }); + + const ticks = 5; + const yTicks = Array.from({ length: ticks }, (_, i) => y0 + ((y1 - y0) * i) / (ticks - 1)); + + return ( +
    +
    + {title ?

    {title}

    : null} +
      + {paths.map((p) => ( +
    • + + {p.label} +
    • + ))} +
    +
    +
    + + {yTicks.map((t) => ( + + + + {t.toFixed(0)} + + + ))} + + {yUnit} + + {paths.map((p) => ( + + ))} + {points.map((p, i) => ( + + {i % Math.ceil(points.length / 8) === 0 ? p.x : ""} + + ))} + +
    +
    + ); +} + +/** 着色短棒 + 可选参考曲线(screenshot_faithful 通用图元) */ +export function SectionMarkChart({ + points, + yUnit = "", + yAxisLabel = "", + designColor = "#e8590c", + invertY = false, +}: { + points: { x: string; value: number; design?: number; color?: string }[]; + yUnit?: string; + yAxisLabel?: string; + designColor?: string; + /** 为 true 时纵轴方向与常规相反(上小下大),由 meta.ui.invert_y 控制 */ + invertY?: boolean; +}) { + if (!points.length) { + return ( +
    +
    暂无曲线数据
    +
    + ); + } + const vals = points.map((p) => p.value).filter((n) => Number.isFinite(n)); + const designs = points.map((p) => p.design).filter((n): n is number => Number.isFinite(n as number)); + const all = [...vals, ...designs]; + let minY = all.length ? Math.min(...all) : 0; + let maxY = all.length ? Math.max(...all) : 1; + if (maxY - minY < 1e-6) { + minY -= 1; + maxY += 1; + } + const pad = (maxY - minY) * 0.08 || 1; + minY -= pad; + maxY += pad; + const W = Math.max(920, points.length * 22); + const H = 260; + const L = 52; + const R = 12; + const T = 16; + const B = 34; + const plotW = W - L - R; + const plotH = H - T - B; + const xAt = (i: number) => L + (points.length <= 1 ? plotW / 2 : (i / (points.length - 1)) * plotW); + const yAt = (v: number) => { + if (invertY) { + return T + ((v - minY) / (maxY - minY)) * plotH; + } + return T + ((maxY - v) / (maxY - minY)) * plotH; + }; + const tickN = 6; + const yTicks = Array.from({ length: tickN }, (_, i) => minY + ((maxY - minY) * i) / (tickN - 1)); + const unitSuffix = yUnit ? String(yUnit) : ""; + const designPath = points + .map((p, i) => { + const d = p.design; + if (!Number.isFinite(d as number)) return null; + const cmd = i === 0 || !Number.isFinite(points[i - 1]?.design as number) ? "M" : "L"; + return `${cmd}${xAt(i).toFixed(1)},${yAt(d as number).toFixed(1)}`; + }) + .filter(Boolean) + .join(" "); + + return ( +
    +
    + + {yTicks.map((t) => ( + + + + {Number.isInteger(t) ? String(t) : t.toFixed(1)} + {unitSuffix} + + + ))} + {yAxisLabel ? ( + + {yAxisLabel} + + ) : null} + {designPath ? : null} + {points.map((p, i) => { + const v = p.value; + if (!Number.isFinite(v)) return null; + const x = xAt(i); + const y = yAt(v); + const y0 = yAt(0); + const top = Math.min(y, y0); + const h = Math.max(3, Math.abs(y - y0)); + return ( + + ); + })} + {points.map((p, i) => ( + + {i % Math.ceil(points.length / 8) === 0 ? p.x : ""} + + ))} + +
    +
    + ); +} + +/** 状态条:单值格,或双段竖条(上/下分段,由 widget.variant=stacked* 驱动) */ +export function StatusStrip({ + title, + items, + variant = "cells", +}: { + title: string; + items: { + label: string; + value: number; + secondary?: number; + tone?: "ok" | "warn" | "muted"; + stop?: boolean; + }[]; + variant?: "cells" | "stacked"; +}) { + if (variant === "stacked") { + return ( +
    + {title ?

    {title}

    : null} +
    + {items.map((it, i) => { + if (it.stop) { + return ( +
    + +
    + ); + } + const top = Math.max(0, Number(it.value) || 0); + const bottom = Math.max(0, Number(it.secondary) || 0); + const sum = top + bottom || 1; + // 几乎全灰:下段过小则整条灰显示上段天数 + if (bottom <= 0 && top > 0) { + return ( +
    + {top} +
    + ); + } + if (top <= 0 && bottom > 0) { + return ( +
    + {bottom} +
    + ); + } + const topPct = (top / sum) * 100; + const botPct = (bottom / sum) * 100; + return ( +
    +
    + {top} +
    +
    + {bottom} +
    +
    + ); + })} +
    +
    + ); + } + + return ( +
    + {title ?

    {title}

    : null} +
    + {items.map((it, i) => ( +
    + {it.label} + {it.value} +
    + ))} +
    +
    + ); +} diff --git a/web/src/ConsoleLayout.tsx b/web/src/ConsoleLayout.tsx new file mode 100644 index 0000000..1c61208 --- /dev/null +++ b/web/src/ConsoleLayout.tsx @@ -0,0 +1,358 @@ +import { ReactNode, useMemo, useState } from "react"; +import { + App as AntApp, + Avatar, + Button, + Form, + Input, + Layout, + Menu, + Modal, + Space, + Tag, + Typography, + theme, +} from "antd"; +import { + ApartmentOutlined, + ApiOutlined, + AuditOutlined, + BankOutlined, + CloudSyncOutlined, + KeyOutlined, + LogoutOutlined, + MobileOutlined, + RobotOutlined, + AppstoreOutlined, + TeamOutlined, + UserAddOutlined, + UserOutlined, + ThunderboltOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, +} from "@ant-design/icons"; +import { bindPhone, changePassword, saveSession, type Session } from "./api"; +import { hasEntitlement, isCompanyAdmin, isPlatformAdmin, roleLabel } from "./agentPerms"; + +const { Header, Sider, Content } = Layout; + +export type ConsoleTab = + | "generate" + | "modules" + | "agent" + | "roles" + | "agents" + | "orgs" + | "invites" + | "members" + | "audit" + | "sync" + | "platform"; + +type Props = { + session: Session; + entitlements: string[] | null; + tab: ConsoleTab; + onTabChange: (tab: ConsoleTab) => void; + onOpenApp: () => void; + onLogout: () => void; + onSession?: (s: Session) => void; + busy?: boolean; + banner?: ReactNode; + children: ReactNode; +}; + +const TAB_TITLES: Record = { + platform: "平台管理", + generate: "生成与发布", + modules: "模块管理", + sync: "数据同步", + agent: "智能体胶囊", + roles: "角色管理", + agents: "智能体账号", + orgs: "组织管理", + invites: "邀请加入", + members: "成员管理", + audit: "审计", +}; + +export function ConsoleLayout({ + session, + entitlements, + tab, + onTabChange, + onOpenApp, + onLogout, + onSession, + busy, + banner, + children, +}: Props) { + const [collapsed, setCollapsed] = useState(false); + const [pwdOpen, setPwdOpen] = useState(false); + const [pwdBusy, setPwdBusy] = useState(false); + const [phoneOpen, setPhoneOpen] = useState(false); + const [phoneBusy, setPhoneBusy] = useState(false); + const [pwdForm] = Form.useForm<{ old_password: string; new_password: string; confirm: string }>(); + const [phoneForm] = Form.useForm<{ phone: string }>(); + const { token } = theme.useToken(); + const { message } = AntApp.useApp(); + + const items = useMemo(() => { + const out: { key: ConsoleTab; icon: ReactNode; label: string }[] = []; + const platform = isPlatformAdmin(session.role); + const inTenant = (session.tenantId || 0) > 0; + + if (platform && !inTenant) { + out.push({ key: "platform", icon: , label: "平台管理" }); + return out; + } + + if (!inTenant) return out; + + if (platform) { + out.push({ key: "platform", icon: , label: "平台管理" }); + } + + const ent = (p: string) => hasEntitlement(entitlements, p); + const companyAdmin = isCompanyAdmin(session.role) || platform; + + if (ent("发布模块") || ent("写入模块") || ent("读取模块")) { + out.push({ key: "generate", icon: , label: "生成与发布" }); + } + if (ent("读取模块")) { + out.push({ key: "modules", icon: , label: "模块管理" }); + } + if (companyAdmin && ent("数据同步")) { + out.push({ key: "sync", icon: , label: "数据同步" }); + } + if (ent("读取模块")) { + out.push({ key: "agent", icon: , label: "智能体胶囊" }); + } + if (ent("管理智能体")) { + out.push({ key: "roles", icon: , label: "角色管理" }); + out.push({ key: "agents", icon: , label: "智能体账号" }); + } + if (companyAdmin && ent("管理组织")) { + out.push({ key: "orgs", icon: , label: "组织管理" }); + } + if (companyAdmin && ent("邀请成员")) { + out.push({ key: "invites", icon: , label: "邀请加入" }); + out.push({ key: "members", icon: , label: "成员管理" }); + } + if (ent("查看审计")) { + out.push({ key: "audit", icon: , label: "审计" }); + } + return out; + }, [session, entitlements]); + + return ( + + +
    + + {!collapsed && ( +
    + 宇信达智建 + {isPlatformAdmin(session.role) ? "平台超管工作台" : "AI 建站控制台"} +
    + )} +
    + onTabChange(key as ConsoleTab)} + style={{ borderInlineEnd: 0, padding: "8px 8px 24px" }} + /> + + +
    + + + + + + +
    + + {banner} +
    {children}
    +
    +
    + + setPwdOpen(false)} + confirmLoading={pwdBusy} + onOk={async () => { + const v = await pwdForm.validateFields(); + setPwdBusy(true); + try { + await changePassword(session, v.old_password, v.new_password); + message.success("密码已更新"); + setPwdOpen(false); + pwdForm.resetFields(); + } catch (e: any) { + message.error(e.message || String(e)); + } finally { + setPwdBusy(false); + } + }} + destroyOnHidden + > +
    + + 账号 {session.username} 为系统分配、不可更改;仅可修改密码。 + 绑定手机后也可用手机号 + 密码登录。 + + + + + + + + ({ + validator(_, value) { + if (!value || getFieldValue("new_password") === value) return Promise.resolve(); + return Promise.reject(new Error("两次输入不一致")); + }, + }), + ]} + > + + + +
    + + setPhoneOpen(false)} + confirmLoading={phoneBusy} + onOk={async () => { + const v = await phoneForm.validateFields(); + setPhoneBusy(true); + try { + const phone = (v.phone || "").trim(); + const res = await bindPhone(session, phone); + const next = { + ...session, + phone: res.phone || undefined, + usernameLoginDisabled: !!res.username_login_disabled, + }; + saveSession(next); + onSession?.(next); + message.success(res.phone ? `已绑定 ${res.phone}` : "已更新"); + setPhoneOpen(false); + } catch (e: any) { + message.error(e.message || String(e)); + } finally { + setPhoneBusy(false); + } + }} + destroyOnHidden + > +
    + + 绑定后可用手机号+密码或短信登录。有 License 期限控制时也可用用户名登录。 + + { + const s = String(value || "").trim(); + if (!s) return; + if (!/^1[3-9]\d{9}$/.test(s) && !/^\+?86\s*1[3-9]\d{9}$/.test(s.replace(/\s/g, ""))) { + throw new Error("请输入 11 位大陆手机号"); + } + }, + }, + ]} + > + + + +
    + + ); +} diff --git a/web/src/GeneratedApp.tsx b/web/src/GeneratedApp.tsx new file mode 100644 index 0000000..fe12874 --- /dev/null +++ b/web/src/GeneratedApp.tsx @@ -0,0 +1,1243 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + Session, + aggregate, + createRow, + deleteRow, + exportRows, + importRows, + listRows, + updateRow, +} from "./api"; +import { BarChart, KpiCard, LineChart, PieChart, StatusStrip } from "./Charts"; +import { BlockMachineTag, PageMachineTag } from "./PageMachineTag"; +import { ScreenshotFaithfulDashboard } from "./ScreenshotFaithfulDashboard"; + +type Blueprint = any; +type PageDef = { + id: string; + title: string; + route: string; + type: string; + entity: string; + layout?: { + columns?: string[]; + filters?: string[]; + actions?: string[]; + action_labels?: Record; + form_fields?: string[]; + widgets?: WidgetDef[]; + preset?: string; + filter_style?: string; + }; +}; + +/** LLM 常把 nav_items / shell_links 写成 {label,route};React 不能直接渲染对象,会白屏。 */ +function asUiLabel(v: unknown): string { + if (typeof v === "string") return v.trim(); + if (v && typeof v === "object") { + const o = v as Record; + for (const k of ["label", "title", "name", "text"]) { + if (typeof o[k] === "string" && String(o[k]).trim()) return String(o[k]).trim(); + } + } + return ""; +} + +function asUiRoute(v: unknown): string { + if (!v || typeof v !== "object") return ""; + const o = v as Record; + for (const k of ["route", "path", "href", "url"]) { + if (typeof o[k] === "string" && String(o[k]).trim()) return String(o[k]).trim(); + } + return ""; +} + +function normalizeUiStrings(raw: unknown, fallback: string[] = []): string[] { + const arr = Array.isArray(raw) ? raw : fallback; + return arr.map(asUiLabel).filter(Boolean); +} + +type WidgetDef = { + type: string; + title?: string; + metric?: string; + metrics?: string[]; + group_by?: string; + x_field?: string; + entity?: string; + columns?: string[]; + label_field?: string; + value_field?: string; + warn_field?: string; + filter_field?: string; + filter_op?: string; + filter_value?: number | string; + y_unit?: string; +}; + +function pageDisplayTitle(page: PageDef, bp: Blueprint): string { + // 尊重蓝图标题;不再把「库存」强行改成「商品」 + return page.title || page.id; +} + +function resourceForEntity(bp: Blueprint, entity: string): string { + const r = (bp?.apis?.resources || []).find((x: any) => x.entity === entity) || bp?.apis?.resources?.[0]; + return String(r?.path || `/${entity}`).replace(/^\//, ""); +} + +type FieldMeta = { + name: string; + label: string; + type: string; + enum_values?: string[]; + widget?: string; +}; + +function fieldsOf(bp: Blueprint, entity: string): FieldMeta[] { + const ent = (bp?.entities || []).find((e: any) => e.name === entity) || bp?.entities?.[0]; + return (ent?.fields || []).map((f: any) => ({ + name: f.name, + label: f.label || f.name, + type: f.type || "string", + enum_values: f.enum_values || f.enumValues, + widget: f.ui?.widget, + })); +} + +const SYSTEM_FORM_FIELDS = new Set([ + "id", "tenant_id", "org_unit_id", "created_by", "created_at", "updated_at", +]); + +const CATEGORY_FIELD_HINT = /(status|type|category|state|分类|类型|状态)$/i; + +function isCategoryField(f: FieldMeta): boolean { + if (f.type === "enum" || f.widget === "select") return true; + if (f.enum_values && f.enum_values.length > 0) return true; + return CATEGORY_FIELD_HINT.test(f.name) || CATEGORY_FIELD_HINT.test(f.label); +} + +function isDateField(f: FieldMeta): boolean { + if (f.type === "datetime" || isDateTimeField(f)) return false; + if (f.type === "date" || f.widget === "datepicker") return true; + return /日期/.test(f.label) || /(^|_)date$/i.test(f.name); +} + +function isDateTimeField(f: FieldMeta): boolean { + return f.type === "datetime" || f.widget === "datetime" || /(_at$|时间|datetime)/i.test(f.name); +} + +function toDateInputValue(raw: string, mode: "date" | "datetime"): string { + if (!raw) return ""; + const d = new Date(raw); + if (Number.isNaN(d.getTime())) { + // already yyyy-mm-dd or yyyy-mm-ddThh:mm + if (mode === "date") return raw.slice(0, 10); + return raw.length >= 16 ? raw.slice(0, 16) : raw; + } + const pad = (n: number) => String(n).padStart(2, "0"); + const y = d.getFullYear(); + const m = pad(d.getMonth() + 1); + const day = pad(d.getDate()); + if (mode === "date") return `${y}-${m}-${day}`; + return `${y}-${m}-${day}T${pad(d.getHours())}:${pad(d.getMinutes())}`; +} + +function fromDateInputValue(raw: string, mode: "date" | "datetime"): string { + if (!raw) return ""; + if (mode === "date") return raw; + // datetime-local → ISO-ish for backend + const d = new Date(raw); + return Number.isNaN(d.getTime()) ? raw : d.toISOString(); +} + +function humanizeDbError(msg: string): string { + if (/status_check|_status_check/i.test(msg)) { + return "状态值不合法:请从下拉框选择蓝图允许的枚举值。"; + } + if (/violates check constraint/i.test(msg) || /检查约束/.test(msg)) { + return `字段值不符合约束:${msg}`; + } + if (/null value|非空|not-null/i.test(msg)) { + return `必填字段为空:${msg}`; + } + return msg; +} + +function FieldControl({ + meta, + value, + options, + onChange, +}: { + meta: FieldMeta; + value: string; + options: string[]; + onChange: (v: string) => void; +}) { + if (isCategoryField(meta) || options.length > 0) { + const opts = options.length ? options : meta.enum_values || []; + return ( + + ); + } + if (meta.type === "boolean" || meta.widget === "checkbox") { + return ( + + ); + } + if (isDateTimeField(meta)) { + return ( + onChange(fromDateInputValue(e.target.value, "datetime"))} + /> + ); + } + if (isDateField(meta)) { + return ( + onChange(e.target.value)} + /> + ); + } + if (meta.type === "int" || meta.type === "bigint" || meta.type === "decimal" || meta.widget === "number") { + return ( + onChange(e.target.value)} + /> + ); + } + if (meta.type === "text" || meta.widget === "textarea") { + return ( +