diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..950bf81ba075d49255d0ad5c4a814bcadab220e9 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +# ============================================ +# Gemini Business2API 配置示例 +# ============================================ + +# 管理员密钥(必需,用于登录管理面板) +# 明文示例: +ADMIN_KEY=your-admin-secret-key + +# 服务端口(可选,默认 7860) +# PORT=7860 + +# ============================================ +# 数据库配置(可选,用于无持久化存储的环境如 HF Spaces) +# ============================================ +# 支持 PostgreSQL 数据库存储(账户/设置/统计) +# 未设置时使用本地文件存储(原有行为) +# +# 示例(Neon PostgreSQL): +# DATABASE_URL=postgresql://user:password@ep-xxx.aws.neon.tech/dbname?sslmode=require +# +# 注意:使用数据库存储需要安装 asyncpg:pip install asyncpg +# DATABASE_URL= + +# ============================================ +# 其他配置请在管理面板的"系统设置"中配置 +# 包括:API密钥、代理、图片生成、重试策略等 +# 配置保存在 data/settings.yaml +# ============================================ + +# ============================================ +# 账户配置 +# ============================================ +# 使用 accounts.json 文件 +# 账户配置保存在 accounts.json 文件中 +# 首次启动时会自动创建空配置 +# 请在管理面板中添加账户,或直接编辑 accounts.json \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dfe0770424b2a19faf507a501ebfc23be8f54e7b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000000000000000000000000000000000000..fbe16f97d5b5c4c250fd8a0dc61336fd080d4598 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,55 @@ +name: Build Multi-Arch Docker Image + +on: + push: + branches: + - main + tags: + - "v*" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKERHUB_USERNAME }}/gemini-business2api + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + # 优化构建参数 + build-args: | + BUILDKIT_INLINE_CACHE=1 + provenance: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e8be8dd548ba4ac125c041f2af341061a794757c --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Python lib directory (but not frontend/src/lib) +/lib/ + +# Virtual Environment +venv/ +env/ +ENV/ +.venv +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Project specific +.env +*.log + +# Generated files +data/ +logs/ +static/ + +# OS +.DS_Store +Thumbs.db +old_version.py +docs/img/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..822aef26789af2630a593f49cd6e8d00679ae113 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# Stage 1: 构建前端 +FROM node:20-slim AS frontend-builder +WORKDIR /app/frontend + +# 先复制 package 文件利用 Docker 缓存 +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm install --silent + +# 复制前端源码并构建 +COPY frontend/ ./ +RUN npm run build + +# Stage 2: 最终运行时镜像 +FROM python:3.11-slim +WORKDIR /app + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + TZ=Asia/Shanghai + +# 安装 Python 依赖和浏览器依赖(合并为单一 RUN 指令以减少层数) +COPY requirements.txt . +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc \ + curl \ + tzdata \ + chromium chromium-driver \ + dbus dbus-x11 \ + xvfb xauth \ + libglib2.0-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \ + libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ + libxfixes3 libxrandr2 libgbm1 libasound2 libpango-1.0-0 \ + libcairo2 fonts-liberation fonts-noto-cjk && \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ + pip install --no-cache-dir -r requirements.txt && \ + apt-get purge -y gcc && \ + apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# 复制后端代码 +COPY main.py . +COPY core ./core +COPY util ./util +COPY scripts ./scripts + +# 从 builder 阶段只复制构建好的静态文件 +COPY --from=frontend-builder /app/static ./static + +# 创建数据目录 +RUN mkdir -p ./data + +# 复制启动脚本 +COPY entrypoint.sh . +RUN chmod +x entrypoint.sh + +# 声明数据卷 +VOLUME ["/app/data"] + +# 声明端口 +EXPOSE 7860 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD curl -f http://localhost:7860/health || exit 1 + +# 启动服务 +CMD ["./entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5fd72d27c1cc96d48a16e41c9dbc71df39749674 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 yu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e41c84f991272cb648e6665869cf22a96023128c --- /dev/null +++ b/README.md @@ -0,0 +1,388 @@ +--- +title: Gemini Business2api +emoji: ♊ +colorFrom: blue +colorTo: green +sdk: docker +pinned: false +--- + +

+ Gemini Business2API logo +

+

Gemini Business2API

+

赋予硅基生物以灵魂

+

当时明月在 · 曾照彩云归

+

+ 简体中文 | English +

+

+ +

将 Gemini Business 转换为 OpenAI 兼容接口,支持多账号负载均衡、图像生成、视频生成、多模态能力与内置管理面板。

+ +--- + +## 📜 开源协议与声明 + +**开源协议**: MIT License - 查看 [LICENSE](LICENSE) 文件了解详情 + +### ⚠️ 严禁滥用:禁止将本工具用于商业用途或任何形式的滥用(无论规模大小) + +**本工具严禁用于以下行为:** +- 商业用途或盈利性使用 +- 任何形式的批量操作或自动化滥用(无论规模大小) +- 破坏市场秩序或恶意竞争 +- 违反 Google 服务条款的任何行为 +- 违反 Microsoft 服务条款的任何行为 + +**违规后果**:滥用行为可能导致账号永久封禁、法律追责,一切后果由使用者自行承担。 + +**合法用途**:本项目仅限个人学习、技术研究与非商业性技术交流。 + +📖 **完整声明与免责条款**:[DISCLAIMER.md](docs/DISCLAIMER.md) + +--- + +## ✨ 功能特性 + +- ✅ OpenAI API 完全兼容 - 无缝对接现有工具 +- ✅ 多账号负载均衡 - 轮询与故障自动切换 +- ✅ 自动化账号管理 - 支持自动注册与登录,集成多种临时邮箱,支持无头浏览器模式 +- ✅ 流式输出 - 实时响应 +- ✅ 多模态输入 - 100+ 文件类型(图片、PDF、Office 文档、音频、视频、代码等) +- ✅ 图片生成 & 图生图 - 模型可配置,Base64 或 URL 返回 +- ✅ 视频生成 - 专用模型,支持 HTML/URL/Markdown 输出格式 +- ✅ 智能文件处理 - 自动识别文件类型,支持 URL 与 Base64 +- ✅ 日志与监控 - 实时状态与统计信息 +- ✅ 代理支持 - 通过设置面板配置 +- ✅ 内置管理面板 - 在线配置与账号管理 +- ✅ PostgreSQL / SQLite 存储 - 账户/设置/统计持久化 + +## 🤖 模型功能 + +| 模型ID | 识图 | 原生联网 | 文件多模态 | 图片生成 | 视频生成 | +| ------------------------ | ---- | -------- | ---------- | -------- | -------- | +| `gemini-auto` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-2.5-flash` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-2.5-pro` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-3-flash-preview` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-3-pro-preview` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-3.1-pro-preview` | ✅ | ✅ | ✅ | 可选 | - | +| `gemini-imagen` | ✅ | ✅ | ✅ | ✅ | - | +| `gemini-veo` | ✅ | ✅ | ✅ | - | ✅ | + +> `gemini-imagen`:专用图片生成模型 · `gemini-veo`:专用视频生成模型 + +--- + +## 🚀 快速开始 + +### 方式一:Docker Compose(推荐) + +**支持 ARM64 和 AMD64 架构** + +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +cp .env.example .env +# 编辑 .env 设置 ADMIN_KEY + +docker-compose up -d + +# 查看日志 +docker-compose logs -f + +# 更新到最新版本 +docker-compose pull && docker-compose up -d +``` + +--- + +### 方式二:安装脚本 + +> **前置要求**:Git、Node.js & npm(构建前端用)。脚本会自动安装 Python 3.11 和 uv。 + +**Linux / macOS / WSL:** +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +bash setup.sh +# 编辑 .env 设置 ADMIN_KEY +source .venv/bin/activate +python main.py +# pm2 后台运行 +pm2 start main.py --name gemini-api --interpreter ./.venv/bin/python3 +``` + +**Windows:** +```cmd +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +setup.bat +# 编辑 .env 设置 ADMIN_KEY +.venv\Scripts\activate.bat +python main.py +# pm2 后台运行 +pm2 start main.py --name gemini-api --interpreter ./.venv/Scripts/python.exe +``` + +安装脚本会自动完成:uv 安装、Python 3.11 下载、依赖安装、前端构建、`.env` 创建。 +更新项目时重新运行同一脚本即可。 + +--- + +### 方式三:手动部署 + +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api + +curl -LsSf https://astral.sh/uv/install.sh | sh +uv python install 3.11 + +cd frontend && npm install && npm run build && cd .. + +uv venv --python 3.11 .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate.bat +uv pip install -r requirements.txt + +cp .env.example .env +# 编辑 .env 设置 ADMIN_KEY +python main.py +``` + +--- + +### 访问方式 + +- **管理面板**:`http://localhost:7860/`(使用 `ADMIN_KEY` 登录) +- **API 接口**:`http://localhost:7860/v1/chat/completions` + +--- + +## 🗄️ 数据库持久化 + +设置 `DATABASE_URL` 可将账户、设置、统计写入数据库,避免容器重启丢数据。未设置时自动使用 SQLite(本地 `data.db`)。 + +**配置方式:** +- 本地部署 → 写入 `.env` +- 云平台 → 在平台环境变量中设置 + +``` +DATABASE_URL=postgresql://user:password@host/dbname?sslmode=require +``` + +**免费 PostgreSQL 推荐:** + +| 服务 | 免费额度 | 获取方式 | +|------|---------|---------| +| [Neon](https://neon.tech) | 512MB 存储 / 100 CPUH 月 | 注册 → Create Project → 复制 Connection string | +| [Aiven](https://aiven.io) | 额度更充裕 | 注册 → 创建 PostgreSQL 服务 → 复制连接串 | + +> `postgres://` 和 `postgresql://` 两种格式均可直接使用,无需手动转换。 + +
+⚠️ 常见问题:定期保存失败 / ConnectionDoesNotExistError + +如果日志出现类似以下错误: + +``` +ERROR [COOLDOWN] 冷却期保存失败: connection was closed in the middle of operation +asyncpg.exceptions.ConnectionDoesNotExistError: connection was closed in the middle of operation +``` + +这是因为部分免费 PostgreSQL 服务(如 Aiven 免费版)会主动关闭长时间空闲的连接。**不影响正常使用**,下次操作会自动重新连接。如频繁出现,建议换用 [Neon](https://neon.tech) 或升级数据库套餐。 + +
+ +
+📦 数据库迁移(从旧版升级) + +如果有旧的本地文件(`accounts.json` / `settings.yaml` / `stats.json`),运行迁移脚本: + +```bash +python scripts/migrate_to_database.py +``` + +迁移脚本会自动检测环境(PostgreSQL / SQLite),迁移完成后自动重命名旧文件。 + + +
+ +--- + +## 📡 API 接口 + +完全兼容 OpenAI API 格式,可直接对接 ChatGPT-Next-Web、LobeChat、OpenCat 等客户端。 + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/v1/chat/completions` | POST | 对话补全(支持流式) | +| `/v1/models` | GET | 获取可用模型列表 | +| `/v1/images/generations` | POST | 图片生成(文生图) | +| `/v1/images/edits` | POST | 图片编辑(图生图) | +| `/health` | GET | 健康检查 | + +**调用示例:** + +```bash +curl http://localhost:7860/v1/chat/completions \ + -H "Authorization: Bearer your-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [{"role": "user", "content": "你好"}], + "stream": true + }' +``` + +> `API_KEY` 在管理面板 → 系统设置中配置,留空则公开访问,支持多个 Key 逗号分隔。 + +--- + +## 📧 邮箱提供商配置 + +项目支持 4 种临时邮箱,用于自动注册账号。在 **管理面板 → 系统设置 → 临时邮箱提供商** 中切换。 + +### Moemail(默认推荐) + +开源临时邮箱服务,开箱即用。 + +- **项目地址**:[github.com/beilunyang/moemail](https://github.com/beilunyang/moemail) +- **官网**:[moemail.app](https://moemail.app) +- **配置项**:API 地址 + API Key + 域名(可选) + +### DuckMail + +临时邮箱 API 服务,推荐配置自定义域名。 + +- **域名管理**:[domain.duckmail.sbs](https://domain.duckmail.sbs/) +- **配置项**:API 地址 + API Key + 注册域名 + +### GPTMail + +临时邮箱 API 服务,无需密码即可使用。 + +- **默认地址**:`https://mail.chatgpt.org.uk` +- **默认 API Key**:`gpt-test` +- **配置项**:API 地址 + API Key + 域名(可选) + +### Freemail + +需要自行搭建的临时邮箱服务,适合有服务器的用户。 + +- **项目地址**:[github.com/idinging/freemail](https://github.com/idinging/freemail) +- **配置项**:自部署服务地址 + JWT Token + 域名(可选) + +> **提示**:所有邮箱配置均在管理面板中完成,无需手动编辑配置文件。Microsoft 邮箱登录也在管理面板中操作。 + +--- + +## 🌐 推荐部署平台 + +除本地 Docker Compose 外,以下平台均支持 Docker 镜像部署: + +| 平台 | 免费额度 | 特点 | +|------|---------|------| +| [Render](https://render.com) | ✅ 有 | 支持 Docker、自动 SSL、免费 PostgreSQL | +| [Railway](https://railway.app) | $5/月额度 | 一键 Docker 部署、自带数据库 | +| [Fly.io](https://fly.io) | ✅ 有 | 全球边缘部署、支持持久化卷 | +| [Claw Cloud](https://claw.cloud) | ✅ 有 | 容器云平台,简单易用 | +| 自建 VPS(推荐) | — | 完全可控,配合 Docker Compose | + +> Docker 镜像:`cooooookk/gemini-business2api:latest` +> +> 部署时设置环境变量 `ADMIN_KEY` 和 `DATABASE_URL` 即可。 + +### Zeabur 部署教程 + +1. Fork 本仓库到你的 GitHub +2. 登录 [Zeabur](https://zeabur.com) → **创建项目** → **共享集群** → **部署新服务** → **连接 GitHub** → 选择 Fork 的仓库 +3. 添加环境变量: + + | 变量名 | 必填 | 说明 | + |--------|------|------| + | `ADMIN_KEY` | ✅ | 管理面板登录密钥 | + | `DATABASE_URL` | 可选 | PostgreSQL 连接串(推荐配置,避免重启丢数据) | + +4. **持久化挂载目录**(重要): + + 在服务设置中添加持久化存储: + + | 硬盘 ID | 挂载目录 | + |---------|---------| + | `data` | `/app/data` | + +5. 点击 **重新部署** 使配置生效 + +**更新方式**:GitHub 仓库 → **Sync fork** → **Update branch**,Zeabur 会自动重新部署。 + +--- + +## 🔄 独立刷新服务 + +如果需要将账号刷新服务单独部署(与主 API 分离),可使用 [`refresh-worker` 分支](https://github.com/Dreamy-rain/gemini-business2api/tree/refresh-worker): + +```bash +git clone -b refresh-worker https://github.com/Dreamy-rain/gemini-business2api.git gemini-refresh-worker +cd gemini-refresh-worker +cp .env.example .env +# 编辑 .env 设置 DATABASE_URL +docker-compose up -d +``` + +该服务从数据库读取账号,独立执行定时刷新,支持 cron 调度、分批执行、冷却防重复。适合需要刷新服务与 API 服务分离部署的场景。 + +--- + +## 🌐 Socks5 免费代理池 + +自动注册/刷新账号时可配置代理以提高成功率。推荐使用免费 Socks5 代理池: + +- **项目地址**:[github.com/Dreamy-rain/socks5-proxy](https://github.com/Dreamy-rain/socks5-proxy) +- **说明**:免费代理不太稳定,但能一定程度提高注册成功率 +- **使用方式**:在管理面板 → 系统设置 → 代理设置中配置 + +--- + +## 📸 功能展示 + +### 管理系统 + + + + + + + + + + + + + + +
管理系统 1管理系统 2
管理系统 3管理系统 4
管理系统 5管理系统 6
+ +### 图片效果 + + + + + + + + + + +
图片效果 1图片效果 2
图片效果 3图片效果 4
+ +### 更多文档 + +- 支持的文件类型:[SUPPORTED_FILE_TYPES.md](docs/SUPPORTED_FILE_TYPES.md) + +## ⭐ Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=Dreamy-rain/gemini-business2api&type=date&legend=top-left)](https://www.star-history.com/#Dreamy-rain/gemini-business2api&type=date&legend=top-left) + +**如果这个项目对你有帮助,请给个 ⭐ Star!** diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c6411600c5efd09ce138ea8bc3ff0b7cfe950b35 --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,6 @@ +""" +Core 模块 +包含认证、模板生成等核心功能 +""" + +__all__ = ['auth', 'templates'] diff --git a/core/account.py b/core/account.py new file mode 100644 index 0000000000000000000000000000000000000000..63fabc7d0202ae9fc58a12e3f9fa9d5d61a5a064 --- /dev/null +++ b/core/account.py @@ -0,0 +1,1185 @@ +"""账户管理模块 + +负责账户配置、多账户协调和会话缓存管理 +""" +import asyncio +import json +import logging +import os +import random +import threading +import time +from dataclasses import dataclass +from datetime import datetime, timedelta, timezone +from typing import Dict, List, Optional, TYPE_CHECKING, Iterable + +from fastapi import HTTPException + +# 导入存储层(支持数据库) +from core import storage + +if TYPE_CHECKING: + from core.jwt import JWTManager + +logger = logging.getLogger(__name__) + +# HTTP错误名称映射 +HTTP_ERROR_NAMES = { + 400: "参数错误", + 401: "认证错误", + 403: "权限错误", + 429: "限流", + 502: "网关错误", + 503: "服务不可用" +} + +# 配额类型定义 +QUOTA_TYPES = { + "text": "对话", + "images": "绘图", + "videos": "视频" +} + +@dataclass +class AccountConfig: + """单个账户配置""" + account_id: str + secure_c_ses: str + host_c_oses: Optional[str] + csesidx: str + config_id: str + expires_at: Optional[str] = None # 账户过期时间 (格式: "2025-12-23 10:59:21") + disabled: bool = False # 手动禁用状态 + mail_provider: Optional[str] = None + mail_address: Optional[str] = None + mail_password: Optional[str] = None + mail_client_id: Optional[str] = None + mail_refresh_token: Optional[str] = None + mail_tenant: Optional[str] = None + # 邮箱自定义配置字段(用于账户级别的邮箱服务配置) + mail_base_url: Optional[str] = None + mail_jwt_token: Optional[str] = None + mail_verify_ssl: Optional[bool] = None + mail_domain: Optional[str] = None + mail_api_key: Optional[str] = None + trial_end: Optional[str] = None # 试用到期日 (格式: "2026-03-25",独立于cookie过期) + + def get_remaining_hours(self) -> Optional[float]: + """计算账户剩余小时数""" + if not self.expires_at: + return None + try: + # 解析过期时间(假设为北京时间) + beijing_tz = timezone(timedelta(hours=8)) + expire_time = datetime.strptime(self.expires_at, "%Y-%m-%d %H:%M:%S") + expire_time = expire_time.replace(tzinfo=beijing_tz) + + # 当前时间(北京时间) + now = datetime.now(beijing_tz) + + # 计算剩余时间 + remaining = (expire_time - now).total_seconds() / 3600 + return remaining + except Exception: + return None + + def is_expired(self) -> bool: + """检查账户是否已过期""" + remaining = self.get_remaining_hours() + if remaining is None: + return False # 未设置过期时间,默认不过期 + return remaining <= 0 + + def get_trial_days_remaining(self) -> Optional[int]: + """计算试用期剩余天数(基于 trial_end 字段)""" + if not self.trial_end: + return None + try: + beijing_tz = timezone(timedelta(hours=8)) + end_date = datetime.strptime(self.trial_end, "%Y-%m-%d") + end_date = end_date.replace(tzinfo=beijing_tz) + now = datetime.now(beijing_tz) + remaining = (end_date.date() - now.date()).days + return max(0, remaining) + except Exception: + return None + + +@dataclass(frozen=True) +class CooldownConfig: + text: int + images: int + videos: int + + +@dataclass(frozen=True) +class RetryPolicy: + cooldowns: CooldownConfig + + +def format_account_expiration(remaining_hours: Optional[float]) -> tuple: + """ + 格式化账户过期时间显示(基于12小时过期周期) + + Args: + remaining_hours: 剩余小时数(None表示未设置过期时间) + + Returns: + (status, status_color, expire_display) 元组 + """ + if remaining_hours is None: + # 未设置过期时间时显示为"未设置" + return ("未设置", "#9e9e9e", "未设置") + elif remaining_hours <= 0: + return ("已过期", "#f44336", "已过期") + elif remaining_hours < 3: # 少于3小时 + return ("即将过期", "#ff9800", f"{remaining_hours:.1f} 小时") + else: # 3小时及以上,统一显示小时 + return ("正常", "#4caf50", f"{remaining_hours:.1f} 小时") + + +class AccountManager: + """单个账户管理器""" + def __init__( + self, + config: AccountConfig, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + ): + self.config = config + self.http_client = http_client + self.user_agent = user_agent + # 冷却时间配置 + self.rate_limit_cooldown_seconds = retry_policy.cooldowns.text # 向后兼容 + self.text_rate_limit_cooldown_seconds = retry_policy.cooldowns.text + self.images_rate_limit_cooldown_seconds = retry_policy.cooldowns.images + self.videos_rate_limit_cooldown_seconds = retry_policy.cooldowns.videos + self.jwt_manager: Optional['JWTManager'] = None # 延迟初始化 + self.is_available = True + self.last_error_time = 0.0 # 保留用于统计 + self.quota_cooldowns: Dict[str, float] = {} # 按配额类型的冷却时间戳 + self.daily_usage: Dict[str, int] = {"text": 0, "images": 0, "videos": 0} # 每日使用计数 + self.daily_usage_date: str = "" # 计数日期(北京时间,格式 "2026-02-24") + self.conversation_count = 0 # 累计成功次数(用于统计展示) + self.failure_count = 0 # 累计失败次数(用于统计展示) + self.session_usage_count = 0 # 本次启动后使用次数(用于均衡轮询) + self.disabled_reason: Optional[str] = None # 自动禁用原因(如 "403 Access Restricted") + + def handle_non_http_error(self, error_context: str = "", request_id: str = "", quota_type: Optional[str] = None) -> None: + """ + 统一处理非HTTP错误(网络错误、解析错误等)- 只记录日志,不触发冷却 + + Args: + error_context: 错误上下文(如"JWT获取"、"聊天请求") + request_id: 请求ID(用于日志) + quota_type: 配额类型(保留参数以保持接口兼容性) + + 注意:网络错误、超时等是临时问题,应该直接切换账户重试,不标记配额冷却 + """ + req_tag = f"[req_{request_id}] " if request_id else "" + + # 只记录日志,不触发冷却 + # 网络错误是临时的,应该直接切换账户重试 + logger.warning( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"{error_context}失败,将切换账户重试(不触发冷却)" + ) + + def _get_quota_cooldown_seconds(self, quota_type: Optional[str]) -> int: + if quota_type == "images": + return self.images_rate_limit_cooldown_seconds + if quota_type == "videos": + return self.videos_rate_limit_cooldown_seconds + return self.text_rate_limit_cooldown_seconds + + def apply_retry_policy(self, retry_policy: RetryPolicy) -> None: + """Apply updated retry policy to this account manager.""" + self.rate_limit_cooldown_seconds = retry_policy.cooldowns.text # 向后兼容 + self.text_rate_limit_cooldown_seconds = retry_policy.cooldowns.text + self.images_rate_limit_cooldown_seconds = retry_policy.cooldowns.images + self.videos_rate_limit_cooldown_seconds = retry_policy.cooldowns.videos + + def _get_quota_period(self) -> str: + """获取当前配额周期标识(北京时间16:00为分界,对齐Google太平洋时间午夜重置)""" + beijing_tz = timezone(timedelta(hours=8)) + now = datetime.now(beijing_tz) + # 16:00前属于前一天的配额周期,16:00后属于当天的配额周期 + if now.hour < 16: + period_date = now.date() - timedelta(days=1) + else: + period_date = now.date() + return period_date.strftime("%Y-%m-%d") + + def _reset_daily_usage_if_needed(self) -> None: + """跨配额周期自动重置每日计数器(懒重置,北京时间16:00刷新)""" + period = self._get_quota_period() + if self.daily_usage_date != period: + self.daily_usage = {"text": 0, "images": 0, "videos": 0} + self.daily_usage_date = period + + def increment_daily_usage(self, quota_type: str) -> None: + """请求成功后增加每日使用计数""" + if quota_type not in QUOTA_TYPES: + return + self._reset_daily_usage_if_needed() + self.daily_usage[quota_type] += 1 + + def handle_http_error(self, status_code: int, error_detail: str = "", request_id: str = "", quota_type: Optional[str] = None) -> None: + """ + 统一处理HTTP错误 - 按错误类型分类处理 + + Args: + status_code: HTTP状态码 + error_detail: 错误详情 + request_id: 请求ID(用于日志) + quota_type: 配额类型("text", "images", "videos"),用于按类型冷却 + + 处理逻辑: + - 400: 参数错误,不计入失败(客户端问题) + - 401/403: 认证错误,冷却 text 配额(等效冷却整个账户) + - 429: 按配额类型冷却(配额耗尽) + - 502/503/504/其他: 只记录日志,不触发冷却(临时服务器错误,应直接切换账户重试) + """ + req_tag = f"[req_{request_id}] " if request_id else "" + + # 400参数错误:不计入失败(客户端问题) + if status_code == 400: + logger.warning( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"HTTP 400参数错误(不计入失败){': ' + error_detail[:100] if error_detail else ''}" + ) + return + + # 403权限错误:Google 返回 403 意味着账户被限制访问,自动禁用 + # (JWT 刷新或 API 调用返回 403 都是永久性封禁,非临时问题) + if status_code == 403: + self.config.disabled = True + self.disabled_reason = "403 Access Restricted" + logger.error( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"⛔ 账户遇到 403 权限错误,已自动禁用" + f"{': ' + error_detail[:200] if error_detail else ''}" + ) + return + + # 401认证错误:冷却 text 配额(等效冷却整个账户,但可自动恢复) + if status_code == 401: + self.quota_cooldowns["text"] = time.time() + cooldown_seconds = self.text_rate_limit_cooldown_seconds + logger.warning( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"遇到认证错误,账户将休息{cooldown_seconds}秒后自动恢复" + f"{': ' + error_detail[:100] if error_detail else ''}" + ) + return + + # 429配额错误:按配额类型冷却 + if status_code == 429: + if not quota_type or quota_type not in QUOTA_TYPES: + quota_type = "text" + + self.quota_cooldowns[quota_type] = time.time() + cooldown_seconds = self._get_quota_cooldown_seconds(quota_type) + logger.warning( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"遇到429配额错误,{QUOTA_TYPES[quota_type]}配额将休息{cooldown_seconds}秒后自动恢复" + f"{': ' + error_detail[:100] if error_detail else ''}" + ) + return + + # 502/503/504/其他错误:只记录日志,不触发冷却 + # 这些是临时服务器错误,应该直接重试切换账户,不标记配额 + error_type = HTTP_ERROR_NAMES.get(status_code, f"HTTP {status_code}") + logger.warning( + f"[ACCOUNT] [{self.config.account_id}] {req_tag}" + f"遇到{error_type}错误,将切换账户重试(不触发冷却)" + f"{': ' + error_detail[:100] if error_detail else ''}" + ) + + def is_quota_available(self, quota_type: str) -> bool: + """检查指定配额是否可用(先检查每日上限,再检查冷却)。""" + if quota_type not in QUOTA_TYPES: + return True + + # 主动配额计数检查 + from core.config import config + quota_limits = config.quota_limits + if quota_limits.enabled: + self._reset_daily_usage_if_needed() + limit = getattr(quota_limits, f"{quota_type}_daily_limit", 0) + if limit > 0 and self.daily_usage.get(quota_type, 0) >= limit: + return False + + # 被动冷却检查(兜底) + cooldown_time = self.quota_cooldowns.get(quota_type) + if not cooldown_time: + return True + + elapsed = time.time() - cooldown_time + cooldown_seconds = self._get_quota_cooldown_seconds(quota_type) + if elapsed < cooldown_seconds: + return False + + # 冷却已过期,清理 + del self.quota_cooldowns[quota_type] + return True + + def are_quotas_available(self, quota_types: Optional[Iterable[str]] = None) -> bool: + """ + 检查多个配额类型是否都可用。 + + 注意:如果对话配额受限,所有配额都不可用(对话是基础功能) + """ + if not quota_types: + return True + if isinstance(quota_types, str): + quota_types = [quota_types] + + # 如果对话配额受限,所有配额都不可用 + if not self.is_quota_available("text"): + return False + + # 检查其他配额 + return all(self.is_quota_available(qt) for qt in quota_types if qt != "text") + + async def get_jwt(self, request_id: str = "") -> str: + """获取 JWT token (带错误处理)""" + # 检查账户是否过期 + if self.config.is_expired(): + self.is_available = False + logger.warning(f"[ACCOUNT] [{self.config.account_id}] 账户已过期,已自动禁用") + raise HTTPException(403, f"Account {self.config.account_id} has expired") + + try: + if self.jwt_manager is None: + # 延迟初始化 JWTManager (避免循环依赖) + from core.jwt import JWTManager + self.jwt_manager = JWTManager(self.config, self.http_client, self.user_agent) + jwt = await self.jwt_manager.get(request_id) + self.is_available = True + return jwt + except Exception as e: + # 使用统一的错误处理入口 + if isinstance(e, HTTPException): + self.handle_http_error(e.status_code, str(e.detail) if hasattr(e, 'detail') else "", request_id) + else: + self.handle_non_http_error("JWT获取", request_id) + raise + + def should_retry(self) -> bool: + """检查账户是否可重试 - 简化版:账户始终可用(由配额冷却控制)""" + # 账户本身始终可用,具体功能由配额冷却控制 + return True + + def get_cooldown_info(self) -> tuple[int, str | None]: + """获取账户冷却信息(只有配额冷却)""" + current_time = time.time() + + # 检查配额冷却(找出最长的剩余冷却时间) + max_quota_remaining = 0 + limited_quota_types = [] # 存储配额类型(text/images/videos) + quota_icons = {"text": "💬", "images": "🎨", "videos": "🎬"} + + for quota_type in QUOTA_TYPES: + if quota_type in self.quota_cooldowns: + cooldown_time = self.quota_cooldowns[quota_type] + elapsed = current_time - cooldown_time + cooldown_seconds = self._get_quota_cooldown_seconds(quota_type) + if elapsed < cooldown_seconds: + remaining = int(cooldown_seconds - elapsed) + if remaining > max_quota_remaining: + max_quota_remaining = remaining + limited_quota_types.append(quota_type) + + # 如果有配额冷却,返回最长的冷却时间和简化的描述 + if max_quota_remaining > 0: + # 生成 emoji 图标组合 + icons = "".join([quota_icons[qt] for qt in limited_quota_types]) + + # 判断是否全部冷却 + if len(limited_quota_types) == 3: + return (max_quota_remaining, f"{icons} 全部冷却") + elif len(limited_quota_types) == 1: + # 单个配额冷却 + quota_name = QUOTA_TYPES[limited_quota_types[0]] + return (max_quota_remaining, f"{icons} {quota_name}冷却") + else: + # 多个配额冷却(但不是全部) + quota_names = "/".join([QUOTA_TYPES[qt] for qt in limited_quota_types]) + return (max_quota_remaining, f"{icons} {quota_names}冷却") + + # 没有冷却,返回正常状态 + return (0, None) + + def get_quota_status(self) -> Dict[str, any]: + """ + 获取配额状态(被动检测 + 主动计数) + + Returns: + { + "quotas": { + "text": {"available": bool, "remaining_seconds": int, "daily_used": int, "daily_limit": int}, + "images": {"available": bool, "remaining_seconds": int, "daily_used": int, "daily_limit": int}, + "videos": {"available": bool, "remaining_seconds": int, "daily_used": int, "daily_limit": int} + }, + "limited_count": int, # 受限配额数量 + "total_count": int, # 总配额数量 + "is_expired": bool # 账户是否过期/禁用 + } + """ + # 获取配额上限配置 + from core.config import config as app_config + quota_limits = app_config.quota_limits + + # 检查账户是否过期或被禁用 + is_expired = self.config.is_expired() or self.config.disabled + if is_expired: + # 账户过期或被禁用,所有配额不可用 + quotas = {quota_type: {"available": False} for quota_type in QUOTA_TYPES} + return { + "quotas": quotas, + "limited_count": len(QUOTA_TYPES), + "total_count": len(QUOTA_TYPES), + "is_expired": True + } + + current_time = time.time() + self._reset_daily_usage_if_needed() + + quotas = {} + limited_count = 0 + expired_quotas = [] # 收集已过期的配额类型 + text_limited = False # 对话配额是否受限 + + # 第一遍:检查所有配额状态 + for quota_type in QUOTA_TYPES: + quota_info: Dict[str, any] = {} + + # 添加每日使用量信息 + if quota_limits.enabled: + daily_limit = getattr(quota_limits, f"{quota_type}_daily_limit", 0) + quota_info["daily_used"] = self.daily_usage.get(quota_type, 0) + quota_info["daily_limit"] = daily_limit + + # 检查每日上限 + if daily_limit > 0 and quota_info["daily_used"] >= daily_limit: + quota_info["available"] = False + quota_info["reason"] = "每日配额已用完" + limited_count += 1 + if quota_type == "text": + text_limited = True + quotas[quota_type] = quota_info + continue + + # 检查被动冷却 + if quota_type in self.quota_cooldowns: + cooldown_time = self.quota_cooldowns[quota_type] + elapsed = current_time - cooldown_time + cooldown_seconds = self._get_quota_cooldown_seconds(quota_type) + if elapsed < cooldown_seconds: + remaining = int(cooldown_seconds - elapsed) + quota_info["available"] = False + quota_info["remaining_seconds"] = remaining + limited_count += 1 + if quota_type == "text": + text_limited = True + quotas[quota_type] = quota_info + continue + else: + expired_quotas.append(quota_type) + + quota_info["available"] = True + quotas[quota_type] = quota_info + + # 统一删除已过期的配额冷却 + for quota_type in expired_quotas: + del self.quota_cooldowns[quota_type] + + # 如果对话配额受限,所有配额都标记为不可用(对话是基础功能) + if text_limited: + for quota_type in QUOTA_TYPES: + if quota_type != "text" and quotas[quota_type].get("available", False): + quotas[quota_type]["available"] = False + quotas[quota_type]["reason"] = "对话配额受限" + limited_count += 1 + + return { + "quotas": quotas, + "limited_count": limited_count, + "total_count": len(QUOTA_TYPES), + "is_expired": False + } + + +class MultiAccountManager: + """多账户协调器""" + def __init__(self, session_cache_ttl_seconds: int): + self.accounts: Dict[str, AccountManager] = {} + self.account_list: List[str] = [] # 账户ID列表 (用于轮询) + self.current_index = 0 + self._cache_lock = asyncio.Lock() # 缓存操作专用锁 + self._counter_lock = threading.Lock() # 轮询计数器锁 + self._request_counter = 0 # 请求计数器 + self._last_account_count = 0 # 可用账户数量 + # 全局会话缓存:{conv_key: {"account_id": str, "session_id": str, "updated_at": float}} + self.global_session_cache: Dict[str, dict] = {} + self.cache_max_size = 1000 # 最大缓存条目数 + self.cache_ttl = session_cache_ttl_seconds # 缓存过期时间(秒) + # Session级别锁:防止同一对话的并发请求冲突 + self._session_locks: Dict[str, asyncio.Lock] = {} + self._session_locks_lock = asyncio.Lock() # 保护锁字典的锁 + self._session_locks_max_size = 2000 # 最大锁数量 + + def _clean_expired_cache(self): + """清理过期的缓存条目""" + current_time = time.time() + expired_keys = [ + key for key, value in self.global_session_cache.items() + if current_time - value["updated_at"] > self.cache_ttl + ] + for key in expired_keys: + del self.global_session_cache[key] + if expired_keys: + logger.info(f"[CACHE] 清理 {len(expired_keys)} 个过期会话缓存") + + def _ensure_cache_size(self): + """确保缓存不超过最大大小(LRU策略)""" + if len(self.global_session_cache) > self.cache_max_size: + # 按更新时间排序,删除最旧的20% + sorted_items = sorted( + self.global_session_cache.items(), + key=lambda x: x[1]["updated_at"] + ) + remove_count = len(sorted_items) - int(self.cache_max_size * 0.8) + for key, _ in sorted_items[:remove_count]: + del self.global_session_cache[key] + logger.info(f"[CACHE] LRU清理 {remove_count} 个最旧会话缓存") + + async def start_background_cleanup(self): + """启动后台缓存清理任务(每5分钟执行一次)""" + try: + while True: + await asyncio.sleep(300) # 5分钟 + async with self._cache_lock: + self._clean_expired_cache() + self._ensure_cache_size() + except asyncio.CancelledError: + logger.info("[CACHE] 后台清理任务已停止") + except Exception as e: + logger.error(f"[CACHE] 后台清理任务异常: {e}") + + async def set_session_cache(self, conv_key: str, account_id: str, session_id: str): + """线程安全地设置会话缓存""" + async with self._cache_lock: + self.global_session_cache[conv_key] = { + "account_id": account_id, + "session_id": session_id, + "updated_at": time.time() + } + # 检查缓存大小 + self._ensure_cache_size() + + async def update_session_time(self, conv_key: str): + """线程安全地更新会话时间戳""" + async with self._cache_lock: + if conv_key in self.global_session_cache: + self.global_session_cache[conv_key]["updated_at"] = time.time() + + async def acquire_session_lock(self, conv_key: str) -> asyncio.Lock: + """获取指定对话的锁(用于防止同一对话的并发请求冲突)""" + async with self._session_locks_lock: + # 清理过多的锁(LRU策略:删除不在缓存中的锁) + if len(self._session_locks) > self._session_locks_max_size: + # 只保留当前缓存中存在的锁 + valid_keys = set(self.global_session_cache.keys()) + keys_to_remove = [k for k in self._session_locks if k not in valid_keys] + for k in keys_to_remove[:len(keys_to_remove)//2]: # 删除一半无效锁 + del self._session_locks[k] + + if conv_key not in self._session_locks: + self._session_locks[conv_key] = asyncio.Lock() + return self._session_locks[conv_key] + + def update_http_client(self, http_client): + """更新所有账户使用的 http_client(用于代理变更后重建客户端)""" + for account_mgr in self.accounts.values(): + account_mgr.http_client = http_client + if account_mgr.jwt_manager is not None: + account_mgr.jwt_manager.http_client = http_client + + def add_account( + self, + config: AccountConfig, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + global_stats: dict, + ): + """添加账户""" + manager = AccountManager(config, http_client, user_agent, retry_policy) + # 从统计数据加载对话次数 + if "account_conversations" in global_stats: + manager.conversation_count = global_stats["account_conversations"].get(config.account_id, 0) + if "account_failures" in global_stats: + manager.failure_count = global_stats["account_failures"].get(config.account_id, 0) + self.accounts[config.account_id] = manager + self.account_list.append(config.account_id) + logger.debug(f"[MULTI] [ACCOUNT] 添加账户: {config.account_id}") + + def get_available_accounts( + self, + required_quota_types: Optional[Iterable[str]] = None + ) -> List[AccountManager]: + """获取可用账户列表(过滤掉禁用、过期、冷却中的账户) + + Args: + required_quota_types: 需要的配额类型列表(如 ["text"], ["images"], ["text", "videos"]) + + Returns: + 可用账户列表 + + 过滤规则: + 1. disabled=True → 跳过(手动禁用) + 2. is_expired() → 跳过(账户过期) + 3. are_quotas_available() → 跳过(配额冷却中) + """ + available = [] + + for acc in self.accounts.values(): + # 1. 检查手动禁用 + if acc.config.disabled: + continue + + # 2. 检查账户过期 + if acc.config.is_expired(): + continue + + # 3. 检查配额可用性(包括冷却检查) + if not acc.are_quotas_available(required_quota_types): + continue + + available.append(acc) + + return available + + async def get_account( + self, + account_id: Optional[str] = None, + request_id: str = "", + required_quota_types: Optional[Iterable[str]] = None + ) -> AccountManager: + """获取账户 - Round-Robin轮询 + + Args: + account_id: 指定账户ID(可选,如果指定则直接返回该账户) + request_id: 请求ID(用于日志) + required_quota_types: 需要的配额类型列表 + + Returns: + 可用的账户管理器 + + Raises: + HTTPException(404): 指定的账户不存在 + HTTPException(503): 没有可用账户 + """ + req_tag = f"[req_{request_id}] " if request_id else "" + + # 指定账户ID时直接返回 + if account_id: + if account_id not in self.accounts: + raise HTTPException(404, f"Account {account_id} not found") + account = self.accounts[account_id] + if not account.should_retry(): + raise HTTPException(503, f"Account {account_id} temporarily unavailable") + if not account.are_quotas_available(required_quota_types): + raise HTTPException(503, f"Account {account_id} quota temporarily unavailable") + return account + + # 获取可用账户列表 + available_accounts = self.get_available_accounts(required_quota_types) + + if not available_accounts: + raise HTTPException(503, "No available accounts") + + # 轮询选择 + with self._counter_lock: + if len(available_accounts) != self._last_account_count: + self._request_counter = random.randint(0, 999999) + self._last_account_count = len(available_accounts) + index = self._request_counter % len(available_accounts) + self._request_counter += 1 + + selected = available_accounts[index] + selected.session_usage_count += 1 + + logger.info(f"[MULTI] [ACCOUNT] {req_tag}选择账户: {selected.config.account_id} " + f"(索引: {index}/{len(available_accounts)}, 使用: {selected.session_usage_count})") + return selected + + +# ---------- 配置管理 ---------- + +def save_accounts_to_file(accounts_data: list): + """保存账户配置(仅数据库模式)。""" + if not storage.is_database_enabled(): + raise RuntimeError("Database is not enabled") + saved = storage.save_accounts_sync(accounts_data) + if not saved: + raise RuntimeError("Database write failed") + + +def load_accounts_from_source() -> list: + """从环境变量或数据库加载账户配置。""" + env_accounts = os.environ.get('ACCOUNTS_CONFIG') + if env_accounts: + try: + accounts_data = json.loads(env_accounts) + if accounts_data: + logger.info(f"[CONFIG] 从环境变量加载配置,共 {len(accounts_data)} 个账户") + else: + logger.warning("[CONFIG] 环境变量 ACCOUNTS_CONFIG 为空") + return accounts_data + except Exception as e: + logger.error(f"[CONFIG] 环境变量加载失败: {str(e)}") + + if storage.is_database_enabled(): + try: + accounts_data = storage.load_accounts_sync() + + # 严格模式:数据库连接失败时抛出异常,阻止应用启动 + if accounts_data is None: + logger.error("[CONFIG] ❌ 数据库连接失败") + logger.error("[CONFIG] 请检查 DATABASE_URL 配置或网络连接") + raise RuntimeError("数据库连接失败,应用无法启动") + + if accounts_data: + logger.info(f"[CONFIG] 从数据库加载配置,共 {len(accounts_data)} 个账户") + else: + logger.warning("[CONFIG] 数据库中账户配置为空") + logger.warning("[CONFIG] 如需迁移数据,请运行: python scripts/migrate_to_database.py") + + return accounts_data + except RuntimeError: + # 重新抛出 RuntimeError(数据库连接失败) + raise + except Exception as e: + logger.error(f"[CONFIG] ❌ 数据库加载失败: {e}") + raise RuntimeError(f"数据库加载失败: {e}") + + logger.error("[CONFIG] 未启用数据库且未提供 ACCOUNTS_CONFIG") + return [] + + +def get_account_id(acc: dict, index: int) -> str: + """获取账户ID(有显式ID则使用,否则生成默认ID)""" + return acc.get("id", f"account_{index}") + + +def load_multi_account_config( + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats: dict +) -> MultiAccountManager: + """从文件或环境变量加载多账户配置""" + manager = MultiAccountManager(session_cache_ttl_seconds) + + accounts_data = load_accounts_from_source() + + for i, acc in enumerate(accounts_data, 1): + # 验证必需字段 + required_fields = ["secure_c_ses", "csesidx", "config_id"] + missing_fields = [f for f in required_fields if f not in acc] + if missing_fields: + raise ValueError(f"账户 {i} 缺少必需字段: {', '.join(missing_fields)}") + + config = AccountConfig( + account_id=get_account_id(acc, i), + secure_c_ses=acc["secure_c_ses"], + host_c_oses=acc.get("host_c_oses"), + csesidx=acc["csesidx"], + config_id=acc["config_id"], + expires_at=acc.get("expires_at"), + disabled=acc.get("disabled", False), # 读取手动禁用状态,默认为False + mail_provider=acc.get("mail_provider"), + mail_address=acc.get("mail_address"), + mail_password=acc.get("mail_password") or acc.get("email_password"), + mail_client_id=acc.get("mail_client_id"), + mail_refresh_token=acc.get("mail_refresh_token"), + mail_tenant=acc.get("mail_tenant"), + trial_end=acc.get("trial_end"), + ) + + # 检查账户是否已过期(已过期也加载到管理面板) + is_expired = config.is_expired() + if is_expired: + logger.debug(f"[CONFIG] 账户 {config.account_id} 已过期,仍加载用于展示") + + manager.add_account(config, http_client, user_agent, retry_policy, global_stats) + + # 从数据库恢复冷却状态和统计数据 + account_mgr = manager.accounts[config.account_id] + if "quota_cooldowns" in acc: + account_mgr.quota_cooldowns = dict(acc["quota_cooldowns"]) + if "conversation_count" in acc: + account_mgr.conversation_count = int(acc.get("conversation_count", 0)) + if "failure_count" in acc: + account_mgr.failure_count = int(acc.get("failure_count", 0)) + if "daily_usage" in acc: + account_mgr.daily_usage = dict(acc["daily_usage"]) + if "daily_usage_date" in acc: + account_mgr.daily_usage_date = str(acc.get("daily_usage_date", "")) + + if is_expired: + manager.accounts[config.account_id].is_available = False + + if not manager.accounts: + logger.warning(f"[CONFIG] 没有有效的账户配置,服务将启动但无法处理请求,请在管理面板添加账户") + else: + logger.info(f"[CONFIG] 成功加载 {len(manager.accounts)} 个账户") + return manager + + +def reload_accounts( + multi_account_mgr: MultiAccountManager, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats: dict +) -> MultiAccountManager: + """Reload account config and preserve runtime cooldown/error state.""" + # Preserve stats + runtime state to avoid clearing cooldowns on reload. + old_stats = {} + for account_id, account_mgr in multi_account_mgr.accounts.items(): + old_stats[account_id] = { + "conversation_count": account_mgr.conversation_count, + "failure_count": account_mgr.failure_count, + "is_available": account_mgr.is_available, + "last_error_time": account_mgr.last_error_time, + "session_usage_count": account_mgr.session_usage_count, + "quota_cooldowns": dict(account_mgr.quota_cooldowns), + "daily_usage": dict(account_mgr.daily_usage), + "daily_usage_date": account_mgr.daily_usage_date, + } + + # Clear session cache and reload config. + multi_account_mgr.global_session_cache.clear() + new_mgr = load_multi_account_config( + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + + # Restore stats + runtime state. + for account_id, stats in old_stats.items(): + if account_id in new_mgr.accounts: + account_mgr = new_mgr.accounts[account_id] + account_mgr.conversation_count = stats["conversation_count"] + account_mgr.failure_count = stats.get("failure_count", 0) + account_mgr.last_error_time = stats.get("last_error_time", 0.0) + account_mgr.session_usage_count = stats.get("session_usage_count", 0) + account_mgr.daily_usage = stats.get("daily_usage", {"text": 0, "images": 0, "videos": 0}) + account_mgr.daily_usage_date = stats.get("daily_usage_date", "") + + # Smart restore: consider new config's expired/disabled state + old_available = stats.get("is_available", True) + old_cooldowns = stats.get("quota_cooldowns", {}) + if account_mgr.config.is_expired() or account_mgr.config.disabled: + # Still expired/disabled → preserve old state + account_mgr.is_available = False + account_mgr.quota_cooldowns = old_cooldowns + elif not old_available and not old_cooldowns: + # Was unavailable with no cooldowns (i.e. expired/disabled), + # now recovered → mark available and clear cooldowns + account_mgr.is_available = True + account_mgr.quota_cooldowns = {} + logger.info(f"[CONFIG] Account {account_id} recovered from expired state, cooldowns cleared") + else: + # Normal case: preserve runtime state (e.g. quota cooldowns) + account_mgr.is_available = old_available + account_mgr.quota_cooldowns = old_cooldowns + + logger.debug(f"[CONFIG] Account {account_id} refreshed; runtime state preserved") + + logger.info( + f"[CONFIG] Reloaded config; accounts={len(new_mgr.accounts)}; cooldown/error state preserved" + ) + return new_mgr + + +def update_accounts_config( + accounts_data: list, + multi_account_mgr: MultiAccountManager, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats: dict +) -> MultiAccountManager: + """更新账户配置(保存到文件并重新加载)""" + save_accounts_to_file(accounts_data) + return reload_accounts( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + + +def delete_account( + account_id: str, + multi_account_mgr: MultiAccountManager, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats: dict +) -> MultiAccountManager: + """删除单个账户""" + if storage.is_database_enabled(): + deleted = storage.delete_accounts_sync([account_id]) + if deleted <= 0: + raise ValueError(f"账户 {account_id} 不存在") + return reload_accounts( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + + accounts_data = load_accounts_from_source() + + filtered = [ + acc for i, acc in enumerate(accounts_data, 1) + if get_account_id(acc, i) != account_id + ] + + if len(filtered) == len(accounts_data): + raise ValueError(f"账户 {account_id} 不存在") + + save_accounts_to_file(filtered) + return reload_accounts( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + + +def update_account_disabled_status( + account_id: str, + disabled: bool, + multi_account_mgr: MultiAccountManager, +) -> MultiAccountManager: + """更新账户的禁用状态(优化版:优先数据库直写)。""" + if storage.is_database_enabled(): + updated = storage.update_account_disabled_sync(account_id, disabled) + if not updated: + raise ValueError(f"账户 {account_id} 不存在") + if account_id in multi_account_mgr.accounts: + multi_account_mgr.accounts[account_id].config.disabled = disabled + return multi_account_mgr + + if account_id not in multi_account_mgr.accounts: + raise ValueError(f"账户 {account_id} 不存在") + account_mgr = multi_account_mgr.accounts[account_id] + account_mgr.config.disabled = disabled + + accounts_data = load_accounts_from_source() + for i, acc in enumerate(accounts_data, 1): + if get_account_id(acc, i) == account_id: + acc["disabled"] = disabled + break + + save_accounts_to_file(accounts_data) + + status_text = "已禁用" if disabled else "已启用" + logger.info(f"[CONFIG] 账户 {account_id} {status_text}") + return multi_account_mgr + + +def bulk_update_account_disabled_status( + account_ids: list[str], + disabled: bool, + multi_account_mgr: MultiAccountManager, +) -> tuple[int, list[str]]: + """批量更新账户禁用状态,单次最多20个。""" + if storage.is_database_enabled(): + updated, missing = storage.bulk_update_accounts_disabled_sync(account_ids, disabled) + for account_id in account_ids: + if account_id in multi_account_mgr.accounts: + multi_account_mgr.accounts[account_id].config.disabled = disabled + errors = [f"{account_id}: 账户不存在" for account_id in missing] + status_text = "已禁用" if disabled else "已启用" + logger.info(f"[CONFIG] 批量{status_text} {updated}/{len(account_ids)} 个账户") + return updated, errors + + success_count = 0 + errors = [] + + for account_id in account_ids: + if account_id not in multi_account_mgr.accounts: + errors.append(f"{account_id}: 账户不存在") + continue + account_mgr = multi_account_mgr.accounts[account_id] + account_mgr.config.disabled = disabled + success_count += 1 + + accounts_data = load_accounts_from_source() + account_id_set = set(account_ids) + + for i, acc in enumerate(accounts_data, 1): + acc_id = get_account_id(acc, i) + if acc_id in account_id_set: + acc["disabled"] = disabled + + save_accounts_to_file(accounts_data) + + status_text = "已禁用" if disabled else "已启用" + logger.info(f"[CONFIG] 批量{status_text} {success_count}/{len(account_ids)} 个账户") + return success_count, errors + + +def bulk_delete_accounts( + account_ids: list[str], + multi_account_mgr: MultiAccountManager, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats: dict +) -> tuple[MultiAccountManager, int, list[str]]: + """批量删除账户,单次最多20个。""" + if storage.is_database_enabled(): + existing_ids = set(multi_account_mgr.accounts.keys()) + missing = [account_id for account_id in account_ids if account_id not in existing_ids] + deleted = storage.delete_accounts_sync(account_ids) + errors = [f"{account_id}: 账户不存在" for account_id in missing] + if deleted > 0: + multi_account_mgr = reload_accounts( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + logger.info(f"[CONFIG] 批量删除 {deleted}/{len(account_ids)} 个账户") + return multi_account_mgr, deleted, errors + + errors = [] + account_id_set = set(account_ids) + + accounts_data = load_accounts_from_source() + kept: list[dict] = [] + deleted_ids: list[str] = [] + + for i, acc in enumerate(accounts_data, 1): + acc_id = get_account_id(acc, i) + if acc_id in account_id_set: + deleted_ids.append(acc_id) + continue + kept.append(acc) + + missing = account_id_set.difference(deleted_ids) + for account_id in missing: + errors.append(f"{account_id}: 账户不存在") + + if deleted_ids: + save_accounts_to_file(kept) + multi_account_mgr = reload_accounts( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats + ) + + success_count = len(deleted_ids) + logger.info(f"[CONFIG] 批量删除 {success_count}/{len(account_ids)} 个账户") + return multi_account_mgr, success_count, errors + + +async def save_account_cooldown_state(account_id: str, account_mgr: AccountManager) -> bool: + """保存单个账户的冷却状态到数据库(优化版:单条更新)""" + if not storage.is_database_enabled(): + return False + + try: + cooldown_data = { + "quota_cooldowns": dict(account_mgr.quota_cooldowns), + "conversation_count": account_mgr.conversation_count, + "failure_count": account_mgr.failure_count, + "daily_usage": dict(account_mgr.daily_usage), + "daily_usage_date": account_mgr.daily_usage_date, + } + + success = await storage.update_account_cooldown(account_id, cooldown_data) + if success: + logger.debug(f"[COOLDOWN] 账户 {account_id} 冷却状态已保存") + else: + logger.warning(f"[COOLDOWN] 账户 {account_id} 不存在") + return success + except Exception as e: + logger.error(f"[COOLDOWN] 保存账户 {account_id} 冷却状态失败: {e}") + return False + + +def save_account_cooldown_state_sync(account_id: str, account_mgr: AccountManager) -> bool: + """保存单个账户的冷却状态到数据库(同步版本)""" + try: + return asyncio.run(save_account_cooldown_state(account_id, account_mgr)) + except Exception as e: + logger.error(f"[COOLDOWN] 同步保存账户 {account_id} 冷却状态失败: {e}") + return False + + +async def save_all_cooldown_states(multi_account_mgr: MultiAccountManager) -> int: + """保存有冷却状态的账户到数据库(优化版:批量更新)""" + if not storage.is_database_enabled(): + return 0 + + # 收集需要保存的账户 + updates = [] + for account_id, account_mgr in multi_account_mgr.accounts.items(): + has_cooldown = ( + account_mgr.quota_cooldowns or + account_mgr.conversation_count > 0 or + account_mgr.failure_count > 0 or + any(v > 0 for v in account_mgr.daily_usage.values()) + ) + + if has_cooldown: + cooldown_data = { + "quota_cooldowns": dict(account_mgr.quota_cooldowns), + "conversation_count": account_mgr.conversation_count, + "failure_count": account_mgr.failure_count, + "daily_usage": dict(account_mgr.daily_usage), + "daily_usage_date": account_mgr.daily_usage_date, + } + updates.append((account_id, cooldown_data)) + + if not updates: + logger.info(f"[COOLDOWN] 无需保存:所有账户无冷却状态") + return 0 + + success_count, missing = await storage.bulk_update_accounts_cooldown(updates) + + if missing: + logger.warning(f"[COOLDOWN] {len(missing)} 个账户不存在: {missing[:5]}") + + logger.info(f"[COOLDOWN] 批量保存冷却状态: {success_count}/{len(updates)} 个账户(跳过 {len(multi_account_mgr.accounts) - len(updates)} 个无状态账户)") + return success_count + diff --git a/core/auth.py b/core/auth.py new file mode 100644 index 0000000000000000000000000000000000000000..e390f784ecd671f891aab79cf7bf30e28a3bf231 --- /dev/null +++ b/core/auth.py @@ -0,0 +1,53 @@ +""" +API认证模块 +提供API Key验证功能(用于API端点) +管理端点使用Session认证(见core/session_auth.py) +""" +from typing import Optional +from fastapi import HTTPException + + +def verify_api_key(api_key_value: str, authorization: Optional[str] = None) -> bool: + """ + 验证 API Key(支持多个密钥,用逗号分隔) + + Args: + api_key_value: 配置的API Key值(如果为空则跳过验证,多个密钥用逗号分隔) + authorization: Authorization Header中的值 + + Returns: + 验证通过返回True,否则抛出HTTPException + + 支持格式: + 1. Bearer YOUR_API_KEY + 2. YOUR_API_KEY + + 多密钥配置示例: + API_KEY=key1,key2,key3 + """ + # 如果未配置 API_KEY,则跳过验证 + if not api_key_value: + return True + + # 检查 Authorization header + if not authorization: + raise HTTPException( + status_code=401, + detail="Missing Authorization header" + ) + + # 提取token(支持Bearer格式) + token = authorization + if authorization.startswith("Bearer "): + token = authorization[7:] + + # 解析多个密钥(用逗号分隔) + valid_keys = [key.strip() for key in api_key_value.split(",") if key.strip()] + + if token not in valid_keys: + raise HTTPException( + status_code=401, + detail="Invalid API Key" + ) + + return True diff --git a/core/base_task_service.py b/core/base_task_service.py new file mode 100644 index 0000000000000000000000000000000000000000..6c30d198967e4c5d3b2590bb440dba811a8a630f --- /dev/null +++ b/core/base_task_service.py @@ -0,0 +1,338 @@ +""" +基础任务服务类 +提供通用的任务管理、日志记录和账户更新功能 +""" +import asyncio +import logging +import threading +import time +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Awaitable, Callable, Deque, Dict, Generic, List, Optional, TypeVar +from collections import deque + +from core.account import RetryPolicy, update_accounts_config + +logger = logging.getLogger("gemini.base_task") + + +class TaskCancelledError(Exception): + """用于在线程/回调中快速中断任务执行。""" + + +class TaskStatus(str, Enum): + """任务状态枚举""" + PENDING = "pending" + RUNNING = "running" + SUCCESS = "success" + FAILED = "failed" + CANCELLED = "cancelled" + + +@dataclass +class BaseTask: + """基础任务数据类""" + id: str + status: TaskStatus = TaskStatus.PENDING + progress: int = 0 + success_count: int = 0 + fail_count: int = 0 + created_at: float = field(default_factory=time.time) + finished_at: Optional[float] = None + results: List[Dict[str, Any]] = field(default_factory=list) + error: Optional[str] = None + logs: List[Dict[str, str]] = field(default_factory=list) + cancel_requested: bool = False + cancel_reason: Optional[str] = None + + def to_dict(self) -> dict: + """转换为字典""" + return { + "id": self.id, + "status": self.status.value, + "progress": self.progress, + "success_count": self.success_count, + "fail_count": self.fail_count, + "created_at": self.created_at, + "finished_at": self.finished_at, + "results": self.results, + "error": self.error, + "logs": self.logs, + "cancel_requested": self.cancel_requested, + "cancel_reason": self.cancel_reason, + } + + +T = TypeVar('T', bound=BaseTask) + + +class BaseTaskService(Generic[T]): + """ + 基础任务服务类 + 提供通用的任务管理、日志记录和账户更新功能 + """ + + def __init__( + self, + multi_account_mgr, + http_client, + user_agent: str, + retry_policy: RetryPolicy, + session_cache_ttl_seconds: int, + global_stats_provider: Callable[[], dict], + set_multi_account_mgr: Optional[Callable[[Any], None]] = None, + log_prefix: str = "TASK", + ) -> None: + """ + 初始化基础任务服务 + + Args: + multi_account_mgr: 多账户管理器 + http_client: HTTP客户端 + user_agent: 用户代理 + retry_policy: 重试策略 + session_cache_ttl_seconds: 会话缓存TTL秒数 + global_stats_provider: 全局统计提供者 + set_multi_account_mgr: 设置多账户管理器的回调 + log_prefix: 日志前缀 + """ + self._executor = ThreadPoolExecutor(max_workers=1) + self._tasks: Dict[str, T] = {} + self._current_task_id: Optional[str] = None + self._last_task_id: Optional[str] = None + self._lock = asyncio.Lock() + self._log_lock = threading.Lock() + self._log_prefix = log_prefix + self._pending_task_ids: Deque[str] = deque() + self._worker_task: Optional[asyncio.Task] = None + self._current_asyncio_task: Optional[asyncio.Task] = None + self._cancel_hooks: Dict[str, List[Callable[[], None]]] = {} + self._cancel_hooks_lock = threading.Lock() + + self.multi_account_mgr = multi_account_mgr + self.http_client = http_client + self.user_agent = user_agent + self.retry_policy = retry_policy + self.session_cache_ttl_seconds = session_cache_ttl_seconds + self.global_stats_provider = global_stats_provider + self.set_multi_account_mgr = set_multi_account_mgr + + def get_task(self, task_id: str) -> Optional[T]: + """获取指定任务""" + return self._tasks.get(task_id) + + def get_current_task(self) -> Optional[T]: + """获取当前任务""" + if self._current_task_id: + current = self._tasks.get(self._current_task_id) + if current: + return current + # 若当前无运行任务,返回队列中最早的 pending 任务(用于前端显示“等待中”) + for task_id in list(self._pending_task_ids): + task = self._tasks.get(task_id) + if task and task.status == TaskStatus.PENDING: + return task + return None + + def get_pending_task_ids(self) -> List[str]: + """返回待执行任务ID列表(调试/展示用)。""" + return list(self._pending_task_ids) + + async def cancel_task(self, task_id: str, reason: str = "cancelled") -> Optional[T]: + """请求取消任务(支持 pending/running)。""" + async with self._lock: + task = self._tasks.get(task_id) + if not task: + return None + + if task.status == TaskStatus.PENDING: + # 从队列移除并直接标记取消 + try: + self._pending_task_ids.remove(task_id) + except ValueError: + pass + task.cancel_requested = True + task.cancel_reason = reason + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + self._append_log(task, "warning", f"task cancelled while pending: {reason}") + self._save_task_history_best_effort(task) + self._last_task_id = task.id + return task + + if task.status == TaskStatus.RUNNING: + task.cancel_requested = True + task.cancel_reason = reason + self._append_log(task, "warning", f"cancel requested: {reason}") + # 尝试立即触发取消回调(例如关闭浏览器) + self._fire_cancel_hooks(task_id) + # 尝试取消当前 await(例如 run_in_executor 等待点) + if self._current_asyncio_task and not self._current_asyncio_task.done(): + self._current_asyncio_task.cancel() + return task + + return task + + async def _enqueue_task(self, task: T) -> None: + """将任务加入队列并启动 worker。""" + self._pending_task_ids.append(task.id) + if not self._worker_task or self._worker_task.done(): + self._worker_task = asyncio.create_task(self._run_worker()) + + async def _run_worker(self) -> None: + """串行执行队列任务(单线程 executor + 单 worker)。""" + while True: + async with self._lock: + next_task: Optional[T] = None + # 清理不存在/非pending的ID + while self._pending_task_ids: + task_id = self._pending_task_ids[0] + task = self._tasks.get(task_id) + if not task or task.status != TaskStatus.PENDING: + self._pending_task_ids.popleft() + continue + next_task = task + self._pending_task_ids.popleft() + self._current_task_id = task.id + break + + if not next_task: + break + + await self._run_one_task(next_task) + + async with self._lock: + if self._current_task_id == next_task.id: + self._current_task_id = None + + async def _run_one_task(self, task: T) -> None: + """执行单个任务,处理取消/异常/收尾。""" + if task.status != TaskStatus.PENDING: + return + if task.cancel_requested: + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + return + + task.status = TaskStatus.RUNNING + self._append_log(task, "info", "task started") + try: + coro = self._execute_task(task) + self._current_asyncio_task = asyncio.create_task(coro) + await self._current_asyncio_task + except asyncio.CancelledError: + # 外部请求取消(或关闭时)会触发 + task.cancel_requested = True + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + self._append_log(task, "warning", f"task cancelled: {task.cancel_reason or 'cancelled'}") + except TaskCancelledError: + task.cancel_requested = True + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + self._append_log(task, "warning", f"task cancelled: {task.cancel_reason or 'cancelled'}") + except Exception as exc: + task.status = TaskStatus.FAILED + task.error = str(exc) + task.finished_at = time.time() + self._append_log(task, "error", f"task error: {type(exc).__name__}: {str(exc)[:200]}") + finally: + self._current_asyncio_task = None + self._clear_cancel_hooks(task.id) + if task.status in (TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.CANCELLED) and task.finished_at: + self._save_task_history_best_effort(task) + self._last_task_id = task.id + + def _add_cancel_hook(self, task_id: str, hook: Callable[[], None]) -> None: + """注册取消回调(线程安全)。""" + with self._cancel_hooks_lock: + self._cancel_hooks.setdefault(task_id, []).append(hook) + + def _fire_cancel_hooks(self, task_id: str) -> None: + """触发取消回调(尽力而为)。""" + with self._cancel_hooks_lock: + hooks = list(self._cancel_hooks.get(task_id) or []) + for hook in hooks: + try: + hook() + except Exception as exc: + logger.warning("[%s] cancel hook error: %s", self._log_prefix, str(exc)[:120]) + + def _clear_cancel_hooks(self, task_id: str) -> None: + with self._cancel_hooks_lock: + self._cancel_hooks.pop(task_id, None) + + # --- 子类需要实现 --- + def _execute_task(self, task: T) -> Awaitable[None]: + """子类实现:执行任务主体(需自行更新 progress/success/fail/finished_at 等)。""" + raise NotImplementedError + + def _append_log(self, task: T, level: str, message: str) -> None: + """ + 添加日志到任务 + + Args: + task: 任务对象 + level: 日志级别 (info, warning, error) + message: 日志消息 + """ + entry = { + "time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), + "level": level, + "message": message, + } + with self._log_lock: + task.logs.append(entry) + if len(task.logs) > 200: + task.logs = task.logs[-200:] + + log_message = f"[{self._log_prefix}] {message}" + if level == "warning": + logger.warning(log_message) + elif level == "error": + logger.error(log_message) + else: + logger.info(log_message) + + # 协作式取消:一旦请求取消,阻断后续通过 log_callback 的执行路径 + # 允许“取消请求/取消完成”相关日志正常写入 + if task.cancel_requested: + safe_messages = ( + "cancel requested:", + "task cancelled", + "task cancelled while pending", + "login task cancelled:", + "register task cancelled:", + ) + if not any(message.startswith(x) for x in safe_messages): + raise TaskCancelledError(task.cancel_reason or "cancelled") + + def _save_task_history_best_effort(self, task: T) -> None: + try: + from main import save_task_to_history + task_type = "login" if self._log_prefix == "REFRESH" else "register" + save_task_to_history(task_type, task.to_dict()) + except Exception: + pass + + def _apply_accounts_update(self, accounts_data: list) -> None: + """ + 应用账户更新 + + Args: + accounts_data: 账户数据列表 + """ + global_stats = self.global_stats_provider() or {} + new_mgr = update_accounts_config( + accounts_data, + self.multi_account_mgr, + self.http_client, + self.user_agent, + self.retry_policy, + self.session_cache_ttl_seconds, + global_stats, + ) + self.multi_account_mgr = new_mgr + if self.set_multi_account_mgr: + self.set_multi_account_mgr(new_mgr) diff --git a/core/cfmail_client.py b/core/cfmail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..7ad24d59272476d50fea69dedd98ecb152145a5a --- /dev/null +++ b/core/cfmail_client.py @@ -0,0 +1,331 @@ +""" +Cloudflare Temp Email 临时邮箱客户端 + +API 文档参考 (基于 Hono 框架,JWT 认证): +- 获取公开配置: GET /open_api/settings +- 创建新邮箱: POST /api/new_address body: {name, domain} → {address, jwt} +- 获取邮件列表: GET /api/mails Authorization: Bearer {jwt} +- 获取邮件详情: GET /api/mail/:mail_id Authorization: Bearer {jwt} +""" + +import random +import string +import time +from datetime import datetime +from typing import Optional + +import requests + +from core.mail_utils import extract_verification_code +from core.proxy_utils import request_with_proxy_fallback + + +class CloudflareMailClient: + """Cloudflare Temp Email 临时邮箱客户端""" + + def __init__( + self, + base_url: str = "", + proxy: str = "", + api_key: str = "", + domain: str = "", + verify_ssl: bool = True, + log_callback=None, + ) -> None: + self.base_url = (base_url or "").rstrip("/") + self.proxy_url = (proxy or "").strip() + self.api_key = (api_key or "").strip() # x-custom-auth 密码 + self.domain = (domain or "").strip() + self.verify_ssl = verify_ssl + self.log_callback = log_callback + + self.email: Optional[str] = None + self.password: Optional[str] = None # 兼容接口,存储 JWT token + self.jwt_token: Optional[str] = None # 创建地址时返回的 JWT + + self._available_domains: list = [] + + # ------------------------------------------------------------------ + # 内部工具 + # ------------------------------------------------------------------ + + def _log(self, level: str, message: str) -> None: + if self.log_callback: + try: + self.log_callback(level, message) + except Exception: + pass + + def _request(self, method: str, url: str, **kwargs) -> requests.Response: + headers = kwargs.pop("headers", None) or {} + + # 实例密码认证(admin 路由使用 x-admin-auth) + if self.api_key and "x-admin-auth" not in {k.lower() for k in headers}: + headers["x-admin-auth"] = self.api_key + + # 邮件操作时使用 JWT Bearer 认证 + if self.jwt_token and "authorization" not in {k.lower() for k in headers}: + headers["Authorization"] = f"Bearer {self.jwt_token}" + + kwargs["headers"] = headers + + self._log("info", f"📤 发送 {method} 请求: {url}") + if "json" in kwargs and kwargs["json"] is not None: + self._log("info", f"📦 请求体: {kwargs['json']}") + + proxies = {"http": self.proxy_url, "https": self.proxy_url} if self.proxy_url else None + + try: + res = request_with_proxy_fallback( + requests.request, + method, + url, + proxies=proxies, + verify=self.verify_ssl, + timeout=kwargs.pop("timeout", 30), + **kwargs, + ) + self._log("info", f"📥 收到响应: HTTP {res.status_code}") + if res.content and res.status_code >= 400: + try: + self._log("error", f"📄 响应内容: {res.text[:500]}") + except Exception: + pass + return res + except Exception as e: + self._log("error", f"❌ 网络请求失败: {e}") + raise + + # ------------------------------------------------------------------ + # 公开接口 + # ------------------------------------------------------------------ + + def set_credentials(self, email: str, password: str = "") -> None: + """设置凭据(兼容接口)。password 存储 JWT token。""" + self.email = email + self.password = password + if password: + self.jwt_token = password + + def _get_available_domains(self) -> list: + """GET /open_api/settings 获取可用域名列表""" + if self._available_domains: + return self._available_domains + + try: + res = self._request("GET", f"{self.base_url}/open_api/settings") + if res.status_code == 200: + data = res.json() if res.content else {} + domains = data.get("domains", []) + if isinstance(domains, list) and domains: + self._available_domains = [str(d).strip() for d in domains if d] + self._log("info", f"🌐 CFMail 可用域名: {self._available_domains}") + return self._available_domains + except Exception as e: + self._log("error", f"❌ 获取可用域名失败: {e}") + + return self._available_domains + + def register_account(self, domain: Optional[str] = None) -> bool: + """POST /api/new_address 创建新邮箱地址""" + if not self.base_url: + self._log("error", "❌ cfmail_base_url 未配置") + return False + + # 确定域名 + selected_domain = domain or self.domain + if not selected_domain: + available = self._get_available_domains() + if available: + selected_domain = random.choice(available) + + # 生成随机用户名 + rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=10)) + timestamp = str(int(time.time()))[-4:] + name = f"t{timestamp}{rand}" + + payload = {"name": name} + if selected_domain: + payload["domain"] = selected_domain + self._log("info", f"📧 使用域名: {selected_domain}") + + self._log("info", f"🎲 创建邮箱: {name}") + + try: + res = self._request("POST", f"{self.base_url}/admin/new_address", json=payload) + + if res.status_code in (200, 201): + data = res.json() if res.content else {} + address = data.get("address", "") + jwt = data.get("jwt", "") + + if address: + self.email = address + self.jwt_token = jwt + self.password = jwt # 兼容接口 + self._log("info", f"✅ CFMail 注册成功: {self.email}") + return True + + self._log("error", f"❌ CFMail 注册失败: HTTP {res.status_code}") + return False + + except Exception as e: + self._log("error", f"❌ CFMail 注册异常: {e}") + return False + + def login(self) -> bool: + """无需登录,直接返回 True""" + return True + + @staticmethod + def _extract_body_from_raw(raw: str) -> str: + """从原始邮件中提取正文(text/plain + text/html),跳过 header""" + if not raw: + return "" + import email as _email + try: + msg = _email.message_from_string(raw) + parts = [] + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + if ct in ("text/plain", "text/html"): + payload = part.get_payload(decode=True) + if payload: + charset = part.get_content_charset() or "utf-8" + parts.append(payload.decode(charset, errors="replace")) + else: + payload = msg.get_payload(decode=True) + if payload: + charset = msg.get_content_charset() or "utf-8" + parts.append(payload.decode(charset, errors="replace")) + return "".join(parts) + except Exception: + return "" + + def fetch_verification_code(self, since_time: Optional[datetime] = None) -> Optional[str]: + """GET /api/mails 获取邮件列表,再 GET /api/mail/:id 获取详情,提取验证码""" + if not self.jwt_token: + self._log("error", "❌ 缺少 JWT token,无法获取邮件") + return None + + try: + self._log("info", "📬 正在拉取 CFMail 邮件列表...") + res = self._request("GET", f"{self.base_url}/api/mails", params={"limit": 20, "offset": 0}) + + if res.status_code != 200: + self._log("error", f"❌ 获取邮件列表失败: HTTP {res.status_code}") + return None + + data = res.json() if res.content else {} + # 响应格式: {"results": [...], "total": N} + messages = data.get("results", []) + if not isinstance(messages, list): + messages = [] + + if not messages: + self._log("info", "📭 邮箱为空,暂无邮件") + return None + + self._log("info", f"📨 收到 {len(messages)} 封邮件,开始检查验证码...") + + # 按 id 降序(新邮件优先) + try: + messages = sorted(messages, key=lambda m: int(m.get("id") or 0), reverse=True) + except Exception: + pass + + for idx, msg in enumerate(messages, 1): + msg_id = msg.get("id") + if not msg_id: + continue + + # 时间过滤 + if since_time: + raw_time = msg.get("created_at") or msg.get("createdAt") + if raw_time: + try: + if isinstance(raw_time, (int, float)): + ts = float(raw_time) + if ts > 1e12: + ts /= 1000.0 + msg_time = datetime.fromtimestamp(ts) + else: + import re + raw_time = re.sub(r"(\.\d{6})\d+", r"\1", str(raw_time)) + # cfmail 的 created_at 是 UTC 无时区标记,显式加 +00:00 再转本地时间 + if not raw_time.endswith("Z") and "+" not in raw_time and raw_time.count("-") <= 2: + raw_time = raw_time + "+00:00" + msg_time = datetime.fromisoformat(raw_time.replace("Z", "+00:00")).astimezone().replace(tzinfo=None) + if msg_time < since_time: + continue + except Exception: + pass + + # 列表响应已包含 raw 字段,直接解析正文提取验证码 + raw_in_list = msg.get("raw") or "" + if raw_in_list: + body = self._extract_body_from_raw(raw_in_list) + code = extract_verification_code(body) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + + # 兜底:尝试从其他摘要字段提取 + summary = (msg.get("subject") or "") + (msg.get("text") or "") + (msg.get("html") or "") + if summary: + code = extract_verification_code(summary) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + + # 获取邮件详情 + self._log("info", f"🔍 正在读取邮件 {idx}/{len(messages)} 详情...") + detail_res = self._request("GET", f"{self.base_url}/api/mail/{msg_id}") + + if detail_res.status_code != 200: + self._log("warning", f"⚠️ 读取邮件详情失败: HTTP {detail_res.status_code}") + continue + + detail = detail_res.json() if detail_res.content else {} + content = self._extract_body_from_raw(detail.get("raw") or "") + if content: + code = extract_verification_code(content) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + else: + self._log("info", f"❌ 邮件 {idx} 中未找到验证码") + + self._log("warning", "⚠️ 所有邮件中均未找到验证码") + return None + + except Exception as e: + self._log("error", f"❌ 获取验证码异常: {e}") + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time: Optional[datetime] = None, + ) -> Optional[str]: + """轮询获取验证码""" + if not self.email: + return None + + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + if i < max_retries: + self._log("info", f"⏳ 等待 {interval} 秒后重试...") + time.sleep(interval) + + self._log("error", f"⏰ 验证码获取超时 ({timeout}秒)") + return None diff --git a/core/child_reaper.py b/core/child_reaper.py new file mode 100644 index 0000000000000000000000000000000000000000..ee0761c6432d10e95e762e4158f9f474fbc9c465 --- /dev/null +++ b/core/child_reaper.py @@ -0,0 +1,84 @@ +""" +Child process reaper (Linux/Unix). + +When third-party libraries spawn subprocesses and do not `wait()` them properly, +the exited children become zombie processes () until the parent reaps. + +This module installs a SIGCHLD handler that reaps all exited children with +os.waitpid(-1, WNOHANG) to prevent zombie accumulation in long-running services. +""" + +from __future__ import annotations + +import errno +import os +import signal +from typing import Callable, Optional, Union + + +_InstalledHandler = Union[int, Callable[[int, object], None], None] + + +def install_child_reaper(log: Optional[Callable[[str], None]] = None) -> bool: + """ + Install a SIGCHLD handler to reap zombie processes. + + Returns True if a handler was installed, otherwise False. + Safe to call multiple times; it will simply re-install the handler. + """ + # Windows has no SIGCHLD; only do this on POSIX. + if os.name != "posix": + return False + + if not hasattr(signal, "SIGCHLD"): + return False + + try: + old_handler: _InstalledHandler = signal.getsignal(signal.SIGCHLD) + except Exception: + old_handler = None + + def _log(msg: str) -> None: + if log: + try: + log(msg) + except Exception: + pass + + def _reap_all_children() -> None: + # Reap all already-exited child processes (non-blocking). + while True: + try: + pid, _status = os.waitpid(-1, os.WNOHANG) + except ChildProcessError: + # No child processes. + return + except OSError as e: + if e.errno == errno.ECHILD: + return + # Any other error: stop to avoid spinning. + _log(f"[CHILD-REAPER] waitpid failed: {e}") + return + + if pid == 0: + return + + def _handler(signum: int, frame) -> None: + # 1) First reap everything we can to prevent zombies. + _reap_all_children() + + # 2) Chain previous handler (if it was a Python callable). + try: + if callable(old_handler): + old_handler(signum, frame) + except Exception: + # Never let exceptions escape a signal handler. + pass + + try: + signal.signal(signal.SIGCHLD, _handler) + return True + except Exception as e: + _log(f"[CHILD-REAPER] failed to install SIGCHLD handler: {e}") + return False + diff --git a/core/config.py b/core/config.py new file mode 100644 index 0000000000000000000000000000000000000000..2a3d814e360641b9345073c8cb25451c6b2a0d01 --- /dev/null +++ b/core/config.py @@ -0,0 +1,554 @@ +""" +统一配置管理系统 + +优先级规则: +1. 安全配置:仅环境变量(ADMIN_KEY, SESSION_SECRET_KEY) +2. 业务配置:数据库 > 默认值 + +配置分类: +- 安全配置:仅从环境变量读取,不可热更新(ADMIN_KEY, SESSION_SECRET_KEY) +- 业务配置:仅从数据库读取,支持热更新(API_KEY, BASE_URL, PROXY, 重试策略等) +""" + +import os +import shutil +import yaml +import secrets +from pathlib import Path +from typing import Optional, List +from pydantic import BaseModel, Field, validator +from dotenv import load_dotenv + +from core import storage + +# 加载 .env 文件 +load_dotenv() + +def _parse_bool(value, default: bool) -> bool: + if isinstance(value, bool): + return value + if value is None: + return default + if isinstance(value, (int, float)): + return value != 0 + if isinstance(value, str): + lowered = value.strip().lower() + if lowered in ("1", "true", "yes", "y", "on"): + return True + if lowered in ("0", "false", "no", "n", "off"): + return False + return default + + +# ==================== 配置模型定义 ==================== + +class BasicConfig(BaseModel): + """基础配置""" + api_key: str = Field(default="", description="API访问密钥(留空则公开访问,多个密钥用逗号分隔)") + base_url: str = Field(default="", description="服务器URL(留空则自动检测)") + proxy_for_auth: str = Field(default="", description="账户操作代理地址(注册/登录/刷新,留空则不使用代理)") + proxy_for_chat: str = Field(default="", description="对话操作代理地址(JWT/会话/消息,留空则不使用代理)") + duckmail_base_url: str = Field(default="https://api.duckmail.sbs", description="DuckMail API地址") + duckmail_api_key: str = Field(default="", description="DuckMail API key") + duckmail_verify_ssl: bool = Field(default=True, description="DuckMail SSL校验") + temp_mail_provider: str = Field(default="duckmail", description="临时邮箱提供商: duckmail/moemail/freemail/gptmail/cfmail") + moemail_base_url: str = Field(default="https://moemail.app", description="Moemail API地址") + moemail_api_key: str = Field(default="", description="Moemail API key") + moemail_domain: str = Field(default="", description="Moemail 邮箱域名(可选,留空则随机选择)") + freemail_base_url: str = Field(default="http://your-freemail-server.com", description="Freemail API地址") + freemail_jwt_token: str = Field(default="", description="Freemail JWT Token") + freemail_verify_ssl: bool = Field(default=True, description="Freemail SSL校验") + freemail_domain: str = Field(default="", description="Freemail 邮箱域名(可选,留空则随机选择)") + mail_proxy_enabled: bool = Field(default=False, description="是否启用临时邮箱代理(使用账户操作代理)") + gptmail_base_url: str = Field(default="https://mail.chatgpt.org.uk", description="GPTMail API地址") + gptmail_api_key: str = Field(default="gpt-test", description="GPTMail API key") + gptmail_verify_ssl: bool = Field(default=True, description="GPTMail SSL校验") + gptmail_domain: str = Field(default="", description="GPTMail 邮箱域名(可选,留空则随机选择)") + cfmail_base_url: str = Field(default="", description="Cloudflare Mail API地址") + cfmail_api_key: str = Field(default="", description="Cloudflare Mail 访问密码(x-custom-auth)") + cfmail_verify_ssl: bool = Field(default=True, description="Cloudflare Mail SSL校验") + cfmail_domain: str = Field(default="", description="Cloudflare Mail 邮箱域名(可选,留空随机)") + browser_engine: str = Field(default="dp", description="浏览器引擎") + browser_headless: bool = Field(default=False, description="自动化浏览器无头模式") + refresh_window_hours: int = Field(default=1, ge=0, le=24, description="过期刷新窗口(小时)") + register_default_count: int = Field(default=1, ge=1, description="默认注册数量") + register_domain: str = Field(default="", description="DuckMail 域名(推荐)") + image_expire_hours: int = Field(default=12, ge=-1, le=720, description="图片/视频过期时间(小时),-1为永不删除") + + +class ImageGenerationConfig(BaseModel): + """图片生成配置""" + enabled: bool = Field(default=False, description="是否启用图片生成") + supported_models: List[str] = Field( + default=[], + description="支持图片生成的模型列表" + ) + output_format: str = Field(default="base64", description="图片输出格式:base64 或 url") + + +class VideoGenerationConfig(BaseModel): + """视频生成配置""" + output_format: str = Field(default="html", description="视频输出格式:html/url/markdown") + + @validator("output_format") + def validate_output_format(cls, v): + allowed = ["html", "url", "markdown"] + if v not in allowed: + raise ValueError(f"output_format 必须是 {allowed} 之一") + return v + + +class RetryConfig(BaseModel): + """重试策略配置""" + max_account_switch_tries: int = Field(default=5, ge=1, le=20, description="账户切换尝试次数") + rate_limit_cooldown_seconds: int = Field(default=7200, ge=3600, le=43200, description="429冷却时间(秒)") + text_rate_limit_cooldown_seconds: int = Field(default=7200, ge=3600, le=86400, description="对话配额冷却(秒)") + images_rate_limit_cooldown_seconds: int = Field(default=14400, ge=3600, le=86400, description="绘图配额冷却(秒)") + videos_rate_limit_cooldown_seconds: int = Field(default=14400, ge=3600, le=86400, description="视频配额冷却(秒)") + session_cache_ttl_seconds: int = Field(default=3600, ge=0, le=86400, description="会话缓存时间(秒,0表示禁用缓存)") + auto_refresh_accounts_seconds: int = Field(default=60, ge=0, le=600, description="自动刷新账号间隔(秒,0禁用)") + # 定时刷新配置 + scheduled_refresh_enabled: bool = Field(default=False, description="是否启用定时刷新任务") + scheduled_refresh_cron: str = Field(default="08:00,20:00", description="刷新时间,如 '08:00,20:00' 或 '*/120'(每120分钟)") + refresh_batch_size: int = Field(default=5, ge=1, le=20, description="每批刷新账号数") + refresh_batch_interval_minutes: int = Field(default=30, ge=5, le=120, description="批次间等待时间(分钟)") + refresh_cooldown_hours: float = Field(default=12.0, ge=1, le=48, description="同一账号刷新冷却期(小时)") + # 向后兼容:旧配置可能只有这个字段,读取时自动转换为 */N cron 格式 + scheduled_refresh_interval_minutes: int = Field(default=0, ge=0, le=720, description="(旧字段,已废弃) 定时刷新检测间隔") + +class QuotaLimitsConfig(BaseModel): + """每日配额上限配置(基于 Google 官方限额,用于主动配额计数)""" + enabled: bool = Field(default=True, description="是否启用主动配额计数") + text_daily_limit: int = Field(default=120, ge=0, le=9999, description="对话每日上限(0=不限制)") + images_daily_limit: int = Field(default=2, ge=0, le=9999, description="绘图每日上限(0=不限制)") + videos_daily_limit: int = Field(default=1, ge=0, le=9999, description="视频每日上限(0=不限制)") + + +class PublicDisplayConfig(BaseModel): + """公开展示配置""" + logo_url: str = Field(default="", description="Logo URL") + chat_url: str = Field(default="", description="开始对话链接") + + +class SessionConfig(BaseModel): + """Session配置""" + expire_hours: int = Field(default=24, ge=1, le=168, description="Session过期时间(小时)") + + +class SecurityConfig(BaseModel): + """安全配置(仅从环境变量读取,不可热更新)""" + admin_key: str = Field(default="", description="管理员密钥(必需)") + session_secret_key: str = Field(..., description="Session密钥") + + +class AppConfig(BaseModel): + """应用配置(统一管理)""" + # 安全配置(仅从环境变量) + security: SecurityConfig + + # 业务配置(环境变量 > 数据库 > 默认值) + basic: BasicConfig + image_generation: ImageGenerationConfig + video_generation: VideoGenerationConfig = Field(default_factory=VideoGenerationConfig) + retry: RetryConfig + quota_limits: QuotaLimitsConfig = Field(default_factory=QuotaLimitsConfig) + public_display: PublicDisplayConfig + session: SessionConfig + + +# ==================== 配置管理器 ==================== + +class ConfigManager: + """配置管理器(单例)""" + + def __init__(self, yaml_path: str = None): + # 自动检测环境并设置默认路径 + if yaml_path is None: + yaml_path = "" + self.yaml_path = Path(yaml_path) + self._config: Optional[AppConfig] = None + self.load() + + def load(self): + """ + 加载配置 + + 优先级规则: + 1. 安全配置(ADMIN_KEY, SESSION_SECRET_KEY):仅从环境变量读取 + 2. 业务配置:数据库 > 默认值 + """ + # 1. 从数据库加载配置 + yaml_data = self._load_yaml() + + # 2. 加载安全配置(仅从环境变量,不允许 Web 修改) + security_config = SecurityConfig( + admin_key=os.getenv("ADMIN_KEY", ""), + session_secret_key=os.getenv("SESSION_SECRET_KEY", self._generate_secret()) + ) + + # 3. 加载基础配置(数据库 > 默认值) + basic_data = yaml_data.get("basic", {}) + refresh_window_raw = basic_data.get("refresh_window_hours", 1) + register_default_raw = basic_data.get("register_default_count", 1) + register_domain_raw = basic_data.get("register_domain", "") + duckmail_api_key_raw = basic_data.get("duckmail_api_key", "") + + # 兼容旧配置:如果存在旧的 proxy 字段,迁移到新字段 + old_proxy = basic_data.get("proxy", "") + old_proxy_for_auth_bool = basic_data.get("proxy_for_auth") + old_proxy_for_chat_bool = basic_data.get("proxy_for_chat") + + # 新配置优先,如果没有新配置则从旧配置迁移 + proxy_for_auth = basic_data.get("proxy_for_auth", "") + proxy_for_chat = basic_data.get("proxy_for_chat", "") + + # 如果新配置为空且存在旧配置,则迁移 + if not proxy_for_auth and old_proxy: + # 如果旧配置中 proxy_for_auth 是布尔值且为 True,则使用旧的 proxy + if isinstance(old_proxy_for_auth_bool, bool) and old_proxy_for_auth_bool: + proxy_for_auth = old_proxy + + if not proxy_for_chat and old_proxy: + # 如果旧配置中 proxy_for_chat 是布尔值且为 True,则使用旧的 proxy + if isinstance(old_proxy_for_chat_bool, bool) and old_proxy_for_chat_bool: + proxy_for_chat = old_proxy + + basic_config = BasicConfig( + api_key=basic_data.get("api_key") or "", + base_url=basic_data.get("base_url") or "", + proxy_for_auth=str(proxy_for_auth or "").strip(), + proxy_for_chat=str(proxy_for_chat or "").strip(), + duckmail_base_url=basic_data.get("duckmail_base_url") or "https://api.duckmail.sbs", + duckmail_api_key=str(duckmail_api_key_raw or "").strip(), + duckmail_verify_ssl=_parse_bool(basic_data.get("duckmail_verify_ssl"), True), + temp_mail_provider=basic_data.get("temp_mail_provider") or "moemail", + moemail_base_url=basic_data.get("moemail_base_url") or "https://moemail.nanohajimi.mom", + moemail_api_key=str(basic_data.get("moemail_api_key") or "").strip(), + moemail_domain=str(basic_data.get("moemail_domain") or "").strip(), + freemail_base_url=basic_data.get("freemail_base_url") or "http://your-freemail-server.com", + freemail_jwt_token=str(basic_data.get("freemail_jwt_token") or "").strip(), + freemail_verify_ssl=_parse_bool(basic_data.get("freemail_verify_ssl"), True), + freemail_domain=str(basic_data.get("freemail_domain") or "").strip(), + mail_proxy_enabled=_parse_bool(basic_data.get("mail_proxy_enabled"), False), + gptmail_base_url=str(basic_data.get("gptmail_base_url") or "https://mail.chatgpt.org.uk").strip(), + gptmail_api_key=str(basic_data.get("gptmail_api_key") or "").strip(), + gptmail_verify_ssl=_parse_bool(basic_data.get("gptmail_verify_ssl"), True), + gptmail_domain=str(basic_data.get("gptmail_domain") or "").strip(), + cfmail_base_url=str(basic_data.get("cfmail_base_url") or "").strip(), + cfmail_api_key=str(basic_data.get("cfmail_api_key") or "").strip(), + cfmail_verify_ssl=_parse_bool(basic_data.get("cfmail_verify_ssl"), True), + cfmail_domain=str(basic_data.get("cfmail_domain") or "").strip(), + browser_engine=basic_data.get("browser_engine") or "dp", + browser_headless=_parse_bool(basic_data.get("browser_headless"), False), + refresh_window_hours=int(refresh_window_raw), + register_default_count=int(register_default_raw), + register_domain=str(register_domain_raw or "").strip(), + image_expire_hours=int(basic_data.get("image_expire_hours", 12)), + ) + + # 4. 加载其他配置(从数据库,带容错处理) + try: + image_generation_config = ImageGenerationConfig( + **yaml_data.get("image_generation", {}) + ) + except Exception as e: + print(f"[WARN] 图片生成配置加载失败,使用默认值: {e}") + image_generation_config = ImageGenerationConfig() + + # 加载视频生成配置 + try: + video_generation_config = VideoGenerationConfig( + **yaml_data.get("video_generation", {}) + ) + except Exception as e: + print(f"[WARN] 视频生成配置加载失败,使用默认值: {e}") + video_generation_config = VideoGenerationConfig() + + # 加载重试配置(Pydantic 会自动验证范围) + try: + retry_config = RetryConfig(**yaml_data.get("retry", {})) + except Exception as e: + print(f"[WARN] 重试配置加载失败,使用默认值: {e}") + retry_config = RetryConfig() + + # 加载配额上限配置 + try: + quota_limits_config = QuotaLimitsConfig(**yaml_data.get("quota_limits", {})) + except Exception as e: + print(f"[WARN] 配额上限配置加载失败,使用默认值: {e}") + quota_limits_config = QuotaLimitsConfig() + + try: + public_display_config = PublicDisplayConfig( + **yaml_data.get("public_display", {}) + ) + except Exception as e: + print(f"[WARN] 公开展示配置加载失败,使用默认值: {e}") + public_display_config = PublicDisplayConfig() + + try: + session_config = SessionConfig( + **yaml_data.get("session", {}) + ) + except Exception as e: + print(f"[WARN] Session配置加载失败,使用默认值: {e}") + session_config = SessionConfig() + + # 5. 构建完整配置 + self._config = AppConfig( + security=security_config, + basic=basic_config, + image_generation=image_generation_config, + video_generation=video_generation_config, + retry=retry_config, + quota_limits=quota_limits_config, + public_display=public_display_config, + session=session_config + ) + + def _load_yaml(self) -> dict: + """从数据库加载配置(允许空配置)。""" + if storage.is_database_enabled(): + try: + data = storage.load_settings_sync() + + # 允许空库启动:None 可能是空配置或连接异常 + if data is None: + print("[WARN] 未读取到 settings(可能为空库或连接异常),将使用默认配置启动") + return {} + + if isinstance(data, dict): + return data + + return {} + except RuntimeError: + # 重新抛出 RuntimeError + raise + except Exception as e: + print(f"[ERROR] 数据库加载失败: {e}") + raise RuntimeError(f"数据库加载失败: {e}") + + print("[ERROR] 未启用数据库") + raise RuntimeError("未配置 DATABASE_URL,应用无法启动") + + def _generate_secret(self) -> str: + """生成随机密钥""" + return secrets.token_urlsafe(32) + + def save_yaml(self, data: dict): + """保存配置到数据库(先验证再保存)""" + if not storage.is_database_enabled(): + raise RuntimeError("Database is not enabled") + + # 先验证数据是否符合 Pydantic 模型要求 + try: + # 构建临时配置进行验证 + security_config = SecurityConfig( + admin_key=os.getenv("ADMIN_KEY", ""), + session_secret_key=os.getenv("SESSION_SECRET_KEY", self._generate_secret()) + ) + + basic_data = data.get("basic", {}) + basic_config = BasicConfig(**basic_data) + + image_generation_config = ImageGenerationConfig( + **data.get("image_generation", {}) + ) + + video_generation_config = VideoGenerationConfig( + **data.get("video_generation", {}) + ) + + retry_config = RetryConfig(**data.get("retry", {})) + + quota_limits_config = QuotaLimitsConfig(**data.get("quota_limits", {})) + + public_display_config = PublicDisplayConfig( + **data.get("public_display", {}) + ) + + session_config = SessionConfig( + **data.get("session", {}) + ) + + # 验证通过,构建完整配置 + test_config = AppConfig( + security=security_config, + basic=basic_config, + image_generation=image_generation_config, + video_generation=video_generation_config, + retry=retry_config, + quota_limits=quota_limits_config, + public_display=public_display_config, + session=session_config + ) + except Exception as e: + # 验证失败,不保存到数据库 + raise ValueError(f"配置验证失败: {str(e)}") + + # 验证通过后才保存到数据库 + try: + saved = storage.save_settings_sync(data) + if saved: + return + except Exception as e: + print(f"[WARN] 数据库保存失败: {e}") + raise RuntimeError("Database write failed") + + def reload(self): + """重新加载配置(热更新)""" + self.load() + + @property + def config(self) -> AppConfig: + """获取配置""" + return self._config + + # ==================== 便捷访问属性 ==================== + + @property + def api_key(self) -> str: + """API访问密钥""" + return self._config.basic.api_key + + @property + def admin_key(self) -> str: + """管理员密钥""" + return self._config.security.admin_key + + @property + def session_secret_key(self) -> str: + """Session密钥""" + return self._config.security.session_secret_key + + @property + def proxy_for_auth(self) -> str: + """账户操作代理地址""" + return self._config.basic.proxy_for_auth + + @property + def proxy_for_chat(self) -> str: + """对话操作代理地址""" + return self._config.basic.proxy_for_chat + + @property + def base_url(self) -> str: + """服务器URL""" + return self._config.basic.base_url + + @property + def logo_url(self) -> str: + """Logo URL""" + return self._config.public_display.logo_url + + @property + def chat_url(self) -> str: + """开始对话链接""" + return self._config.public_display.chat_url + + @property + def image_generation_enabled(self) -> bool: + """是否启用图片生成""" + return self._config.image_generation.enabled + + @property + def image_generation_models(self) -> List[str]: + """支持图片生成的模型列表""" + return self._config.image_generation.supported_models + + @property + def image_output_format(self) -> str: + """图片输出格式""" + return self._config.image_generation.output_format + + @property + def video_output_format(self) -> str: + """视频输出格式""" + return self._config.video_generation.output_format + + @property + def session_expire_hours(self) -> int: + """Session过期时间(小时)""" + return self._config.session.expire_hours + + @property + def max_account_switch_tries(self) -> int: + """账户切换尝试次数""" + return self._config.retry.max_account_switch_tries + + @property + def rate_limit_cooldown_seconds(self) -> int: + # 429 cooldown (seconds) + if hasattr(self._config.retry, 'text_rate_limit_cooldown_seconds'): + return self._config.retry.text_rate_limit_cooldown_seconds + return self._config.retry.rate_limit_cooldown_seconds + + @property + def text_rate_limit_cooldown_seconds(self) -> int: + return self._config.retry.text_rate_limit_cooldown_seconds + + @property + def images_rate_limit_cooldown_seconds(self) -> int: + return self._config.retry.images_rate_limit_cooldown_seconds + + @property + def videos_rate_limit_cooldown_seconds(self) -> int: + return self._config.retry.videos_rate_limit_cooldown_seconds + + @property + def session_cache_ttl_seconds(self) -> int: + # Session cache TTL (seconds) + return self._config.retry.session_cache_ttl_seconds + + @property + def auto_refresh_accounts_seconds(self) -> int: + # Auto refresh accounts interval (seconds) + return self._config.retry.auto_refresh_accounts_seconds + + +# ==================== 全局配置管理器 ==================== + +config_manager = ConfigManager() + +# 注意:不要直接引用 config_manager.config,因为 reload() 后引用会失效 +# 应该始终通过 config_manager.config 访问配置 +def get_config() -> AppConfig: + """获取当前配置(支持热更新)""" + return config_manager.config + +# 为了向后兼容,保留 config 变量,但使用属性访问 +class _ConfigProxy: + """配置代理,确保始终访问最新配置""" + @property + def basic(self): + return config_manager.config.basic + + @property + def security(self): + return config_manager.config.security + + @property + def image_generation(self): + return config_manager.config.image_generation + + @property + def video_generation(self): + return config_manager.config.video_generation + + @property + def retry(self): + return config_manager.config.retry + + @property + def quota_limits(self): + return config_manager.config.quota_limits + + @property + def public_display(self): + return config_manager.config.public_display + + @property + def session(self): + return config_manager.config.session + +config = _ConfigProxy() diff --git a/core/database.py b/core/database.py new file mode 100644 index 0000000000000000000000000000000000000000..c7bb0a1074e2ab91cbdf6391f00be018e5298d87 --- /dev/null +++ b/core/database.py @@ -0,0 +1,191 @@ +""" +统计数据库操作 - 使用 storage.py 的统一数据库连接 +""" +import time +from datetime import datetime +from typing import Dict, Tuple +import asyncio +from collections import defaultdict +from core.storage import _get_sqlite_conn, _sqlite_lock + + +class StatsDatabase: + """统计数据库管理类 - 使用统一的 data.db""" + + async def insert_request_log( + self, timestamp: float, model: str, ttfb_ms: int = None, + total_ms: int = None, status: str = "success", status_code: int = None + ): + """插入请求记录""" + def _insert(): + conn = _get_sqlite_conn() + with _sqlite_lock: + conn.execute( + """ + INSERT INTO request_logs + (timestamp, model, ttfb_ms, total_ms, status, status_code) + VALUES (?, ?, ?, ?, ?, ?) + """, + (int(timestamp), model, ttfb_ms, total_ms, status, status_code) + ) + conn.commit() + + await asyncio.to_thread(_insert) + + async def get_stats_by_time_range(self, time_range: str = "24h") -> Dict: + """按时间范围获取统计数据""" + def _query(): + now = time.time() + if time_range == "24h": + start_time = now - 24 * 3600 + bucket_size = 3600 + elif time_range == "7d": + start_time = now - 7 * 24 * 3600 + bucket_size = 6 * 3600 + elif time_range == "30d": + start_time = now - 30 * 24 * 3600 + bucket_size = 24 * 3600 + else: + start_time = now - 24 * 3600 + bucket_size = 3600 + + conn = _get_sqlite_conn() + with _sqlite_lock: + rows = conn.execute( + """ + SELECT timestamp, model, ttfb_ms, total_ms, status, status_code + FROM request_logs + WHERE timestamp >= ? + ORDER BY timestamp + """, + (int(start_time),) + ).fetchall() + + # 数据分桶 + buckets = defaultdict(lambda: { + "total": 0, "failed": 0, "rate_limited": 0, + "models": defaultdict(int), + "model_ttfb": defaultdict(list), + "model_total": defaultdict(list) + }) + + for row in rows: + ts, model, ttfb, total, status, status_code = row + bucket_key = int((ts - start_time) // bucket_size) + bucket = buckets[bucket_key] + + bucket["total"] += 1 + bucket["models"][model] += 1 + + if status != "success": + bucket["failed"] += 1 + if status_code == 429: + bucket["rate_limited"] += 1 + + if status == "success" and ttfb is not None and total is not None: + bucket["model_ttfb"][model].append(ttfb) + bucket["model_total"][model].append(total) + + # 生成结果 + num_buckets = int((now - start_time) // bucket_size) + 1 + labels = [] + total_requests = [] + failed_requests = [] + rate_limited_requests = [] + + # 先收集所有出现过的模型 + all_models = set() + for bucket in buckets.values(): + all_models.update(bucket["models"].keys()) + all_models.update(bucket["model_ttfb"].keys()) + all_models.update(bucket["model_total"].keys()) + + # 初始化每个模型的数据列表 + model_requests = {model: [] for model in all_models} + model_ttfb_times = {model: [] for model in all_models} + model_total_times = {model: [] for model in all_models} + + # 遍历每个时间桶 + for i in range(num_buckets): + bucket_time = start_time + i * bucket_size + dt = datetime.fromtimestamp(bucket_time) + + if time_range == "24h": + labels.append(dt.strftime("%H:00")) + elif time_range == "7d": + labels.append(dt.strftime("%m-%d %H:00")) + else: + labels.append(dt.strftime("%m-%d")) + + bucket = buckets[i] + total_requests.append(bucket["total"]) + failed_requests.append(bucket["failed"]) + rate_limited_requests.append(bucket["rate_limited"]) + + # 为每个模型添加数据(存在则添加实际值,不存在则添加0) + for model in all_models: + # 请求数 + model_requests[model].append(bucket["models"].get(model, 0)) + + # TTFB平均时间 + if model in bucket["model_ttfb"] and bucket["model_ttfb"][model]: + avg_ttfb = sum(bucket["model_ttfb"][model]) / len(bucket["model_ttfb"][model]) + model_ttfb_times[model].append(avg_ttfb) + else: + model_ttfb_times[model].append(0) + + # 总响应平均时间 + if model in bucket["model_total"] and bucket["model_total"][model]: + avg_total = sum(bucket["model_total"][model]) / len(bucket["model_total"][model]) + model_total_times[model].append(avg_total) + else: + model_total_times[model].append(0) + + # 数据已经是按时间顺序(旧→新),不需要反转 + # ECharts 从左到右渲染,所以最旧的在左边,最新的在右边 + + return { + "labels": labels, + "total_requests": total_requests, + "failed_requests": failed_requests, + "rate_limited_requests": rate_limited_requests, + "model_requests": dict(model_requests), + "model_ttfb_times": dict(model_ttfb_times), + "model_total_times": dict(model_total_times) + } + + return await asyncio.to_thread(_query) + + async def get_total_counts(self) -> Tuple[int, int]: + """获取总成功和失败次数""" + def _query(): + conn = _get_sqlite_conn() + with _sqlite_lock: + success = conn.execute( + "SELECT COUNT(*) FROM request_logs WHERE status = 'success'" + ).fetchone()[0] + failed = conn.execute( + "SELECT COUNT(*) FROM request_logs WHERE status != 'success'" + ).fetchone()[0] + return success, failed + + return await asyncio.to_thread(_query) + + async def cleanup_old_data(self, days: int = 30): + """清理过期数据 - 默认保留30天""" + def _cleanup(): + cutoff_time = int(time.time() - days * 24 * 3600) + conn = _get_sqlite_conn() + with _sqlite_lock: + cursor = conn.execute( + "DELETE FROM request_logs WHERE timestamp < ?", + (cutoff_time,) + ) + conn.commit() + return cursor.rowcount + + return await asyncio.to_thread(_cleanup) + + +# 全局实例 +stats_db = StatsDatabase() diff --git a/core/duckmail_client.py b/core/duckmail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..ade36ac67d36f61d798eabf5dad13e901f0a1e69 --- /dev/null +++ b/core/duckmail_client.py @@ -0,0 +1,304 @@ +import os +import random +import string +import time +from typing import Optional + +import requests + +from core.mail_utils import extract_verification_code +from core.proxy_utils import request_with_proxy_fallback + + +class DuckMailClient: + """DuckMail客户端""" + + def __init__( + self, + base_url: str = "https://api.duckmail.sbs", + proxy: str = "", + verify_ssl: bool = True, + api_key: str = "", + log_callback=None, + ) -> None: + self.base_url = base_url.rstrip("/") + self.verify_ssl = verify_ssl + self.proxies = {"http": proxy, "https": proxy} if proxy else None + self.api_key = api_key.strip() + self.log_callback = log_callback + + self.email: Optional[str] = None + self.password: Optional[str] = None + self.account_id: Optional[str] = None + self.token: Optional[str] = None + + def set_credentials(self, email: str, password: str) -> None: + self.email = email + self.password = password + + def _request(self, method: str, url: str, **kwargs) -> requests.Response: + """发送请求并打印详细日志""" + headers = kwargs.pop("headers", None) or {} + if self.api_key and "Authorization" not in headers: + headers["Authorization"] = f"Bearer {self.api_key}" + kwargs["headers"] = headers + self._log("info", f"📤 发送 {method} 请求: {url}") + if "json" in kwargs: + self._log("info", f"📦 请求体: {kwargs['json']}") + + try: + res = request_with_proxy_fallback( + requests.request, + method, + url, + proxies=self.proxies, + verify=self.verify_ssl, + timeout=kwargs.pop("timeout", 15), + **kwargs, + ) + self._log("info", f"📥 收到响应: HTTP {res.status_code}") + log_body = os.getenv("DUCKMAIL_LOG_BODY", "").strip().lower() in ("1", "true", "yes", "y", "on") + if res.content and (log_body or res.status_code >= 400): + try: + self._log("info", f"📄 响应内容: {res.text[:500]}") + except Exception: + pass + return res + except Exception as e: + self._log("error", f"❌ 网络请求失败: {e}") + raise + + def register_account(self, domain: Optional[str] = None) -> bool: + """注册新邮箱账号""" + # 获取域名 + if not domain: + self._log("info", "🔍 正在获取可用域名...") + domain = self._get_domain() + self._log("info", f"📧 使用域名: {domain}") + + # 生成随机邮箱和密码 + rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=10)) + timestamp = str(int(time.time()))[-4:] + self.email = f"t{timestamp}{rand}@{domain}" + self.password = f"Pwd{rand}{timestamp}" + self._log("info", f"🎲 生成邮箱: {self.email}") + self._log("info", f"🔑 生成密码: {self.password}") + + try: + self._log("info", "📤 正在向 DuckMail 发送注册请求...") + res = self._request( + "POST", + f"{self.base_url}/accounts", + json={"address": self.email, "password": self.password}, + ) + if res.status_code in (200, 201): + data = res.json() if res.content else {} + self.account_id = data.get("id") + self._log("info", f"✅ DuckMail 注册成功,账户ID: {self.account_id}") + return True + else: + self._log("error", f"❌ DuckMail 注册失败: HTTP {res.status_code}") + except Exception as e: + self._log("error", f"❌ DuckMail 注册异常: {e}") + return False + + self._log("error", "❌ DuckMail 注册失败") + return False + + def login(self) -> bool: + """登录获取token""" + if not self.email or not self.password: + self._log("error", "❌ 邮箱或密码未设置") + return False + + try: + self._log("info", f"🔐 正在登录 DuckMail: {self.email}") + res = self._request( + "POST", + f"{self.base_url}/token", + json={"address": self.email, "password": self.password}, + ) + if res.status_code == 200: + data = res.json() if res.content else {} + token = data.get("token") + if token: + self.token = token + self._log("info", f"✅ DuckMail 登录成功,Token: {token[:20]}...") + return True + else: + self._log("error", "❌ 响应中未找到 Token") + else: + self._log("error", f"❌ DuckMail 登录失败: HTTP {res.status_code}") + except Exception as e: + self._log("error", f"❌ DuckMail 登录异常: {e}") + return False + + self._log("error", "❌ DuckMail 登录失败") + return False + + def fetch_verification_code(self, since_time=None) -> Optional[str]: + """获取验证码""" + if not self.token: + self._log("info", "🔐 Token 不存在,尝试重新登录...") + if not self.login(): + self._log("error", "❌ 登录失败,无法获取验证码") + return None + + try: + self._log("info", "📬 正在拉取邮件列表...") + # 获取邮件列表 + res = self._request( + "GET", + f"{self.base_url}/messages", + headers={"Authorization": f"Bearer {self.token}"}, + ) + + if res.status_code != 200: + self._log("error", f"❌ 获取邮件列表失败: HTTP {res.status_code}") + return None + + data = res.json() if res.content else {} + messages = data.get("hydra:member", []) + + if not messages: + self._log("info", "📭 邮箱为空,暂无邮件") + return None + + self._log("info", f"📨 收到 {len(messages)} 封邮件,开始检查验证码...") + + from datetime import datetime + import re + + def _parse_message_time(msg_obj) -> Optional[datetime]: + created_at = msg_obj.get("createdAt") + if created_at is None: + return None + + if isinstance(created_at, (int, float)): + timestamp = float(created_at) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp).astimezone().replace(tzinfo=None) + + if isinstance(created_at, str): + raw = created_at.strip() + if not raw: + return None + if raw.isdigit(): + timestamp = float(raw) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp).astimezone().replace(tzinfo=None) + + # 截断纳秒到微秒(fromisoformat 只支持6位小数) + raw = re.sub(r"(\.\d{6})\d+", r"\1", raw) + return datetime.fromisoformat(raw.replace("Z", "+00:00")).astimezone().replace(tzinfo=None) + + return None + + # 按时间倒序,优先检查最新邮件 + messages_with_time = [(msg, _parse_message_time(msg)) for msg in messages] + if any(item[1] is not None for item in messages_with_time): + messages_with_time.sort(key=lambda item: item[1] or datetime.min, reverse=True) + messages = [item[0] for item in messages_with_time] + + # 遍历邮件,过滤时间 + for idx, msg in enumerate(messages, 1): + msg_id = msg.get("id") + if not msg_id: + continue + + # 时间过滤 + if since_time: + msg_time = _parse_message_time(msg) + if msg_time and msg_time < since_time: + continue + + self._log("info", f"🔍 正在读取邮件 {idx}/{len(messages)} (ID: {msg_id[:10]}...)") + detail = self._request( + "GET", + f"{self.base_url}/messages/{msg_id}", + headers={"Authorization": f"Bearer {self.token}"}, + ) + + if detail.status_code != 200: + self._log("warning", f"⚠️ 读取邮件详情失败: HTTP {detail.status_code}") + continue + + payload = detail.json() if detail.content else {} + + # 获取邮件内容 + text_content = payload.get("text") or "" + html_content = payload.get("html") or "" + + if isinstance(html_content, list): + html_content = "".join(str(item) for item in html_content) + if isinstance(text_content, list): + text_content = "".join(str(item) for item in text_content) + + content = text_content + html_content + self._log("info", f"📄 邮件内容预览: {content[:200]}...") + + code = extract_verification_code(content) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + else: + self._log("info", f"❌ 邮件 {idx} 中未找到验证码") + + self._log("warning", "⚠️ 所有邮件中均未找到验证码") + return None + + except Exception as e: + self._log("error", f"❌ 获取验证码异常: {e}") + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time=None, + ) -> Optional[str]: + """轮询获取验证码""" + if not self.token: + self._log("info", "🔐 Token 不存在,尝试登录...") + if not self.login(): + self._log("error", "❌ 登录失败,无法轮询验证码") + return None + + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + + if i < max_retries: + self._log("info", f"⏳ 等待 {interval} 秒后重试...") + time.sleep(interval) + + self._log("error", f"⏰ 验证码获取超时 ({timeout}秒)") + return None + + def _get_domain(self) -> str: + """获取可用域名""" + try: + res = self._request("GET", f"{self.base_url}/domains") + if res.status_code == 200: + data = res.json() if res.content else {} + members = data.get("hydra:member", []) + if members: + return members[0].get("domain") or "duck.com" + except Exception: + pass + return "duck.com" + + def _log(self, level: str, message: str) -> None: + if self.log_callback: + try: + self.log_callback(level, message) + except Exception: + pass diff --git a/core/freemail_client.py b/core/freemail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..3e539a62ebb50e34305a2ffc3ba99266ccaf3f96 --- /dev/null +++ b/core/freemail_client.py @@ -0,0 +1,325 @@ +import random +import string +import time +from typing import Optional + +import requests + +from core.mail_utils import extract_verification_code +from core.proxy_utils import request_with_proxy_fallback + + +class FreemailClient: + """Freemail 临时邮箱客户端""" + + def __init__( + self, + base_url: str = "http://your-freemail-server.com", + jwt_token: str = "", + proxy: str = "", + verify_ssl: bool = True, + log_callback=None, + ) -> None: + self.base_url = base_url.rstrip("/") + self.jwt_token = jwt_token.strip() + self.verify_ssl = verify_ssl + self.proxies = {"http": proxy, "https": proxy} if proxy else None + self.log_callback = log_callback + + self.email: Optional[str] = None + + def set_credentials(self, email: str, password: str = None) -> None: + """设置邮箱凭证(Freemail 不需要密码)""" + self.email = email + + def _request(self, method: str, url: str, **kwargs) -> requests.Response: + """发送请求并打印日志""" + self._log("info", f"📤 发送 {method} 请求: {url}") + if "params" in kwargs: + self._log("info", f"🔎 参数: {kwargs['params']}") + + try: + res = request_with_proxy_fallback( + requests.request, + method, + url, + proxies=self.proxies, + verify=self.verify_ssl, + timeout=kwargs.pop("timeout", 15), + **kwargs, + ) + self._log("info", f"📥 收到响应: HTTP {res.status_code}") + if res.status_code >= 400: + try: + self._log("error", f"📄 响应内容: {res.text[:500]}") + except Exception: + pass + return res + except Exception as e: + self._log("error", f"❌ 网络请求失败: {e}") + raise + + def register_account(self, domain: Optional[str] = None) -> bool: + """创建新的临时邮箱""" + try: + params = {"admin_token": self.jwt_token} + if domain: + params["domain"] = domain + self._log("info", f"📧 使用域名: {domain}") + else: + self._log("info", "🔍 自动选择域名...") + + res = self._request( + "POST", + f"{self.base_url}/api/generate", + params=params, + ) + + if res.status_code in (200, 201): + data = res.json() if res.content else {} + # Freemail API 返回的字段是 "email" 或 "mailbox" + email = data.get("email") or data.get("mailbox") + if email: + self.email = email + self._log("info", f"✅ Freemail 邮箱创建成功: {self.email}") + return True + else: + self._log("error", "❌ 响应中缺少 email 字段") + return False + elif res.status_code in (401, 403): + self._log("error", "❌ Freemail 认证失败 (JWT Token 无效)") + return False + else: + self._log("error", f"❌ Freemail 创建失败: HTTP {res.status_code}") + return False + + except Exception as e: + self._log("error", f"❌ Freemail 注册异常: {e}") + return False + + def login(self) -> bool: + """登录(Freemail 不需要登录,直接返回 True)""" + return True + + def fetch_verification_code(self, since_time=None) -> Optional[str]: + """获取验证码""" + if not self.email: + self._log("error", "❌ 邮箱地址未设置") + return None + + try: + self._log("info", "📬 正在拉取 Freemail 邮件列表...") + params = { + "mailbox": self.email, + "admin_token": self.jwt_token, + } + + res = self._request( + "GET", + f"{self.base_url}/api/emails", + params=params, + ) + + if res.status_code == 401 or res.status_code == 403: + self._log("error", "❌ Freemail 认证失败") + return None + + if res.status_code != 200: + self._log("error", f"❌ 获取邮件列表失败: HTTP {res.status_code}") + return None + + emails = res.json() if res.content else [] + if not isinstance(emails, list): + self._log("error", "❌ 响应格式错误(不是列表)") + return None + + if not emails: + self._log("info", "📭 邮箱为空,暂无邮件") + return None + + self._log("info", f"📨 收到 {len(emails)} 封邮件,开始检查验证码...") + + from datetime import datetime, timezone + import re + + def _parse_email_time(email_obj) -> Optional[datetime]: + time_keys = ( + "created_at", + "createdAt", + "received_at", + "receivedAt", + "sent_at", + "sentAt", + ) + + raw_time = None + for key in time_keys: + if email_obj.get(key) is not None: + raw_time = email_obj.get(key) + break + + if raw_time is None: + return None + + if isinstance(raw_time, (int, float)): + timestamp = float(raw_time) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp).astimezone().replace(tzinfo=None) + + if isinstance(raw_time, str): + raw = raw_time.strip() + if not raw: + return None + if raw.isdigit(): + timestamp = float(raw) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp).astimezone().replace(tzinfo=None) + + # 截断纳秒到微秒(fromisoformat 只支持6位小数) + raw = re.sub(r"(\.\d{6})\d+", r"\1", raw) + + try: + parsed = datetime.fromisoformat(raw.replace("Z", "+00:00")) + if parsed.tzinfo: + return parsed.astimezone().replace(tzinfo=None) + return parsed.replace(tzinfo=timezone.utc).astimezone().replace(tzinfo=None) + except Exception: + return None + + return None + + # 按时间倒序,优先检查最新邮件 + emails_with_time = [(email_item, _parse_email_time(email_item)) for email_item in emails] + if any(item[1] is not None for item in emails_with_time): + emails_with_time.sort(key=lambda item: item[1] or datetime.min, reverse=True) + emails = [item[0] for item in emails_with_time] + + skipped_no_time_indexes = [] + skipped_expired_indexes = [] + + def _format_indexes(indexes: list[int]) -> str: + if len(indexes) <= 10: + return ",".join(str(index) for index in indexes) + preview = ",".join(str(index) for index in indexes[:10]) + return f"{preview}...(+{len(indexes) - 10})" + + def _log_skip_summary() -> None: + if skipped_no_time_indexes: + self._log( + "info", + f"⏭️ 已跳过 {len(skipped_no_time_indexes)} 封缺少可解析时间的邮件" + f"(序号: {_format_indexes(skipped_no_time_indexes)})", + ) + if skipped_expired_indexes: + self._log( + "info", + f"⏭️ 已跳过 {len(skipped_expired_indexes)} 封过期邮件" + f"(序号: {_format_indexes(skipped_expired_indexes)})", + ) + + # 从最新一封邮件开始查找 + for idx, email_data in enumerate(emails, 1): + # 时间过滤 + if since_time: + email_time = _parse_email_time(email_data) + if email_time is None: + skipped_no_time_indexes.append(idx) + continue + if email_time < since_time: + skipped_expired_indexes.append(idx) + continue + + # 获取邮件完整内容 + email_id = email_data.get("id") + if email_id: + # 调用详情接口获取完整内容 + detail_res = self._request( + "GET", + f"{self.base_url}/api/email/{email_id}", + params={"admin_token": self.jwt_token}, + ) + if detail_res.status_code == 200: + detail_data = detail_res.json() + content = detail_data.get("content") or "" + html_content = detail_data.get("html_content") or "" + else: + # 降级:如果详情接口失败,使用列表中的字段 + content = email_data.get("content") or "" + html_content = email_data.get("html_content") or "" + preview = email_data.get("preview") or "" + content = content + " " + preview + else: + # 降级:没有 ID,使用列表中的字段 + content = email_data.get("content") or "" + html_content = email_data.get("html_content") or "" + preview = email_data.get("preview") or "" + content = content + " " + preview + + subject = email_data.get("subject") or "" + full_content = subject + " " + content + " " + html_content + code = extract_verification_code(full_content) + if code: + _log_skip_summary() + self._log("info", f"✅ 找到验证码: {code}") + return code + else: + self._log("info", f"❌ 邮件 {idx} 中未找到验证码") + + _log_skip_summary() + self._log("warning", "⚠️ 所有邮件中均未找到验证码") + return None + + except Exception as e: + self._log("error", f"❌ 获取验证码异常: {e}") + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time=None, + ) -> Optional[str]: + """轮询获取验证码""" + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + + if i < max_retries: + self._log("info", f"⏳ 等待 {interval} 秒后重试...") + time.sleep(interval) + + self._log("error", f"⏰ 验证码获取超时 ({timeout}秒)") + return None + + def _get_domain(self) -> str: + """获取可用域名""" + try: + params = {"admin_token": self.jwt_token} + res = self._request( + "GET", + f"{self.base_url}/api/domains", + params=params, + ) + if res.status_code == 200: + domains = res.json() if res.content else [] + if isinstance(domains, list) and domains: + return domains[0] + except Exception: + pass + return "" + + def _log(self, level: str, message: str) -> None: + """日志回调""" + if self.log_callback: + try: + self.log_callback(level, message) + except Exception: + pass diff --git a/core/gemini_automation.py b/core/gemini_automation.py new file mode 100644 index 0000000000000000000000000000000000000000..92291c01f2f6cca9743f48b7926236e96457760a --- /dev/null +++ b/core/gemini_automation.py @@ -0,0 +1,1103 @@ +""" +Gemini自动化登录模块(用于新账号注册) +""" +import os +import json +import random +import re +import string +import time +from datetime import datetime, timedelta, timezone +from typing import Optional +from urllib.parse import quote + +from DrissionPage import ChromiumPage, ChromiumOptions +from core.base_task_service import TaskCancelledError + + +# 常量 +AUTH_HOME_URL = "https://auth.business.gemini.google/login" + +# Linux 下常见的 Chromium 路径 +CHROMIUM_PATHS = [ + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/usr/bin/google-chrome", + "/usr/bin/google-chrome-stable", +] + +# 注册时随机使用的真实英文姓名(避免明显的机器人特征) +REGISTER_NAMES = [ + "James Smith", "John Johnson", "Robert Williams", "Michael Brown", "William Jones", + "David Garcia", "Mary Miller", "Patricia Davis", "Jennifer Rodriguez", "Linda Martinez", + "Barbara Anderson", "Susan Thomas", "Jessica Jackson", "Sarah White", "Karen Harris", + "Lisa Martin", "Nancy Thompson", "Betty Garcia", "Margaret Martinez", "Sandra Robinson", + "Ashley Clark", "Dorothy Rodriguez", "Emma Lewis", "Olivia Lee", "Ava Walker", + "Emily Hall", "Abigail Allen", "Madison Young", "Elizabeth Hernandez", "Charlotte King", +] + +# 常见桌面分辨率(避免固定 1280x800 成为指纹) +COMMON_VIEWPORTS = [ + (1366, 768), (1440, 900), (1536, 864), (1280, 720), + (1920, 1080), (1600, 900), (1280, 800), (1360, 768), +] + + +def _find_chromium_path() -> Optional[str]: + """查找可用的 Chromium/Chrome 浏览器路径""" + for path in CHROMIUM_PATHS: + if os.path.isfile(path) and os.access(path, os.X_OK): + return path + return None + + +class GeminiAutomation: + """Gemini自动化登录""" + + def __init__( + self, + user_agent: str = "", + proxy: str = "", + headless: bool = True, + timeout: int = 60, + log_callback=None, + ) -> None: + self.user_agent = user_agent or self._get_ua() + self.proxy = proxy + self.headless = headless + self.timeout = timeout + self.log_callback = log_callback + self._page = None + self._user_data_dir = None + self._last_send_error = "" + + def stop(self) -> None: + """外部请求停止:尽力关闭浏览器实例。""" + page = self._page + if page: + try: + page.quit() + except Exception: + pass + + def login_and_extract(self, email: str, mail_client, is_new_account: bool = False) -> dict: + """执行登录并提取配置""" + page = None + user_data_dir = None + try: + page = self._create_page() + user_data_dir = getattr(page, 'user_data_dir', None) + self._page = page + self._user_data_dir = user_data_dir + return self._run_flow(page, email, mail_client, is_new_account=is_new_account) + except TaskCancelledError: + raise + except Exception as exc: + self._log("error", f"automation error: {exc}") + return {"success": False, "error": str(exc)} + finally: + if page: + try: + page.quit() + except Exception: + pass + self._page = None + self._cleanup_user_data(user_data_dir) + self._user_data_dir = None + + def _create_page(self) -> ChromiumPage: + """创建浏览器页面""" + options = ChromiumOptions() + + # 自动检测 Chromium 浏览器路径(Linux/Docker 环境) + chromium_path = _find_chromium_path() + if chromium_path: + options.set_browser_path(chromium_path) + + options.set_argument("--incognito") + options.set_argument("--no-sandbox") + options.set_argument("--disable-dev-shm-usage") + options.set_argument("--disable-setuid-sandbox") + options.set_argument("--disable-blink-features=AutomationControlled") + + # 随机窗口尺寸(避免固定分辨率成为指纹) + vw, vh = random.choice(COMMON_VIEWPORTS) + options.set_argument(f"--window-size={vw},{vh}") + options.set_user_agent(self.user_agent) + + # 防止 WebRTC 泄露真实 IP(即使使用代理也可能暴露) + options.set_argument("--disable-webrtc") + options.set_argument("--enforce-webrtc-ip-handling-policy") + options.set_pref("webrtc.ip_handling_policy", "disable_non_proxied_udp") + options.set_pref("webrtc.multiple_routes_enabled", False) + options.set_pref("webrtc.nonproxied_udp_enabled", False) + + # 语言设置(确保使用中文界面) + options.set_argument("--lang=zh-CN") + options.set_pref("intl.accept_languages", "zh-CN,zh") + + if self.proxy: + options.set_argument(f"--proxy-server={self.proxy}") + + if self.headless: + # 使用新版无头模式,更接近真实浏览器 + options.set_argument("--headless=new") + options.set_argument("--disable-gpu") + options.set_argument("--no-first-run") + options.set_argument("--disable-extensions") + # 反检测参数 + options.set_argument("--disable-infobars") + options.set_argument("--enable-features=NetworkService,NetworkServiceInProcess") + + + + options.auto_port() + page = ChromiumPage(options) + page.set.timeouts(self.timeout) + + # 最小化 JS 注入:只设置 window.chrome(不使用 Object.defineProperty,避免被 reCAPTCHA 检测) + # 注意:DrissionPage 不像 Selenium 那样暴露 navigator.webdriver,无需额外隐藏 + try: + page.run_cdp("Page.addScriptToEvaluateOnNewDocument", source=""" + // 确保 window.chrome 存在(headless 模式下可能缺失) + if (!window.chrome) { + window.chrome = {runtime: {}, loadTimes: function(){return {}}, csi: function(){return {}}}; + } + """) + except Exception: + pass + + return page + + def _extract_xsrf_token(self, page) -> str: + """从页面中提取真实的 XSRF Token(避免硬编码被标黑)""" + try: + html = page.html or "" + # 尝试从 meta 标签提取 + m = re.search(r'name=["\']xsrf-token["\']\s+content=["\']([^"\']+)["\']', html, re.IGNORECASE) + if m: + self._log("info", "🔑 从 meta 标签提取到 XSRF token") + return m.group(1) + # 尝试从隐藏 input 提取 + m = re.search(r'name=["\']xsrfToken["\'][^>]*value=["\']([A-Za-z0-9_-]{20,})["\']', html) + if m: + self._log("info", "🔑 从 input 提取到 XSRF token") + return m.group(1) + # 尝试从 JS 变量提取 + m = re.search(r'xsrfToken["\']?\s*[=:]\s*["\']([A-Za-z0-9_-]{20,})["\']', html) + if m: + self._log("info", "🔑 从 JS 提取到 XSRF token") + return m.group(1) + # 尝试从 URL 参数提取 + m = re.search(r'xsrfToken=([A-Za-z0-9_-]{20,})', html) + if m: + self._log("info", "🔑 从 URL 参数提取到 XSRF token") + return m.group(1) + except Exception as e: + self._log("warning", f"⚠️ XSRF token 提取异常: {e}") + self._log("warning", "⚠️ 未能从页面提取 XSRF token,使用备用值") + return "GXO_B0wnNhs6UQJZMcrSbTsbEEs" + + def _run_flow(self, page, email: str, mail_client, is_new_account: bool = False) -> dict: + """执行登录流程(is_new_account=True 时启用注册专用的增强用户名处理)""" + + # 记录任务开始时间,用于邮件时间过滤(全流程固定,不随重发更新) + from datetime import datetime + task_start_time = datetime.now() + + # Step 1: 导航到登录页面 + self._log("info", f"🌐 打开登录页面: {email}") + page.get(AUTH_HOME_URL, timeout=self.timeout) + time.sleep(random.uniform(2, 4)) + + # 从页面动态提取 XSRF token(避免硬编码被 Google 标黑) + xsrf_token = self._extract_xsrf_token(page) + + # 设置 XSRF Cookie + try: + self._log("info", "🍪 设置 XSRF Cookie...") + page.set.cookies({ + "name": "__Host-AP_SignInXsrf", + "value": xsrf_token, + "url": AUTH_HOME_URL, + "path": "/", + "secure": True, + }) + except Exception as e: + self._log("warning", f"⚠️ Cookie 设置失败: {e}") + + # Step 1.5: 通过 URL 方式提交邮箱(稳定,不触发风控) + login_hint = quote(email, safe="") + login_url = f"https://auth.business.gemini.google/login/email?continueUrl=https%3A%2F%2Fbusiness.gemini.google%2F&loginHint={login_hint}&xsrfToken={xsrf_token}" + + # 先启动网络监听,再导航(避免漏掉页面加载期间的请求) + try: + page.listen.start( + targets=["batchexecute"], + is_regex=False, + method=("POST",), + res_type=("XHR", "FETCH"), + ) + except Exception: + pass + + self._log("info", "📧 使用 URL 方式提交邮箱...") + page.get(login_url, timeout=self.timeout) + time.sleep(random.uniform(3, 5)) + + # 模拟真实用户行为:页面加载后随机滚动 + self._random_scroll(page) + + # Step 2: 检查当前页面状态 + current_url = page.url + self._log("info", f"📍 当前 URL: {current_url}") + + # 检测 signin-error 页面(极端情况,一般 URL 方式不会触发) + if "signin-error" in current_url: + self._log("error", "❌ 进入 signin-error 页面,可能是代理或网络问题") + self._save_screenshot(page, "signin_error") + return {"success": False, "error": "signin-error: token rejected by Google, try changing proxy"} + + has_business_params = "business.gemini.google" in current_url and "csesidx=" in current_url and "/cid/" in current_url + + if has_business_params: + self._log("info", "✅ 已登录,提取配置") + return self._extract_config(page, email) + + # 检测 403 Access Restricted(刷新/登录时账户可能已被封禁) + access_error = self._check_access_restricted(page, email) + if access_error: + return access_error + + # Step 3: 点击发送验证码按钮(最多5轮,适度退避间隔) + self._log("info", "📧 发送验证码...") + max_send_rounds = 5 + send_round_delays = [10, 10, 15, 15, 20] + send_round = 0 + while True: + send_round += 1 + if self._click_send_code_button(page): + break + if send_round >= max_send_rounds: + self._log("error", "❌ 验证码发送失败(可能触发风控),建议更换代理IP") + self._save_screenshot(page, "send_code_button_failed") + return {"success": False, "error": "send code failed after retries"} + delay = send_round_delays[min(send_round - 1, len(send_round_delays) - 1)] + self._log("warning", f"⚠️ 发送失败,{delay}秒后重试 ({send_round}/{max_send_rounds})") + time.sleep(delay) + + # Step 4: 等待验证码输入框出现 + code_input = self._wait_for_code_input(page) + if not code_input: + self._log("error", "❌ 验证码输入框未出现") + self._save_screenshot(page, "code_input_missing") + return {"success": False, "error": "code input not found"} + + # Step 5: 轮询邮件获取验证码(3次,每次5秒间隔) + self._log("info", "📬 等待邮箱验证码...") + code = mail_client.poll_for_code(timeout=15, interval=5, since_time=task_start_time) + + if not code: + self._log("warning", "⚠️ 验证码超时,等待后重新发送...") + time.sleep(random.uniform(12, 18)) + # 尝试点击重新发送按钮 + if self._click_resend_code_button(page): + # 再次轮询验证码(3次,每次5秒间隔) + code = mail_client.poll_for_code(timeout=15, interval=5, since_time=task_start_time) + if not code: + self._log("error", "❌ 重新发送后仍未收到验证码") + self._save_screenshot(page, "code_timeout_after_resend") + return {"success": False, "error": "verification code timeout after resend"} + else: + self._log("error", "❌ 验证码超时且未找到重新发送按钮") + self._save_screenshot(page, "code_timeout") + return {"success": False, "error": "verification code timeout"} + + self._log("info", f"✅ 收到验证码: {code}") + + # Step 6: 输入验证码并提交 + code_input = page.ele("css:input[jsname='ovqh0b']", timeout=3) or \ + page.ele("css:input[type='tel']", timeout=2) + + if not code_input: + self._log("error", "❌ 验证码输入框已失效") + return {"success": False, "error": "code input expired"} + + # 尝试模拟人类输入,失败则降级到直接注入 + self._log("info", "⌨️ 输入验证码...") + if not self._simulate_human_input(code_input, code): + self._log("warning", "⚠️ 模拟输入失败,降级为直接输入") + code_input.input(code, clear=True) + time.sleep(random.uniform(0.4, 0.8)) + + # 提交验证码:先回车,再找验证按钮兜底 + self._log("info", "⏎ 提交验证码") + code_input.input("\n") + time.sleep(random.uniform(1, 2)) + # 如果回车没触发,找验证按钮点击 + if "verify-oob-code" in page.url: + verify_btn = self._find_verify_button(page) + if verify_btn: + try: + verify_btn.click() + self._log("info", "✅ 已点击验证按钮(兜底)") + except Exception: + pass + + # [注册专用] 验证码提交后先等几秒让页面跳转,再检查 403 + if is_new_account: + time.sleep(3) + access_error = self._check_access_restricted(page, email) + if access_error: + return access_error + self._log("info", "📝 [注册] 验证码已提交,等待姓名输入页面...") + if self._handle_username_setup(page, is_new_account=True): + self._log("info", "✅ 姓名填写完成,等待工作台 URL...") + if self._wait_for_business_params(page, timeout=45): + self._log("info", "🎊 注册成功,提取配置...") + return self._extract_config(page, email) + # 姓名步骤失败或未出现,继续走通用流程兜底 + self._log("info", "⚠️ 姓名步骤未完成,走通用流程兜底...") + + # Step 7: 等待页面自动重定向(提交验证码后 Google 会自动跳转) + self._log("info", "⏳ 等待验证后跳转...") + time.sleep(random.uniform(10, 15)) + + # 记录当前 URL 状态 + current_url = page.url + self._log("info", f"📍 验证后 URL: {current_url}") + + # 检查是否还停留在验证码页面(说明提交失败) + if "verify-oob-code" in current_url: + self._log("error", "❌ 验证码提交失败") + self._save_screenshot(page, "verification_submit_failed") + return {"success": False, "error": "verification code submission failed"} + + # Step 8: 处理协议页面(如果有) + self._handle_agreement_page(page) + + # Step 8.5: 检测 403 Access Restricted 页面 + access_error = self._check_access_restricted(page, email) + if access_error: + return access_error + + # Step 9: 检查是否已经在正确的页面 + current_url = page.url + has_business_params = "business.gemini.google" in current_url and "csesidx=" in current_url and "/cid/" in current_url + + if has_business_params: + return self._extract_config(page, email) + + # Step 10: 如果不在正确的页面,尝试导航 + if "business.gemini.google" not in current_url: + page.get("https://business.gemini.google/", timeout=self.timeout) + time.sleep(random.uniform(4, 7)) + + # Step 11: 检查是否需要设置用户名(仅登录刷新走此路径,注册已在早期处理) + if not is_new_account and "cid" not in page.url: + if self._handle_username_setup(page): + time.sleep(random.uniform(4, 7)) + + # Step 12: 再次检测 403(导航后可能出现) + access_error = self._check_access_restricted(page, email) + if access_error: + return access_error + + # Step 13: 等待 URL 参数生成(csesidx 和 cid) + if not self._wait_for_business_params(page): + page.refresh() + time.sleep(random.uniform(4, 7)) + if not self._wait_for_business_params(page): + self._log("error", "❌ URL 参数生成失败") + self._save_screenshot(page, "params_missing") + return {"success": False, "error": "URL parameters not found"} + + # Step 13: 提取配置 + self._log("info", "🎊 登录成功,提取配置...") + return self._extract_config(page, email) + + def _click_send_code_button(self, page) -> bool: + """点击发送验证码按钮(如果需要)""" + time.sleep(random.uniform(1.5, 3)) + max_send_attempts = 5 + # 适度退避延迟序列(秒) + retry_delays = [10, 10, 15, 15, 20] + + # 方法1: 直接通过ID查找 + direct_btn = page.ele("#sign-in-with-email", timeout=5) + if direct_btn: + for attempt in range(1, max_send_attempts + 1): + try: + self._last_send_error = "" + self._human_click(page, direct_btn) + if self._verify_code_send_by_network(page) or self._verify_code_send_status(page): + self._stop_listen(page) + return True + delay = retry_delays[min(attempt - 1, len(retry_delays) - 1)] + if self._last_send_error == "captcha_check_failed": + self._log("error", f"❌ 触发风控,建议更换代理IP ({attempt}/{max_send_attempts})") + else: + self._log("warning", f"⚠️ 发送失败,{delay}秒后重试 ({attempt}/{max_send_attempts})") + time.sleep(delay) + except Exception as e: + self._log("warning", f"⚠️ 点击失败: {e}") + self._stop_listen(page) + return False + + # 方法2: 通过关键词查找 + keywords = ["通过电子邮件发送验证码", "通过电子邮件发送", "email", "Email", "Send code", "Send verification", "Verification code"] + try: + buttons = page.eles("tag:button") + for btn in buttons: + text = (btn.text or "").strip() + if text and any(kw in text for kw in keywords): + for attempt in range(1, max_send_attempts + 1): + try: + self._last_send_error = "" + self._human_click(page, btn) + if self._verify_code_send_by_network(page) or self._verify_code_send_status(page): + self._stop_listen(page) + return True + delay = retry_delays[min(attempt - 1, len(retry_delays) - 1)] + if self._last_send_error == "captcha_check_failed": + self._log("error", f"❌ 触发风控,建议更换代理IP ({attempt}/{max_send_attempts})") + else: + self._log("warning", f"⚠️ 发送失败,{delay}秒后重试 ({attempt}/{max_send_attempts})") + time.sleep(delay) + except Exception as e: + self._log("warning", f"⚠️ 点击失败: {e}") + self._stop_listen(page) + return False + except Exception as e: + self._log("warning", f"⚠️ 搜索按钮异常: {e}") + + # 检查是否在 signin-error 页面(不应该继续尝试发送) + if "signin-error" in (page.url or ""): + self._stop_listen(page) + self._log("error", "❌ 在 signin-error 页面,无法发送验证码") + return False + + # 检查是否已经在验证码输入页面 + code_input = page.ele("css:input[jsname='ovqh0b']", timeout=2) or page.ele("css:input[name='pinInput']", timeout=1) + if code_input: + self._stop_listen(page) + self._log("info", "✅ 已在验证码输入页面") + + # 直接点击重新发送按钮(不管之前是否发送过) + if self._click_resend_code_button(page): + self._log("info", "✅ 已点击重新发送按钮") + return True + else: + self._log("warning", "⚠️ 未找到重新发送按钮,继续流程") + return True + + self._stop_listen(page) + self._log("error", "❌ 未找到发送验证码按钮") + return False + + def _stop_listen(self, page) -> None: + """安全地停止网络监听""" + try: + if hasattr(page, 'listen') and page.listen: + page.listen.stop() + except Exception: + pass + + def _verify_code_send_by_network(self, page) -> bool: + """通过监听网络请求验证验证码是否成功发送""" + try: + time.sleep(1) + + packets = [] + max_wait_seconds = 6 + deadline = time.time() + max_wait_seconds + try: + while time.time() < deadline: + got_any = False + for packet in page.listen.steps(timeout=1, gap=1): + packets.append(packet) + got_any = True + if got_any: + time.sleep(0.2) + else: + break + except Exception: + return False + + if not packets: + return False + + # 保存网络日志(仅用于调试) + self._save_network_packets(packets) + + found_batchexecute = False + found_batchexecute_error = False + + for packet in packets: + try: + url = str(packet.url) if hasattr(packet, 'url') else str(packet) + + if 'batchexecute' in url: + found_batchexecute = True + + try: + response = packet.response if hasattr(packet, 'response') else None + if response and hasattr(response, 'raw_body'): + body = response.raw_body + raw_body_str = str(body) + if "CAPTCHA_CHECK_FAILED" in raw_body_str: + found_batchexecute_error = True + self._last_send_error = "captcha_check_failed" + elif "SendEmailOtpError" in raw_body_str: + found_batchexecute_error = True + self._last_send_error = "send_email_otp_error" + except Exception: + pass + + except Exception: + continue + + if found_batchexecute: + if found_batchexecute_error: + return False + return True + else: + return False + + except Exception: + return False + + def _verify_code_send_status(self, page) -> bool: + """检测页面提示判断是否发送成功""" + time.sleep(random.uniform(1.5, 3)) + try: + success_keywords = ["验证码已发送", "code sent", "email sent", "check your email", "已发送"] + error_keywords = [ + "出了点问题", + "something went wrong", + "error", + "failed", + "try again", + "稍后再试", + "选择其他登录方法" + ] + selectors = [ + "css:.zyTWof-gIZMF", + "css:[role='alert']", + "css:aside", + ] + for selector in selectors: + try: + elements = page.eles(selector, timeout=1) + for elem in elements[:20]: + text = (elem.text or "").strip() + if not text: + continue + if any(kw in text for kw in error_keywords): + return False + if any(kw in text for kw in success_keywords): + return True + except Exception: + continue + return True + except Exception: + return True + + def _truncate_text(self, text: str, max_len: int = 2000) -> str: + if text is None: + return "" + if len(text) <= max_len: + return text + return text[:max_len] + f"...(truncated, total={len(text)})" + + def _save_network_packets(self, packets) -> None: + """保存网络日志(仅用于调试)""" + try: + from core.storage import _data_file_path + base_dir = _data_file_path(os.path.join("logs", "network")) + os.makedirs(base_dir, exist_ok=True) + ts = datetime.now().strftime("%Y%m%d-%H%M%S") + file_path = os.path.join(base_dir, f"network-{ts}.jsonl") + + def safe_str(value): + try: + return value if isinstance(value, str) else str(value) + except Exception: + return "" + + with open(file_path, "a", encoding="utf-8") as f: + for packet in packets: + try: + req = packet.request if hasattr(packet, "request") else None + resp = packet.response if hasattr(packet, "response") else None + fail = packet.fail_info if hasattr(packet, "fail_info") else None + + item = { + "url": safe_str(packet.url) if hasattr(packet, "url") else safe_str(packet), + "method": safe_str(packet.method) if hasattr(packet, "method") else "UNKNOWN", + "resourceType": safe_str(packet.resourceType) if hasattr(packet, "resourceType") else "", + "is_failed": bool(packet.is_failed) if hasattr(packet, "is_failed") else False, + "fail_info": safe_str(fail) if fail else "", + "request": { + "headers": req.headers if req and hasattr(req, "headers") else {}, + "postData": req.postData if req and hasattr(req, "postData") else "", + }, + "response": { + "status": resp.status if resp and hasattr(resp, "status") else 0, + "headers": resp.headers if resp and hasattr(resp, "headers") else {}, + "raw_body": resp.raw_body if resp and hasattr(resp, "raw_body") else "", + }, + } + f.write(json.dumps(item, ensure_ascii=False) + "\n") + except Exception as e: + f.write(json.dumps({"error": safe_str(e)}, ensure_ascii=False) + "\n") + except Exception: + pass + + def _wait_for_code_input(self, page, timeout: int = 30): + """等待验证码输入框出现""" + selectors = [ + "css:input[jsname='ovqh0b']", + "css:input[type='tel']", + "css:input[name='pinInput']", + "css:input[autocomplete='one-time-code']", + ] + for _ in range(timeout // 2): + for selector in selectors: + try: + el = page.ele(selector, timeout=1) + if el: + return el + except Exception: + continue + time.sleep(2) + return None + + def _simulate_human_input(self, element, text: str) -> bool: + """模拟人类输入(逐字符输入,带非均匀延迟) + + Args: + element: 输入框元素 + text: 要输入的文本 + + Returns: + bool: 是否成功 + """ + try: + # 先点击输入框获取焦点 + element.click() + time.sleep(random.uniform(0.2, 0.5)) + + # 逐字符输入,模拟真实打字节奏 + for i, char in enumerate(text): + element.input(char) + # 基础延迟 80-180ms(正常打字速度) + delay = random.uniform(0.08, 0.18) + # 每3-5个字符偶尔有更长的停顿(模拟犹豫/看屏幕) + if i > 0 and random.random() < 0.2: + delay += random.uniform(0.2, 0.5) + time.sleep(delay) + + # 输入完成后停顿(模拟核对) + time.sleep(random.uniform(0.3, 0.8)) + return True + except Exception: + return False + + def _human_click(self, page, element) -> None: + """模拟人类点击:先移动鼠标到元素附近,再点击""" + try: + # 尝试用 actions 链模拟鼠标移动 + 点击 + page.actions.move_to(element) + time.sleep(random.uniform(0.1, 0.3)) + page.actions.click() + except Exception: + # 降级为直接点击 + element.click() + + def _random_scroll(self, page) -> None: + """模拟真实用户的页面滚动行为""" + try: + scroll_amount = random.randint(50, 200) + page.run_js(f"window.scrollBy(0, {scroll_amount})") + time.sleep(random.uniform(0.3, 0.8)) + # 有时候滚回去一点 + if random.random() < 0.3: + page.run_js(f"window.scrollBy(0, -{random.randint(20, 80)})") + time.sleep(random.uniform(0.2, 0.5)) + except Exception: + pass + + def _find_verify_button(self, page): + """查找验证按钮(排除重新发送按钮)""" + try: + buttons = page.eles("tag:button") + for btn in buttons: + text = (btn.text or "").strip().lower() + if text and "重新" not in text and "发送" not in text and "resend" not in text and "send" not in text: + return btn + except Exception: + pass + return None + + def _click_resend_code_button(self, page) -> bool: + """点击重新发送验证码按钮""" + time.sleep(random.uniform(1.5, 3)) + + # 查找包含重新发送关键词的按钮(与 _find_verify_button 相反) + try: + buttons = page.eles("tag:button") + for btn in buttons: + text = (btn.text or "").strip().lower() + if text and ("重新" in text or "resend" in text): + try: + self._log("info", f"🔄 点击重新发送按钮") + self._human_click(page, btn) + time.sleep(random.uniform(1.5, 3)) + return True + except Exception: + pass + except Exception: + pass + + return False + + def _check_access_restricted(self, page, email: str = "") -> dict | None: + """检测 403 Access Restricted 页面,返回错误 dict 或 None""" + domain = email.split("@")[1] if "@" in email else "unknown" + error_msg = f"403 域名封禁 ({domain})" + + # 方法1: 搜索 h1 标签 + try: + h1 = page.ele("tag:h1", timeout=2) + h1_text = h1.text if h1 else "" + if h1_text and "Access Restricted" in h1_text: + self._log("error", "⛔ 403 Access Restricted: email banned by Google") + self._log("error", f"⛔ 403 访问受限,域名 {domain} 可能已被 Google 封禁") + self._save_screenshot(page, "access_restricted_403") + return {"success": False, "error": error_msg} + except Exception: + pass + + # 方法2: body 文本 + try: + body = page.ele("tag:body", timeout=2) + body_text = (body.text or "")[:500] if body else "" + if "Access Restricted" in body_text: + self._log("error", "⛔ 403 Access Restricted: email banned by Google") + self._log("error", f"⛔ 403 访问受限,域名 {domain} 可能已被 Google 封禁") + self._save_screenshot(page, "access_restricted_403") + return {"success": False, "error": error_msg} + except Exception: + pass + + # 方法3: page.html 源码 + try: + html = (page.html or "")[:2000] + if "Access Restricted" in html: + self._log("error", "⛔ 403 Access Restricted: email banned by Google") + self._log("error", f"⛔ 403 访问受限,域名 {domain} 可能已被 Google 封禁") + self._save_screenshot(page, "access_restricted_403") + return {"success": False, "error": error_msg} + except Exception: + pass + + return None + + def _handle_agreement_page(self, page) -> None: + """处理协议页面""" + if "/admin/create" in page.url: + agree_btn = page.ele("css:button.agree-button", timeout=5) + if agree_btn: + self._human_click(page, agree_btn) + time.sleep(random.uniform(2, 4)) + + def _wait_for_cid(self, page, timeout: int = 10) -> bool: + """等待URL包含cid""" + for _ in range(timeout): + if "cid" in page.url: + return True + time.sleep(1) + return False + + def _wait_for_business_params(self, page, timeout: int = 30) -> bool: + """等待业务页面参数生成(csesidx 和 cid)""" + for _ in range(timeout): + url = page.url + if "csesidx=" in url and "/cid/" in url: + return True + time.sleep(1) + return False + + def _handle_username_setup(self, page, is_new_account: bool = False) -> bool: + """处理用户名设置页面(is_new_account=True 时启用按钮兜底和延长超时)""" + current_url = page.url + + if "auth.business.gemini.google/login" in current_url: + return False + + # 精准选择器(参考实际页面 DOM,优先级从高到低) + selectors = [ + "css:input[formcontrolname='fullName']", + "css:input#mat-input-0", + "css:input[placeholder='全名']", + "css:input[placeholder='Full name']", + "css:input[name='displayName']", + "css:input[aria-label*='用户名' i]", + "css:input[aria-label*='display name' i]", + "css:input[type='text']", + ] + + # 轮询等待输入框出现(最多30秒,每秒检查一次) + # 与参考代码对齐:页面加载慢时不会过早放弃 + username_input = None + self._log("info", "⏳ 等待用户名输入框出现(最多30秒)...") + for i in range(30): + for selector in selectors: + try: + el = page.ele(selector, timeout=1) + if el: + username_input = el + self._log("info", f"✅ 找到用户名输入框: {selector}") + break + except Exception: + continue + if username_input: + break + time.sleep(1) + + if not username_input: + self._log("warning", "⚠️ 30秒内未找到用户名输入框,跳过此步骤") + return False + + name = random.choice(REGISTER_NAMES) + self._log("info", f"✏️ 输入姓名: {name}") + + try: + # 清空输入框 + username_input.click() + time.sleep(random.uniform(0.2, 0.5)) + username_input.clear() + time.sleep(random.uniform(0.1, 0.3)) + + # 尝试模拟人类输入,失败则降级到直接注入 + if not self._simulate_human_input(username_input, name): + username_input.input(name) + time.sleep(0.3) + + # 回车提交 + username_input.input("\n") + + if is_new_account: + # 注册专用:回车后等待1.5秒,若未跳转则用按钮兜底 + time.sleep(random.uniform(1.5, 3)) + if "cid" not in page.url: + self._log("info", "⌨️ 回车未跳转,尝试点击提交按钮...") + try: + for btn in page.eles("tag:button"): + try: + if btn.is_displayed() and btn.is_enabled(): + btn.click() + self._log("info", "✅ 已点击提交按钮(兜底)") + time.sleep(1) + break + except Exception: + continue + except Exception as e: + self._log("warning", f"⚠️ 按钮兜底失败: {e}") + + # 注册专用:等待45秒,失败则刷新再等15秒 + if not self._wait_for_cid(page, timeout=45): + self._log("warning", "⚠️ 用户名提交后未检测到 cid 参数,尝试刷新...") + page.refresh() + time.sleep(random.uniform(2, 4)) + if not self._wait_for_cid(page, timeout=15): + self._log("error", "❌ 刷新后仍未检测到 cid 参数") + self._save_screenshot(page, "step7_after_verify") + return False + else: + # 登录刷新:原有30秒逻辑 + if not self._wait_for_cid(page, timeout=30): + self._log("warning", "⚠️ 用户名提交后未检测到 cid 参数") + return False + + return True + except Exception as e: + self._log("warning", f"⚠️ 用户名设置异常: {e}") + return False + + def _extract_config(self, page, email: str) -> dict: + """提取配置(轮询等待 cookie 到位)""" + try: + if "cid/" not in page.url: + page.get("https://business.gemini.google/", timeout=self.timeout) + time.sleep(random.uniform(2, 4)) + + url = page.url + if "cid/" not in url: + return {"success": False, "error": "cid not found"} + + config_id = url.split("cid/")[1].split("?")[0].split("/")[0] + csesidx = url.split("csesidx=")[1].split("&")[0] if "csesidx=" in url else "" + + # 轮询等待关键 cookie 到位(最多10秒) + ses = None + host = None + ses_obj = None + for _ in range(10): + cookies = page.cookies() + ses = next((c["value"] for c in cookies if c["name"] == "__Secure-C_SES"), None) + host = next((c["value"] for c in cookies if c["name"] == "__Host-C_OSES"), None) + ses_obj = next((c for c in cookies if c["name"] == "__Secure-C_SES"), None) + if ses and host: + break + time.sleep(1) + + if not ses or not host: + self._log("warning", f"⚠️ Cookie 不完整 (ses={'有' if ses else '无'}, host={'有' if host else '无'})") + + # 使用北京时区,确保时间计算正确(Cookie expiry 是 UTC 时间戳) + beijing_tz = timezone(timedelta(hours=8)) + if ses_obj and "expiry" in ses_obj: + cookie_expire_beijing = datetime.fromtimestamp(ses_obj["expiry"], tz=beijing_tz) + expires_at = (cookie_expire_beijing - timedelta(hours=12)).strftime("%Y-%m-%d %H:%M:%S") + else: + expires_at = (datetime.now(beijing_tz) + timedelta(hours=12)).strftime("%Y-%m-%d %H:%M:%S") + + config = { + "id": email, + "csesidx": csesidx, + "config_id": config_id, + "secure_c_ses": ses, + "host_c_oses": host, + "expires_at": expires_at, + } + + # 提取试用期信息 + trial_end = self._extract_trial_end(page, csesidx, config_id) + if trial_end: + config["trial_end"] = trial_end + + return {"success": True, "config": config} + except Exception as e: + return {"success": False, "error": str(e)} + + def _extract_trial_end(self, page, csesidx: str, config_id: str) -> Optional[str]: + """从页面中提取试用期到期日期,不跳转到可能 400 的深层路径""" + # re 已在文件顶部导入 + try: + self._log("info", "📅 获取试用期信息...") + + def _days_to_end_date(days: int) -> str: + end_date = (datetime.now(timezone(timedelta(hours=8))) + timedelta(days=days)).strftime("%Y-%m-%d") + self._log("info", f"📅 试用期剩余 {days} 天,到期日: {end_date}") + return end_date + + def _search_page_source(source: str) -> Optional[str]: + """在页面源码中搜索试用期信息""" + # 格式1: "daysLeft":29 (JSON数据) + m = re.search(r'"daysLeft"\s*:\s*(\d+)', source) + if m: + return _days_to_end_date(int(m.group(1))) + # 格式2: "trialDaysRemaining":29 + m = re.search(r'"trialDaysRemaining"\s*:\s*(\d+)', source) + if m: + return _days_to_end_date(int(m.group(1))) + # 格式3: 日期数组 "[2026,3,25]" 形式 (batchexecute格式) + m = re.search(r'\[(\d{4}),(\d{1,2}),(\d{1,2})\].*?\[(\d{4}),(\d{1,2}),(\d{1,2})\]', source) + if m: + # 取第二个日期(结束日期) + try: + end_date = f"{m.group(4):0>4}-{int(m.group(5)):02d}-{int(m.group(6)):02d}" + # 简单校验年份合理 + if 2025 <= int(m.group(4)) <= 2030: + self._log("info", f"📅 试用期到期日: {end_date}") + return end_date + except Exception: + pass + # 格式4: "29 days left" 或 "还剩29天" + m = re.search(r'(\d+)\s*days?\s*left', source, re.IGNORECASE) + if m: + return _days_to_end_date(int(m.group(1))) + m = re.search(r'还剩\s*(\d+)\s*天', source) + if m: + return _days_to_end_date(int(m.group(1))) + return None + + # ——— 方式1: 当前页面(刚登录完,不需要跳转)——— + try: + source = page.html + result = _search_page_source(source or "") + if result: + return result + except Exception: + pass + + # ——— 方式2: 跳转到 /settings(不带 billing/plans 后缀,SPA可以处理)——— + try: + settings_url = f"https://business.gemini.google/cid/{config_id}/settings?csesidx={csesidx}" + page.get(settings_url, timeout=self.timeout) + time.sleep(random.uniform(1.5, 3)) + source = page.html + result = _search_page_source(source or "") + if result: + return result + except Exception: + pass + + # ——— 方式3: 跳转到主页(最保险)——— + try: + main_url = f"https://business.gemini.google/cid/{config_id}?csesidx={csesidx}" + page.get(main_url, timeout=self.timeout) + time.sleep(random.uniform(1.5, 3)) + source = page.html + result = _search_page_source(source or "") + if result: + return result + except Exception: + pass + + self._log("warning", "⚠️ 未能获取试用期信息(页面中未找到相关数据)") + return None + except Exception as e: + self._log("warning", f"⚠️ 获取试用期失败: {e}") + return None + + def _save_screenshot(self, page, name: str) -> None: + """保存截图""" + try: + from core.storage import _data_file_path + screenshot_dir = _data_file_path("automation") + os.makedirs(screenshot_dir, exist_ok=True) + path = os.path.join(screenshot_dir, f"{name}_{int(time.time())}.png") + page.get_screenshot(path=path) + except Exception: + pass + + def _log(self, level: str, message: str) -> None: + """记录日志""" + if self.log_callback: + try: + self.log_callback(level, message) + except TaskCancelledError: + raise + except Exception: + pass + + def _cleanup_user_data(self, user_data_dir: Optional[str]) -> None: + """清理浏览器用户数据目录""" + if not user_data_dir: + return + try: + import shutil + if os.path.exists(user_data_dir): + shutil.rmtree(user_data_dir, ignore_errors=True) + except Exception: + pass + + @staticmethod + def _get_ua() -> str: + """生成随机User-Agent(使用当前主流 Chrome 版本)""" + major = random.choice([132, 133, 134, 135]) + v = f"{major}.0.{random.randint(6800, 6950)}.{random.randint(50, 150)}" + return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{v} Safari/537.36" diff --git a/core/google_api.py b/core/google_api.py new file mode 100644 index 0000000000000000000000000000000000000000..7f82306363efbbdded4f520063a62fa673610231 --- /dev/null +++ b/core/google_api.py @@ -0,0 +1,339 @@ +"""Google API交互模块 + +负责与Google Gemini Business API的所有交互操作 +""" +import asyncio +import json +import logging +import os +import re +import time +import uuid +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, List + +import httpx +from fastapi import HTTPException + +if TYPE_CHECKING: + from main import AccountManager + +logger = logging.getLogger(__name__) + +# Google API 基础URL +GEMINI_API_BASE = "https://biz-discoveryengine.googleapis.com/v1alpha" + + + +def get_common_headers(jwt: str, user_agent: str) -> dict: + """生成通用请求头""" + return { + "accept": "*/*", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", + "authorization": f"Bearer {jwt}", + "content-type": "application/json", + "origin": "https://business.gemini.google", + "referer": "https://business.gemini.google/", + "user-agent": user_agent, + "x-server-timeout": "1800", + "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "cross-site", + } + + +async def make_request_with_jwt_retry( + account_mgr: "AccountManager", + method: str, + url: str, + http_client: httpx.AsyncClient, + user_agent: str, + request_id: str = "", + **kwargs +) -> httpx.Response: + """通用HTTP请求,自动处理JWT过期重试 + + Args: + account_mgr: AccountManager实例 + method: HTTP方法 (GET/POST) + url: 请求URL + http_client: httpx客户端 + user_agent: User-Agent字符串 + request_id: 请求ID(用于日志) + **kwargs: 传递给httpx的其他参数(如json, headers等) + + Returns: + httpx.Response对象 + """ + jwt = await account_mgr.get_jwt(request_id) + headers = get_common_headers(jwt, user_agent) + + # 合并用户提供的headers(如果有) + extra_headers = kwargs.pop("headers", None) + if extra_headers: + headers.update(extra_headers) + + # 提取timeout参数(如果有),单独传给httpx + req_timeout = kwargs.pop("timeout", None) + + # 发起请求 + req_kwargs = {**kwargs} + if req_timeout is not None: + req_kwargs["timeout"] = req_timeout + + if method.upper() == "GET": + resp = await http_client.get(url, headers=headers, **req_kwargs) + elif method.upper() == "POST": + resp = await http_client.post(url, headers=headers, **req_kwargs) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + # 如果401,刷新JWT后重试一次 + if resp.status_code == 401: + jwt = await account_mgr.get_jwt(request_id) + headers = get_common_headers(jwt, user_agent) + if extra_headers: + headers.update(extra_headers) + + if method.upper() == "GET": + resp = await http_client.get(url, headers=headers, **req_kwargs) + elif method.upper() == "POST": + resp = await http_client.post(url, headers=headers, **req_kwargs) + + return resp + + +async def create_google_session( + account_manager: "AccountManager", + http_client: httpx.AsyncClient, + user_agent: str, + request_id: str = "" +) -> str: + """创建Google Session""" + jwt = await account_manager.get_jwt(request_id) + headers = get_common_headers(jwt, user_agent) + body = { + "configId": account_manager.config.config_id, + "additionalParams": {"token": "-"}, + "createSessionRequest": { + "session": {"name": "", "displayName": ""} + } + } + + req_tag = f"[req_{request_id}] " if request_id else "" + r = await http_client.post( + f"{GEMINI_API_BASE}/locations/global/widgetCreateSession", + headers=headers, + json=body, + timeout=30.0, + ) + if r.status_code != 200: + logger.error(f"[SESSION] [{account_manager.config.account_id}] {req_tag}Session 创建失败: {r.status_code}") + raise HTTPException(r.status_code, "createSession failed") + sess_name = r.json()["session"]["name"] + logger.info(f"[SESSION] [{account_manager.config.account_id}] {req_tag}创建成功: {sess_name[-12:]}") + return sess_name + + +async def upload_context_file( + session_name: str, + mime_type: str, + base64_content: str, + account_manager: "AccountManager", + http_client: httpx.AsyncClient, + user_agent: str, + request_id: str = "" +) -> str: + """上传文件到指定 Session,返回 fileId""" + jwt = await account_manager.get_jwt(request_id) + headers = get_common_headers(jwt, user_agent) + + # 生成随机文件名 + ext = mime_type.split('/')[-1] if '/' in mime_type else "bin" + file_name = f"upload_{int(time.time())}_{uuid.uuid4().hex[:6]}.{ext}" + + body = { + "configId": account_manager.config.config_id, + "additionalParams": {"token": "-"}, + "addContextFileRequest": { + "name": session_name, + "fileName": file_name, + "mimeType": mime_type, + "fileContents": base64_content + } + } + + r = await http_client.post( + f"{GEMINI_API_BASE}/locations/global/widgetAddContextFile", + headers=headers, + json=body, + timeout=60.0, + ) + + req_tag = f"[req_{request_id}] " if request_id else "" + if r.status_code != 200: + logger.error(f"[FILE] [{account_manager.config.account_id}] {req_tag}文件上传失败: {r.status_code}") + error_text = r.text + if r.status_code == 400: + try: + payload = json.loads(r.text or "{}") + message = payload.get("error", {}).get("message", "") + except Exception: + message = "" + if "Unsupported file type" in message: + mime_type = message.split("Unsupported file type:", 1)[-1].strip() + hint = f"不支持的文件类型: {mime_type}。请转换为 PDF、图片或纯文本后再上传。" + raise HTTPException(400, hint) + raise HTTPException(r.status_code, f"Upload failed: {error_text}") + + data = r.json() + file_id = data.get("addContextFileResponse", {}).get("fileId") + logger.info(f"[FILE] [{account_manager.config.account_id}] {req_tag}文件上传成功: {mime_type}") + return file_id + + +async def get_session_file_metadata( + account_mgr: "AccountManager", + session_name: str, + http_client: httpx.AsyncClient, + user_agent: str, + request_id: str = "" +) -> dict: + """获取session中的文件元数据,包括正确的session路径""" + body = { + "configId": account_mgr.config.config_id, + "additionalParams": {"token": "-"}, + "listSessionFileMetadataRequest": { + "name": session_name, + "filter": "file_origin_type = AI_GENERATED" + } + } + + resp = await make_request_with_jwt_retry( + account_mgr, + "POST", + f"{GEMINI_API_BASE}/locations/global/widgetListSessionFileMetadata", + http_client, + user_agent, + request_id, + json=body, + timeout=30.0 + ) + + if resp.status_code != 200: + logger.warning(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 获取文件元数据失败: {resp.status_code}") + return {} + + data = resp.json() + result = {} + file_metadata_list = data.get("listSessionFileMetadataResponse", {}).get("fileMetadata", []) + + for fm in file_metadata_list: + fid = fm.get("fileId") + if fid: + result[fid] = fm + + return result + + +def build_image_download_url(session_name: str, file_id: str) -> str: + """构造图片下载URL""" + return f"{GEMINI_API_BASE}/{session_name}:downloadFile?fileId={file_id}&alt=media" + + +async def download_image_with_jwt( + account_mgr: "AccountManager", + session_name: str, + file_id: str, + http_client: httpx.AsyncClient, + user_agent: str, + request_id: str = "", + max_retries: int = 3 +) -> bytes: + """ + 使用JWT认证下载图片(带超时和重试机制) + + Args: + account_mgr: 账户管理器 + session_name: Session名称 + file_id: 文件ID + http_client: httpx客户端 + user_agent: User-Agent字符串 + request_id: 请求ID + max_retries: 最大重试次数(默认3次) + + Returns: + 图片字节数据 + + Raises: + HTTPException: 下载失败 + asyncio.TimeoutError: 超时 + """ + url = build_image_download_url(session_name, file_id) + logger.info(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 开始下载图片: {file_id[:8]}...") + + for attempt in range(max_retries): + try: + # 3分钟超时(180秒)- 使用 wait_for 兼容 Python 3.10 + resp = await asyncio.wait_for( + make_request_with_jwt_retry( + account_mgr, + "GET", + url, + http_client, + user_agent, + request_id, + follow_redirects=True + ), + timeout=180 + ) + + resp.raise_for_status() + logger.info(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 图片下载成功: {file_id[:8]}... ({len(resp.content)} bytes)") + return resp.content + + except asyncio.TimeoutError: + logger.warning(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 图片下载超时 (尝试 {attempt + 1}/{max_retries}): {file_id[:8]}...") + if attempt == max_retries - 1: + raise HTTPException(504, f"Image download timeout after {max_retries} attempts") + await asyncio.sleep(2 ** attempt) # 指数退避:2s, 4s, 8s + + except httpx.HTTPError as e: + logger.warning(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 图片下载失败 (尝试 {attempt + 1}/{max_retries}): {type(e).__name__}") + if attempt == max_retries - 1: + raise HTTPException(500, f"Image download failed: {str(e)[:100]}") + await asyncio.sleep(2 ** attempt) # 指数退避 + + except Exception as e: + logger.error(f"[IMAGE] [{account_mgr.config.account_id}] [req_{request_id}] 图片下载异常: {type(e).__name__}: {str(e)[:100]}") + raise + + # 不应该到达这里 + raise HTTPException(500, "Image download failed unexpectedly") + + +def save_image_to_hf(image_data: bytes, chat_id: str, file_id: str, mime_type: str, base_url: str, image_dir: str, url_path: str = "images") -> str: + """保存图片到持久化存储,返回完整的公开URL""" + ext_map = { + "image/png": ".png", + "image/jpeg": ".jpg", + "image/gif": ".gif", + "image/webp": ".webp", + "video/mp4": ".mp4", + "video/webm": ".webm", + "video/quicktime": ".mov" + } + ext = ext_map.get(mime_type, ".png") + + filename = f"{chat_id}_{file_id}{ext}" + save_path = os.path.join(image_dir, filename) + + # 目录已在启动时创建,无需重复创建 + with open(save_path, "wb") as f: + f.write(image_data) + + return f"{base_url}/{url_path}/{filename}" diff --git a/core/gptmail_client.py b/core/gptmail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..5043b6ae43c42c9511f508d82e4d29a4cc2ecb73 --- /dev/null +++ b/core/gptmail_client.py @@ -0,0 +1,219 @@ +import os +import random +import string +import time +from datetime import datetime +from typing import Any, Dict, List, Optional + +import requests + +from core.mail_utils import extract_verification_code +from core.proxy_utils import request_with_proxy_fallback + + +class GPTMailClient: + """GPTMail 临时邮箱客户端""" + + def __init__( + self, + base_url: str = "https://mail.chatgpt.org.uk", + proxy: str = "", + verify_ssl: bool = True, + api_key: str = "", + domain: str = "", + log_callback=None, + ) -> None: + self.base_url = (base_url or "").rstrip("/") + self.verify_ssl = verify_ssl + self.proxy_url = (proxy or "").strip() + self.api_key = (api_key or "").strip() + self.domain = (domain or "").strip() + self.log_callback = log_callback + + self.email: Optional[str] = None + + def set_credentials(self, email: str, password: Optional[str] = None) -> None: + self.email = email + + def _log(self, level: str, message: str) -> None: + if self.log_callback: + try: + self.log_callback(level, message) + except Exception: + pass + + def _request(self, method: str, url: str, **kwargs) -> requests.Response: + headers = kwargs.pop("headers", None) or {} + if self.api_key and "X-API-Key" not in headers: + headers["X-API-Key"] = self.api_key + kwargs["headers"] = headers + + self._log("info", f"📤 发送 {method} 请求: {url}") + if "params" in kwargs and kwargs["params"]: + self._log("info", f"🔎 Query: {kwargs['params']}") + if "json" in kwargs and kwargs["json"] is not None: + self._log("info", f"📦 请求体: {kwargs['json']}") + + proxies = {"http": self.proxy_url, "https": self.proxy_url} if self.proxy_url else None + + res = request_with_proxy_fallback( + requests.request, + method, + url, + proxies=proxies, + verify=self.verify_ssl, + timeout=kwargs.pop("timeout", 15), + **kwargs, + ) + self._log("info", f"📥 收到响应: HTTP {res.status_code}") + log_body = os.getenv("GPTMAIL_LOG_BODY", "").strip().lower() in ("1", "true", "yes", "y", "on") + if res.content and (log_body or res.status_code >= 400): + try: + self._log("info", f"📄 响应内容: {res.text[:500]}") + except Exception: + pass + return res + + def generate_email(self, domain: Optional[str] = None) -> Optional[str]: + """生成一个新的邮箱地址。""" + if not self.base_url: + self._log("error", "❌ GPTMail base_url 为空") + return None + + rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=10)) + timestamp = str(int(time.time()))[-4:] + prefix = f"t{timestamp}{rand}" + + payload: Dict[str, Any] = {"prefix": prefix} + # 优先使用传入的 domain,其次使用配置的 domain + effective_domain = domain or self.domain + if effective_domain: + payload["domain"] = effective_domain + + url = f"{self.base_url}/api/generate-email" + try: + res = self._request("POST", url, json=payload) + if res.status_code != 200: + self._log("error", f"❌ 生成邮箱失败: HTTP {res.status_code}") + return None + body = res.json() if res.content else {} + if not body.get("success"): + self._log("error", f"❌ 生成邮箱失败: {body.get('error') or 'unknown error'}") + return None + email = ((body.get("data") or {}).get("email") or "").strip() + if not email: + self._log("error", "❌ 生成邮箱成功但响应缺少 email") + return None + self.email = email + self._log("info", f"✅ GPTMail 邮箱生成成功: {email}") + return email + except Exception as exc: + self._log("error", f"❌ 生成邮箱异常: {exc}") + return None + + def register_account(self, domain: Optional[str] = None) -> bool: + """生成一个新的邮箱地址并视为注册成功。""" + return bool(self.generate_email(domain=domain)) + + def _list_emails(self, email: str) -> List[Dict[str, Any]]: + url = f"{self.base_url}/api/emails" + res = self._request("GET", url, params={"email": email}) + if res.status_code != 200: + self._log("error", f"❌ 获取邮件列表失败: HTTP {res.status_code}") + return [] + body = res.json() if res.content else {} + if not body.get("success"): + self._log("error", f"❌ 获取邮件列表失败: {body.get('error') or 'unknown error'}") + return [] + return list(((body.get("data") or {}).get("emails") or [])) + + def _get_email(self, mail_id: str) -> Optional[Dict[str, Any]]: + url = f"{self.base_url}/api/email/{mail_id}" + res = self._request("GET", url) + if res.status_code != 200: + self._log("warning", f"⚠️ 获取邮件详情失败: HTTP {res.status_code}") + return None + body = res.json() if res.content else {} + if not body.get("success"): + self._log("warning", f"⚠️ 获取邮件详情失败: {body.get('error') or 'unknown error'}") + return None + return body.get("data") or None + + def fetch_verification_code(self, since_time: Optional[datetime] = None) -> Optional[str]: + """获取验证码(从邮件内容提取)。""" + if not self.email: + return None + + try: + self._log("info", "📬 正在拉取 GPTMail 邮件列表...") + emails = self._list_emails(self.email) + if not emails: + self._log("info", "📭 邮箱为空,暂无邮件") + return None + + emails = sorted(emails, key=lambda item: int(item.get("timestamp") or 0), reverse=True) + self._log("info", f"📨 收到 {len(emails)} 封邮件,开始检查验证码...") + + for msg in emails: + msg_id = str(msg.get("id") or "").strip() + if not msg_id: + continue + + ts = msg.get("timestamp") + if since_time and ts: + try: + msg_time = datetime.fromtimestamp(int(ts)).astimezone().replace(tzinfo=None) + if msg_time < since_time: + continue + except Exception: + pass + + content = (msg.get("content") or "") + (msg.get("html_content") or "") + code = extract_verification_code(content) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + + detail = self._get_email(msg_id) + if not detail: + continue + + detail_text = ( + (detail.get("content") or "") + + (detail.get("html_content") or "") + + (detail.get("raw_content") or "") + ) + code = extract_verification_code(detail_text) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + + self._log("warning", "⚠️ 所有邮件中均未找到验证码") + return None + except Exception as exc: + self._log("error", f"❌ 获取验证码异常: {exc}") + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time: Optional[datetime] = None, + ) -> Optional[str]: + if not self.email: + return None + + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + if i < max_retries: + time.sleep(interval) + + self._log("error", "❌ 验证码获取超时") + return None diff --git a/core/jwt.py b/core/jwt.py new file mode 100644 index 0000000000000000000000000000000000000000..946045cfc2bde03c7308cfeaccf8a68d6814c498 --- /dev/null +++ b/core/jwt.py @@ -0,0 +1,102 @@ +"""JWT管理模块 + +负责JWT token的生成、刷新和管理 +""" +import asyncio +import base64 +import hashlib +import hmac +import json +import logging +import time +from typing import TYPE_CHECKING + +import httpx +from fastapi import HTTPException + +if TYPE_CHECKING: + from main import AccountConfig + +logger = logging.getLogger(__name__) + + +def urlsafe_b64encode(data: bytes) -> str: + return base64.urlsafe_b64encode(data).decode().rstrip("=") + +def kq_encode(s: str) -> str: + b = bytearray() + for ch in s: + v = ord(ch) + if v > 255: + b.append(v & 255) + b.append(v >> 8) + else: + b.append(v) + return urlsafe_b64encode(bytes(b)) + +def create_jwt(key_bytes: bytes, key_id: str, csesidx: str) -> str: + now = int(time.time()) + header = {"alg": "HS256", "typ": "JWT", "kid": key_id} + payload = { + "iss": "https://business.gemini.google", + "aud": "https://biz-discoveryengine.googleapis.com", + "sub": f"csesidx/{csesidx}", + "iat": now, + "exp": now + 300, + "nbf": now, + } + header_b64 = kq_encode(json.dumps(header, separators=(",", ":"))) + payload_b64 = kq_encode(json.dumps(payload, separators=(",", ":"))) + message = f"{header_b64}.{payload_b64}" + sig = hmac.new(key_bytes, message.encode(), hashlib.sha256).digest() + return f"{message}.{urlsafe_b64encode(sig)}" + + +class JWTManager: + """JWT token管理器 + + 负责JWT的获取、刷新和缓存 + """ + def __init__(self, config: "AccountConfig", http_client: httpx.AsyncClient, user_agent: str) -> None: + self.config = config + self.http_client = http_client + self.user_agent = user_agent + self.jwt: str = "" + self.expires: float = 0 + self._lock = asyncio.Lock() + + async def get(self, request_id: str = "") -> str: + """获取JWT token(自动刷新)""" + async with self._lock: + if time.time() > self.expires: + await self._refresh(request_id) + return self.jwt + + async def _refresh(self, request_id: str = "") -> None: + """刷新JWT token""" + cookie = f"__Secure-C_SES={self.config.secure_c_ses}" + if self.config.host_c_oses: + cookie += f"; __Host-C_OSES={self.config.host_c_oses}" + + req_tag = f"[req_{request_id}] " if request_id else "" + r = await self.http_client.get( + "https://business.gemini.google/auth/getoxsrf", + params={"csesidx": self.config.csesidx}, + headers={ + "cookie": cookie, + "user-agent": self.user_agent, + "referer": "https://business.gemini.google/" + }, + ) + if r.status_code != 200: + error_body = r.text[:200] if r.text else "" + logger.error(f"[AUTH] [{self.config.account_id}] {req_tag}JWT 刷新失败: {r.status_code} {error_body}") + raise HTTPException(r.status_code, f"getoxsrf failed: {error_body}") + + txt = r.text[4:] if r.text.startswith(")]}'") else r.text + data = json.loads(txt) + + key_bytes = base64.urlsafe_b64decode(data["xsrfToken"] + "==") + self.jwt = create_jwt(key_bytes, data["keyId"], self.config.csesidx) + self.expires = time.time() + 270 + logger.info(f"[AUTH] [{self.config.account_id}] {req_tag}JWT 刷新成功") diff --git a/core/login_service.py b/core/login_service.py new file mode 100644 index 0000000000000000000000000000000000000000..7a35369d2687ce9ee9e6fd4393a96ded79bc4226 --- /dev/null +++ b/core/login_service.py @@ -0,0 +1,557 @@ +import asyncio +import logging +import os +import time +import uuid +from dataclasses import dataclass, field +from datetime import datetime, timedelta, timezone +from typing import Any, Callable, Dict, List, Optional + +from core.account import load_accounts_from_source +from core.base_task_service import BaseTask, BaseTaskService, TaskCancelledError, TaskStatus +from core.config import config +from core.mail_providers import create_temp_mail_client +from core.gemini_automation import GeminiAutomation +from core.microsoft_mail_client import MicrosoftMailClient +from core.proxy_utils import parse_proxy_setting + +logger = logging.getLogger("gemini.login") + +# 常量定义 +CONFIG_CHECK_INTERVAL_SECONDS = 60 # 配置检查间隔(秒) + + +@dataclass +class LoginTask(BaseTask): + """登录任务数据类""" + account_ids: List[str] = field(default_factory=list) + + def to_dict(self) -> dict: + """转换为字典""" + base_dict = super().to_dict() + base_dict["account_ids"] = self.account_ids + return base_dict + + +class LoginService(BaseTaskService[LoginTask]): + """登录服务类 - 统一任务管理""" + + def __init__( + self, + multi_account_mgr, + http_client, + user_agent: str, + retry_policy, + session_cache_ttl_seconds: int, + global_stats_provider: Callable[[], dict], + set_multi_account_mgr: Optional[Callable[[Any], None]] = None, + ) -> None: + super().__init__( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats_provider, + set_multi_account_mgr, + log_prefix="REFRESH", + ) + self._is_polling = False + # 防重复:记录每个账号最后一次成功刷新的时间戳 + self._refresh_timestamps: Dict[str, float] = {} + # cron 触发记录:避免同一时间点当天重复触发 + self._triggered_today: set = set() + + def _get_running_task(self) -> Optional[LoginTask]: + """获取正在运行或等待中的任务""" + for task in self._tasks.values(): + if isinstance(task, LoginTask) and task.status in (TaskStatus.PENDING, TaskStatus.RUNNING): + return task + return None + + async def start_login(self, account_ids: List[str]) -> LoginTask: + """ + 启动登录任务 - 统一任务管理 + - 如果有正在运行的任务,将新账户添加到该任务(去重) + - 如果没有正在运行的任务,创建新任务 + """ + async with self._lock: + if not account_ids: + raise ValueError("账户列表不能为空") + + # 检查是否有正在运行的任务 + running_task = self._get_running_task() + + if running_task: + # 将新账户添加到现有任务(去重) + new_accounts = [aid for aid in account_ids if aid not in running_task.account_ids] + + if new_accounts: + running_task.account_ids.extend(new_accounts) + self._append_log( + running_task, + "info", + f"📝 添加 {len(new_accounts)} 个账户到现有任务 (总计: {len(running_task.account_ids)})" + ) + else: + self._append_log(running_task, "info", "📝 所有账户已在当前任务中") + + return running_task + + # 创建新任务 + task = LoginTask(id=str(uuid.uuid4()), account_ids=list(account_ids)) + self._tasks[task.id] = task + self._append_log(task, "info", f"📝 创建刷新任务 (账号数量: {len(task.account_ids)})") + + # 直接启动任务 + self._current_task_id = task.id + asyncio.create_task(self._run_task_directly(task)) + return task + + async def _run_task_directly(self, task: LoginTask) -> None: + """直接执行任务""" + try: + await self._run_one_task(task) + finally: + # 任务完成后清理 + async with self._lock: + if self._current_task_id == task.id: + self._current_task_id = None + + def _execute_task(self, task: LoginTask): + return self._run_login_async(task) + + async def _run_login_async(self, task: LoginTask) -> None: + """异步执行登录任务(支持取消)。""" + loop = asyncio.get_running_loop() + self._append_log(task, "info", f"🚀 刷新任务已启动 (共 {len(task.account_ids)} 个账号)") + + for idx, account_id in enumerate(task.account_ids, 1): + # 检查是否请求取消 + if task.cancel_requested: + self._append_log(task, "warning", f"login task cancelled: {task.cancel_reason or 'cancelled'}") + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + return + + try: + self._append_log(task, "info", f"📊 进度: {idx}/{len(task.account_ids)}") + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + self._append_log(task, "info", f"🔄 开始刷新账号: {account_id}") + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + result = await loop.run_in_executor(self._executor, self._refresh_one, account_id, task) + except TaskCancelledError: + # 线程侧已触发取消,直接结束任务 + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + return + except Exception as exc: + result = {"success": False, "email": account_id, "error": str(exc)} + task.progress += 1 + task.results.append(result) + + if result.get("success"): + task.success_count += 1 + # 记录刷新成功时间(防重复层 1) + self._refresh_timestamps[account_id] = time.time() + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + self._append_log(task, "info", f"🎉 刷新成功: {account_id}") + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + else: + task.fail_count += 1 + error = result.get('error', '未知错误') + self._append_log(task, "error", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + self._append_log(task, "error", f"❌ 刷新失败: {account_id}") + self._append_log(task, "error", f"❌ 失败原因: {error}") + self._append_log(task, "error", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + # 403 自动禁用账户 + if "403" in error: + try: + accounts = load_accounts_from_source() + for acc in accounts: + if acc.get("id") == account_id: + acc["disabled"] = True + acc["disabled_reason"] = "403 Access Restricted" + break + self._apply_accounts_update(accounts) + # 同步到内存中的 account manager + if account_id in self.multi_account_mgr.accounts: + mgr = self.multi_account_mgr.accounts[account_id] + mgr.config.disabled = True + mgr.disabled_reason = "403 Access Restricted" + self._append_log(task, "error", f"⛔ 已自动禁用账户: {account_id}") + except Exception as e: + self._append_log(task, "warning", f"⚠️ 自动禁用失败: {e}") + + # 账号之间等待 10 秒,避免资源争抢和风控 + if idx < len(task.account_ids) and not task.cancel_requested: + self._append_log(task, "info", "⏳ 等待 10 秒后处理下一个账号...") + await asyncio.sleep(10) + + if task.cancel_requested: + task.status = TaskStatus.CANCELLED + else: + task.status = TaskStatus.SUCCESS if task.fail_count == 0 else TaskStatus.FAILED + task.finished_at = time.time() + self._append_log(task, "info", f"login task finished ({task.success_count}/{len(task.account_ids)})") + self._current_task_id = None + self._append_log(task, "info", f"🏁 刷新任务完成 (成功: {task.success_count}, 失败: {task.fail_count}, 总计: {len(task.account_ids)})") + + def _refresh_one(self, account_id: str, task: LoginTask) -> dict: + """刷新单个账户""" + accounts = load_accounts_from_source() + account = next((acc for acc in accounts if acc.get("id") == account_id), None) + if not account: + return {"success": False, "email": account_id, "error": "账号不存在"} + + if account.get("disabled"): + return {"success": False, "email": account_id, "error": "账号已禁用"} + + # 获取邮件提供商 + mail_provider = (account.get("mail_provider") or "").lower() + if not mail_provider: + if account.get("mail_client_id") or account.get("mail_refresh_token"): + mail_provider = "microsoft" + else: + mail_provider = "duckmail" + + # 获取邮件配置 + mail_password = account.get("mail_password") or account.get("email_password") + mail_client_id = account.get("mail_client_id") + mail_refresh_token = account.get("mail_refresh_token") + mail_tenant = account.get("mail_tenant") or "consumers" + proxy_for_auth, _ = parse_proxy_setting(config.basic.proxy_for_auth) + + def log_cb(level, message): + self._append_log(task, level, f"[{account_id}] {message}") + + log_cb("info", f"📧 邮件提供商: {mail_provider}") + + # 创建邮件客户端 + if mail_provider == "microsoft": + if not mail_client_id or not mail_refresh_token: + return {"success": False, "email": account_id, "error": "Microsoft OAuth 配置缺失"} + mail_address = account.get("mail_address") or account_id + client = MicrosoftMailClient( + client_id=mail_client_id, + refresh_token=mail_refresh_token, + tenant=mail_tenant, + proxy=proxy_for_auth, + log_callback=log_cb, + ) + client.set_credentials(mail_address) + elif mail_provider in ("duckmail", "moemail", "freemail", "gptmail", "cfmail"): + if mail_provider not in ("freemail", "gptmail", "cfmail") and not mail_password: + error_message = "邮箱密码缺失" if mail_provider == "duckmail" else "mail password (email_id) missing" + return {"success": False, "email": account_id, "error": error_message} + if mail_provider == "freemail" and not account.get("mail_jwt_token") and not config.basic.freemail_jwt_token: + return {"success": False, "email": account_id, "error": "Freemail JWT Token 未配置"} + + # 创建邮件客户端,优先使用账户级别配置 + mail_address = account.get("mail_address") or account_id + + # 构建账户级别的配置参数 + account_config = {} + if account.get("mail_base_url"): + account_config["base_url"] = account["mail_base_url"] + if account.get("mail_api_key"): + account_config["api_key"] = account["mail_api_key"] + if account.get("mail_jwt_token"): + account_config["jwt_token"] = account["mail_jwt_token"] + if account.get("mail_verify_ssl") is not None: + account_config["verify_ssl"] = account["mail_verify_ssl"] + if account.get("mail_domain"): + account_config["domain"] = account["mail_domain"] + + # 创建客户端(工厂会优先使用传入的参数,其次使用全局配置) + client = create_temp_mail_client( + mail_provider, + log_cb=log_cb, + **account_config + ) + client.set_credentials(mail_address, mail_password) + if mail_provider == "moemail": + client.email_id = mail_password # 设置 email_id 用于获取邮件 + else: + return {"success": False, "email": account_id, "error": f"不支持的邮件提供商: {mail_provider}"} + + headless = config.basic.browser_headless + + log_cb("info", f"🌐 启动浏览器 (无头模式={headless})...") + + automation = GeminiAutomation( + user_agent=self.user_agent, + proxy=proxy_for_auth, + headless=headless, + log_callback=log_cb, + ) + # 允许外部取消时立刻关闭浏览器 + self._add_cancel_hook(task.id, lambda: getattr(automation, "stop", lambda: None)()) + try: + log_cb("info", "🔐 执行 Gemini 自动登录...") + result = automation.login_and_extract(account_id, client) + except Exception as exc: + log_cb("error", f"❌ 自动登录异常: {exc}") + return {"success": False, "email": account_id, "error": str(exc)} + if not result.get("success"): + error = result.get("error", "自动化流程失败") + log_cb("error", f"❌ 自动登录失败: {error}") + return {"success": False, "email": account_id, "error": error} + + log_cb("info", "✅ Gemini 登录成功,正在保存配置...") + + # 更新账户配置 + config_data = result["config"] + config_data["mail_provider"] = mail_provider + if mail_provider in ("freemail", "gptmail"): + config_data["mail_password"] = "" + elif mail_provider == "cfmail": + config_data["mail_password"] = mail_password # 保留 JWT token + else: + config_data["mail_password"] = mail_password + if mail_provider == "microsoft": + config_data["mail_address"] = account.get("mail_address") or account_id + config_data["mail_client_id"] = mail_client_id + config_data["mail_refresh_token"] = mail_refresh_token + config_data["mail_tenant"] = mail_tenant + config_data["disabled"] = account.get("disabled", False) + + for acc in accounts: + if acc.get("id") == account_id: + acc.update(config_data) + break + + self._apply_accounts_update(accounts) + + # 清除该账户的所有冷却状态(重新登录后恢复可用) + if account_id in self.multi_account_mgr.accounts: + account_mgr = self.multi_account_mgr.accounts[account_id] + account_mgr.quota_cooldowns.clear() # 清除配额冷却 + account_mgr.is_available = True # 恢复可用状态 + log_cb("info", "✅ 已清除账户冷却状态") + + log_cb("info", "✅ 配置已保存到数据库") + return {"success": True, "email": account_id, "config": config_data} + + + def _get_expiring_accounts(self) -> List[str]: + """获取即将过期的账户列表""" + accounts = load_accounts_from_source() + expiring = [] + beijing_tz = timezone(timedelta(hours=8)) + now = datetime.now(beijing_tz) + + for account in accounts: + account_id = account.get("id") + if not account_id: + continue + + if account.get("disabled"): + continue + mail_provider = (account.get("mail_provider") or "").lower() + if not mail_provider: + if account.get("mail_client_id") or account.get("mail_refresh_token"): + mail_provider = "microsoft" + else: + mail_provider = "duckmail" + + mail_password = account.get("mail_password") or account.get("email_password") + if mail_provider == "microsoft": + if not account.get("mail_client_id") or not account.get("mail_refresh_token"): + continue + elif mail_provider in ("duckmail", "moemail"): + if not mail_password: + continue + elif mail_provider == "freemail": + if not config.basic.freemail_jwt_token: + continue + elif mail_provider == "gptmail": + # GPTMail 不需要密码,允许直接刷新 + pass + elif mail_provider == "cfmail": + # cfmail 需要 JWT token(存在 mail_password 中)或全局配置 + if not mail_password and not config.basic.cfmail_api_key: + continue + else: + continue + expires_at = account.get("expires_at") + if not expires_at: + continue + + try: + expire_time = datetime.strptime(expires_at, "%Y-%m-%d %H:%M:%S") + expire_time = expire_time.replace(tzinfo=beijing_tz) + remaining = (expire_time - now).total_seconds() / 3600 + except Exception: + continue + + if remaining > config.basic.refresh_window_hours: + continue + + # 冷却检查(防重复层 1):跳过最近刚刷新过的账号 + cooldown_seconds = config.retry.refresh_cooldown_hours * 3600 + if account_id in self._refresh_timestamps: + elapsed = time.time() - self._refresh_timestamps[account_id] + if elapsed < cooldown_seconds: + logger.debug(f"[LOGIN] skip {account_id}: refreshed {elapsed/3600:.1f}h ago, cooldown {config.retry.refresh_cooldown_hours}h") + continue + + if True: # 通过所有检查 + expiring.append(account_id) + + return expiring + + async def check_and_refresh(self) -> Optional[LoginTask]: + if os.environ.get("ACCOUNTS_CONFIG"): + logger.info("[LOGIN] ACCOUNTS_CONFIG set, skipping refresh") + return None + expiring_accounts = self._get_expiring_accounts() + if not expiring_accounts: + logger.debug("[LOGIN] no accounts need refresh") + return None + + try: + return await self.start_login(expiring_accounts) + except Exception as exc: + logger.warning("[LOGIN] refresh enqueue failed: %s", exc) + return None + + @staticmethod + def _parse_cron(cron_str: str) -> dict: + """解析 cron 表达式。 + 支持两种格式: + - '08:00,20:00' → {'mode': 'daily', 'times': ['08:00', '20:00']} + - '*/120' → {'mode': 'interval', 'minutes': 120} + """ + cron_str = cron_str.strip() + if cron_str.startswith("*/"): + try: + minutes = int(cron_str[2:]) + return {"mode": "interval", "minutes": max(minutes, 5)} + except ValueError: + return {"mode": "interval", "minutes": 120} + else: + times = [t.strip() for t in cron_str.split(",") if t.strip()] + valid = [] + for t in times: + parts = t.split(":") + if len(parts) == 2: + try: + h, m = int(parts[0]), int(parts[1]) + if 0 <= h <= 23 and 0 <= m <= 59: + valid.append(f"{h:02d}:{m:02d}") + except ValueError: + pass + return {"mode": "daily", "times": valid or ["08:00", "20:00"]} + + async def _wait_for_next_trigger(self) -> None: + """等待下一个触发时间点。 + - interval 模式:等 N 分钟 + - daily 模式:等到下一个匹配的 HH:MM,每个时间点每天只触发一次 + """ + cron_str = config.retry.scheduled_refresh_cron + # 向后兼容:如果旧字段有值且新字段是默认值,转换为 interval 模式 + if (not cron_str or cron_str == "08:00,20:00") and config.retry.scheduled_refresh_interval_minutes > 0: + cron_str = f"*/{config.retry.scheduled_refresh_interval_minutes}" + + cron = self._parse_cron(cron_str) + + if cron["mode"] == "interval": + minutes = cron["minutes"] + logger.info(f"[LOGIN] 间隔模式:{minutes} 分钟后下一次检查") + await asyncio.sleep(minutes * 60) + return + + # daily 模式:每秒检查一次当前时间是否命中 + beijing_tz = timezone(timedelta(hours=8)) + while self._is_polling: + now = datetime.now(beijing_tz) + current_time = now.strftime("%H:%M") + today_str = now.strftime("%Y-%m-%d") + + # 新的一天,清空触发记录 + old_keys = [k for k in self._triggered_today if not k.startswith(today_str)] + for k in old_keys: + self._triggered_today.discard(k) + + for t in cron["times"]: + trigger_key = f"{today_str}_{t}" + if current_time == t and trigger_key not in self._triggered_today: + self._triggered_today.add(trigger_key) + logger.info(f"[LOGIN] 定时触发: {t}") + return + + await asyncio.sleep(30) # 每 30 秒检查一次 + + async def _wait_task_complete(self, task: LoginTask) -> None: + """等待任务完成(防重复层 3:串行等待)""" + while task.status in (TaskStatus.PENDING, TaskStatus.RUNNING): + await asyncio.sleep(5) + + async def start_polling(self) -> None: + if self._is_polling: + logger.warning("[LOGIN] polling already running") + return + + self._is_polling = True + logger.info("[LOGIN] 智能刷新调度器已启动") + try: + while self._is_polling: + # 检查是否启用 + if not config.retry.scheduled_refresh_enabled: + logger.debug("[LOGIN] scheduled refresh disabled") + await asyncio.sleep(CONFIG_CHECK_INTERVAL_SECONDS) + continue + + # 等待下一个触发时间点 + await self._wait_for_next_trigger() + if not self._is_polling: + break + + # 获取所有待刷新账号(已含冷却过滤) + expiring = self._get_expiring_accounts() + if not expiring: + logger.info("[LOGIN] 本轮无需刷新的账号") + continue + + batch_size = config.retry.refresh_batch_size + total_batches = (len(expiring) + batch_size - 1) // batch_size + logger.info(f"[LOGIN] 待刷新 {len(expiring)} 个账号,分 {total_batches} 批(每批 {batch_size} 个)") + + # 分批执行 + for i in range(0, len(expiring), batch_size): + if not self._is_polling: + break + + batch = expiring[i:i + batch_size] + batch_num = i // batch_size + 1 + logger.info(f"[LOGIN] 第 {batch_num}/{total_batches} 批: {batch}") + + try: + task = await self.start_login(batch) + # 等待这批完成(防重复层 3) + await self._wait_task_complete(task) + logger.info(f"[LOGIN] 第 {batch_num} 批完成 (成功: {task.success_count}, 失败: {task.fail_count})") + except Exception as exc: + logger.warning(f"[LOGIN] 第 {batch_num} 批异常: {exc}") + + # 批次间等待(最后一批不等) + remaining = expiring[i + batch_size:] + if remaining and self._is_polling: + interval = config.retry.refresh_batch_interval_minutes * 60 + logger.info(f"[LOGIN] 等待 {config.retry.refresh_batch_interval_minutes} 分钟后开始下一批...") + await asyncio.sleep(interval) + + logger.info("[LOGIN] 本轮刷新完成") + + except asyncio.CancelledError: + logger.info("[LOGIN] polling stopped") + except Exception as exc: + logger.error("[LOGIN] polling error: %s", exc) + finally: + self._is_polling = False + + def stop_polling(self) -> None: + self._is_polling = False + logger.info("[LOGIN] stopping polling") diff --git a/core/mail_providers/__init__.py b/core/mail_providers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..136d23de3dc81eb57959dc848b5db14e5ebdf274 --- /dev/null +++ b/core/mail_providers/__init__.py @@ -0,0 +1,3 @@ +from .factory import create_temp_mail_client + +__all__ = ["create_temp_mail_client"] diff --git a/core/mail_providers/factory.py b/core/mail_providers/factory.py new file mode 100644 index 0000000000000000000000000000000000000000..0949dc8bfcbd6c849b11657bf7ffa92b48895365 --- /dev/null +++ b/core/mail_providers/factory.py @@ -0,0 +1,94 @@ +from typing import Callable, Optional + +from core.config import config +from core.proxy_utils import extract_host, no_proxy_matches, parse_proxy_setting +from core.cfmail_client import CloudflareMailClient +from core.duckmail_client import DuckMailClient +from core.freemail_client import FreemailClient +from core.gptmail_client import GPTMailClient +from core.moemail_client import MoemailClient + + +def create_temp_mail_client( + provider: str, + *, + domain: Optional[str] = None, + proxy: Optional[str] = None, + log_cb: Optional[Callable[[str, str], None]] = None, + base_url: Optional[str] = None, + api_key: Optional[str] = None, + jwt_token: Optional[str] = None, + verify_ssl: Optional[bool] = None, +): + """ + 创建临时邮箱客户端 + + 参数优先级:传入参数 > 全局配置 + """ + provider = (provider or "duckmail").lower() + if proxy is None: + proxy_source = config.basic.proxy_for_auth if config.basic.mail_proxy_enabled else "" + else: + proxy_source = proxy + proxy, no_proxy = parse_proxy_setting(proxy_source) + + if provider == "moemail": + effective_base_url = base_url or config.basic.moemail_base_url + if no_proxy_matches(extract_host(effective_base_url), no_proxy): + proxy = "" + return MoemailClient( + base_url=effective_base_url, + proxy=proxy, + api_key=api_key or config.basic.moemail_api_key, + domain=domain or config.basic.moemail_domain, + log_callback=log_cb, + ) + + if provider == "freemail": + effective_base_url = base_url or config.basic.freemail_base_url + if no_proxy_matches(extract_host(effective_base_url), no_proxy): + proxy = "" + return FreemailClient( + base_url=effective_base_url, + jwt_token=jwt_token or config.basic.freemail_jwt_token, + proxy=proxy, + verify_ssl=verify_ssl if verify_ssl is not None else config.basic.freemail_verify_ssl, + log_callback=log_cb, + ) + + if provider == "gptmail": + effective_base_url = base_url or config.basic.gptmail_base_url + if no_proxy_matches(extract_host(effective_base_url), no_proxy): + proxy = "" + return GPTMailClient( + base_url=effective_base_url, + api_key=api_key or config.basic.gptmail_api_key, + proxy=proxy, + verify_ssl=verify_ssl if verify_ssl is not None else config.basic.gptmail_verify_ssl, + domain=domain or config.basic.gptmail_domain, + log_callback=log_cb, + ) + + if provider == "cfmail": + effective_base_url = base_url or config.basic.cfmail_base_url + if no_proxy_matches(extract_host(effective_base_url), no_proxy): + proxy = "" + return CloudflareMailClient( + base_url=effective_base_url, + proxy=proxy, + api_key=api_key or config.basic.cfmail_api_key, + domain=domain or config.basic.cfmail_domain, + verify_ssl=verify_ssl if verify_ssl is not None else config.basic.cfmail_verify_ssl, + log_callback=log_cb, + ) + + effective_base_url = base_url or config.basic.duckmail_base_url + if no_proxy_matches(extract_host(effective_base_url), no_proxy): + proxy = "" + return DuckMailClient( + base_url=effective_base_url, + proxy=proxy, + verify_ssl=verify_ssl if verify_ssl is not None else config.basic.duckmail_verify_ssl, + api_key=api_key or config.basic.duckmail_api_key, + log_callback=log_cb, + ) diff --git a/core/mail_utils.py b/core/mail_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..bf9f4a23ebca8cc3ec2237968621d97109b98bb7 --- /dev/null +++ b/core/mail_utils.py @@ -0,0 +1,29 @@ +import re +from typing import Optional + + +def extract_verification_code(text: str) -> Optional[str]: + """提取验证码""" + if not text: + return None + + # 策略1: 上下文关键词匹配(中英文冒号) + context_pattern = r"(?:验证码|code|verification|passcode|pin).*?[::]\s*([A-Za-z0-9]{4,8})\b" + match = re.search(context_pattern, text, re.IGNORECASE) + if match: + candidate = match.group(1) + # 排除 CSS 单位值 + if not re.match(r"^\d+(?:px|pt|em|rem|vh|vw|%)$", candidate, re.IGNORECASE): + return candidate + + # 策略2: 6位字母数字混合(与测试代码一致,优先级提高) + match = re.search(r"[A-Z0-9]{6}", text) + if match: + return match.group(0) + + # 策略3: 6位数字(降级为备选) + digits = re.findall(r"\b\d{6}\b", text) + if digits: + return digits[0] + + return None diff --git a/core/message.py b/core/message.py new file mode 100644 index 0000000000000000000000000000000000000000..64901590ecd8c879ebf27b9518eac6a0c5db877a --- /dev/null +++ b/core/message.py @@ -0,0 +1,154 @@ +"""消息处理模块 + +负责消息的解析、文本提取和会话指纹生成 +""" +import asyncio +import base64 +import hashlib +import logging +import re +from typing import List, TYPE_CHECKING + +import httpx + +if TYPE_CHECKING: + from main import Message + +logger = logging.getLogger(__name__) + + +def get_conversation_key(messages: List[dict], client_identifier: str = "") -> str: + """ + 生成对话指纹(使用前3条消息+客户端标识,确保唯一性) + + 策略: + 1. 使用前3条消息生成指纹(而非仅第1条) + 2. 加入客户端标识(IP或request_id)避免不同用户冲突 + 3. 保持Session复用能力(同一用户的后续消息仍能找到同一Session) + + Args: + messages: 消息列表 + client_identifier: 客户端标识(如IP地址或request_id),用于区分不同用户 + """ + if not messages: + return f"{client_identifier}:empty" if client_identifier else "empty" + + # 提取前3条消息的关键信息(角色+内容) + message_fingerprints = [] + for msg in messages[:3]: # 只取前3条 + role = msg.get("role", "") + content = msg.get("content", "") + + # 统一处理内容格式(字符串或数组) + if isinstance(content, list): + # 多模态消息:只提取文本部分 + text = extract_text_from_content(content) + else: + text = str(content) + + # 标准化:去除首尾空白,转小写 + text = text.strip().lower() + + # 组合角色和内容 + message_fingerprints.append(f"{role}:{text}") + + # 使用前3条消息+客户端标识生成指纹 + conversation_prefix = "|".join(message_fingerprints) + if client_identifier: + conversation_prefix = f"{client_identifier}|{conversation_prefix}" + + return hashlib.md5(conversation_prefix.encode()).hexdigest() + + +def extract_text_from_content(content) -> str: + """ + 从消息 content 中提取文本内容 + 统一处理字符串和多模态数组格式 + """ + if isinstance(content, str): + return content + elif isinstance(content, list): + # 多模态消息:只提取文本部分 + return "".join([x.get("text", "") for x in content if x.get("type") == "text"]) + else: + return str(content) + + +async def parse_last_message(messages: List['Message'], http_client: httpx.AsyncClient, request_id: str = ""): + """解析最后一条消息,分离文本和文件(支持图片、PDF、文档等,base64 和 URL)""" + if not messages: + return "", [] + + last_msg = messages[-1] + content = last_msg.content + + text_content = "" + images = [] # List of {"mime": str, "data": str_base64} - 兼容变量名,实际支持所有文件 + image_urls = [] # 需要下载的 URL - 兼容变量名,实际支持所有文件 + + if isinstance(content, str): + text_content = content + elif isinstance(content, list): + for part in content: + if part.get("type") == "text": + text_content += part.get("text", "") + elif part.get("type") == "image_url": + url = part.get("image_url", {}).get("url", "") + # 解析 Data URI: data:mime/type;base64,xxxxxx (支持所有 MIME 类型) + match = re.match(r"data:([^;]+);base64,(.+)", url) + if match: + images.append({"mime": match.group(1), "data": match.group(2)}) + elif url.startswith(("http://", "https://")): + image_urls.append(url) + else: + logger.warning(f"[FILE] [req_{request_id}] 不支持的文件格式: {url[:30]}...") + + # 并行下载所有 URL 文件(支持图片、PDF、文档等) + if image_urls: + async def download_url(url: str): + try: + resp = await http_client.get(url, timeout=30, follow_redirects=True) + if resp.status_code == 404: + logger.warning(f"[FILE] [req_{request_id}] URL文件已失效(404),已跳过: {url[:50]}...") + return None + resp.raise_for_status() + content_type = resp.headers.get("content-type", "application/octet-stream").split(";")[0] + # 移除图片类型限制,支持所有文件类型 + b64 = base64.b64encode(resp.content).decode() + logger.info(f"[FILE] [req_{request_id}] URL文件下载成功: {url[:50]}... ({len(resp.content)} bytes, {content_type})") + return {"mime": content_type, "data": b64} + except httpx.HTTPStatusError as e: + status_code = e.response.status_code if e.response else "unknown" + logger.warning(f"[FILE] [req_{request_id}] URL文件下载失败({status_code}): {url[:50]}... - {e}") + return None + except Exception as e: + logger.warning(f"[FILE] [req_{request_id}] URL文件下载失败: {url[:50]}... - {e}") + return None + + results = await asyncio.gather(*[download_url(u) for u in image_urls], return_exceptions=True) + safe_results = [] + for result in results: + if isinstance(result, Exception): + logger.warning(f"[FILE] [req_{request_id}] URL文件下载异常: {type(result).__name__}: {str(result)[:120]}") + continue + safe_results.append(result) + images.extend([r for r in safe_results if r]) + + return text_content, images + + +def build_full_context_text(messages: List['Message']) -> str: + """仅拼接历史文本,图片只处理当次请求的""" + prompt = "" + for msg in messages: + role = "User" if msg.role in ["user", "system"] else "Assistant" + content_str = extract_text_from_content(msg.content) + + # 为多模态消息添加图片标记 + if isinstance(msg.content, list): + image_count = sum(1 for part in msg.content if part.get("type") == "image_url") + if image_count > 0: + content_str += "[图片]" * image_count + + prompt += f"{role}: {content_str}\n\n" + return prompt diff --git a/core/microsoft_mail_client.py b/core/microsoft_mail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..d7cb86da63f22a517dcb8da012b8fb2990cc1caa --- /dev/null +++ b/core/microsoft_mail_client.py @@ -0,0 +1,221 @@ +import imaplib +import time +from datetime import datetime, timedelta +from email import message_from_bytes +from email.utils import parsedate_to_datetime +from typing import Optional + +import requests + +from core.mail_utils import extract_verification_code + +# 常量定义 +CANCELLATION_CHECK_INTERVAL_SECONDS = 5 # 取消检查间隔(秒) + + +class MicrosoftMailClient: + def __init__( + self, + client_id: str, + refresh_token: str, + tenant: str = "consumers", + proxy: str = "", + log_callback=None, + ) -> None: + self.client_id = client_id + self.refresh_token = refresh_token + self.tenant = tenant or "consumers" + self.proxies = {"http": proxy, "https": proxy} if proxy else None + self.log_callback = log_callback + self.email: Optional[str] = None + + def set_credentials(self, email: str, password: Optional[str] = None) -> None: + self.email = email + + def _get_access_token(self) -> Optional[str]: + url = f"https://login.microsoftonline.com/{self.tenant}/oauth2/v2.0/token" + data = { + "client_id": self.client_id, + "grant_type": "refresh_token", + "refresh_token": self.refresh_token, + } + try: + self._log("info", f"🔑 正在获取 Microsoft OAuth 令牌...") + res = requests.post(url, data=data, proxies=self.proxies, timeout=15) + if res.status_code != 200: + self._log("error", f"❌ Microsoft 令牌获取失败: HTTP {res.status_code}") + return None + payload = res.json() if res.content else {} + token = payload.get("access_token") + if not token: + self._log("error", "❌ Microsoft 令牌响应中缺少 access_token") + return None + self._log("info", "✅ Microsoft OAuth 令牌获取成功") + return token + except Exception as exc: + self._log("error", f"❌ Microsoft 令牌获取异常: {exc}") + return None + + def fetch_verification_code(self, since_time: Optional[datetime] = None) -> Optional[str]: + if not self.email: + return None + + self._log("info", "📬 正在获取验证码...") + token = self._get_access_token() + if not token: + self._log("error", "❌ 无法获取访问令牌,跳过邮箱检查") + return None + + auth_string = f"user={self.email}\x01auth=Bearer {token}\x01\x01".encode() + client = imaplib.IMAP4_SSL("outlook.office365.com", 993) + try: + self._log("info", f"🔐 正在使用 IMAP XOAUTH2 认证: {self.email}") + client.authenticate("XOAUTH2", lambda _: auth_string) + self._log("info", "✅ IMAP 认证成功,已连接到邮箱") + except Exception as exc: + self._log("error", f"❌ IMAP 认证失败: {exc}") + try: + client.logout() + except Exception: + pass + return None + + search_since = since_time or (datetime.now() - timedelta(minutes=5)) + self._log("info", f"🔍 搜索 {search_since.strftime('%Y-%m-%d %H:%M:%S')} 之后的邮件") + + try: + for mailbox in ("INBOX", "Junk"): + try: + status, _ = client.select(mailbox, readonly=True) + if status != "OK": + self._log("warning", f"⚠️ 无法选择邮箱: {mailbox}") + continue + self._log("info", f"📂 正在检查邮箱: {mailbox}") + except Exception as e: + self._log("warning", f"⚠️ 选择邮箱 {mailbox} 时出错: {e}") + continue + + # 搜索所有邮件 + status, data = client.search(None, "ALL") + if status != "OK" or not data or not data[0]: + self._log("info", f"📭 邮箱 {mailbox} 中没有邮件") + continue + + ids = data[0].split()[-5:] # 只检查最近 5 封 + self._log("info", f"📨 在 {mailbox} 中发现 {len(ids)} 封邮件") + + checked_count = 0 + for msg_id in reversed(ids): + status, msg_data = client.fetch(msg_id, "(RFC822)") + if status != "OK" or not msg_data: + continue + raw_bytes = None + for item in msg_data: + if isinstance(item, tuple) and len(item) > 1: + raw_bytes = item[1] + break + if not raw_bytes: + continue + + msg = message_from_bytes(raw_bytes) + msg_date = self._parse_message_date(msg.get("Date")) + + # 按时间过滤(静默跳过旧邮件) + if msg_date and msg_date < search_since: + continue + + checked_count += 1 + content = self._message_to_text(msg) + import re + match = re.search(r'[A-Z0-9]{6}', content) + if match: + code = match.group(0) + self._log("info", f"🎉 在 {mailbox} 中找到验证码: {code}") + return code + + if checked_count > 0: + self._log("info", f"🔍 已检查 {mailbox} 中 {checked_count} 封近期邮件,未找到验证码") + + self._log("warning", "⚠️ 所有邮箱中均未找到验证码") + finally: + try: + client.logout() + except Exception: + pass + + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time: Optional[datetime] = None, + ) -> Optional[str]: + if not self.email: + return None + + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + # 检查任务是否被取消(通过 log 触发 TaskCancelledError) + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + if i < max_retries: + # 分段 sleep,每5秒检查一次取消状态 + for _ in range(interval // CANCELLATION_CHECK_INTERVAL_SECONDS): + time.sleep(CANCELLATION_CHECK_INTERVAL_SECONDS) + # 通过 log 检查取消状态(使用有意义的日志) + self._log("debug", f"等待验证码中... ({(_ + 1) * CANCELLATION_CHECK_INTERVAL_SECONDS}/{interval}秒)") + # 处理剩余的秒数 + remaining = interval % CANCELLATION_CHECK_INTERVAL_SECONDS + if remaining > 0: + time.sleep(remaining) + + self._log("error", "❌ 验证码获取超时") + return None + + @staticmethod + def _message_to_text(msg) -> str: + if msg.is_multipart(): + parts = [] + for part in msg.walk(): + content_type = part.get_content_type() + if content_type not in ("text/plain", "text/html"): + continue + payload = part.get_payload(decode=True) + if not payload: + continue + charset = part.get_content_charset() or "utf-8" + parts.append(payload.decode(charset, errors="ignore")) + return "".join(parts) + payload = msg.get_payload(decode=True) + if isinstance(payload, bytes): + return payload.decode(msg.get_content_charset() or "utf-8", errors="ignore") + return str(payload) if payload else "" + + @staticmethod + def _parse_message_date(value: Optional[str]) -> Optional[datetime]: + if not value: + return None + try: + parsed = parsedate_to_datetime(value) + if parsed is None: + return None + if parsed.tzinfo: + return parsed.astimezone(tz=None).replace(tzinfo=None) + return parsed + except Exception: + return None + + def _log(self, level: str, message: str) -> None: + if self.log_callback: + try: + self.log_callback(level, message) + except TaskCancelledError: + raise + except Exception: + pass diff --git a/core/moemail_client.py b/core/moemail_client.py new file mode 100644 index 0000000000000000000000000000000000000000..28fb8aa4f2dceef9cca9c6bef2bea4fc8fb44f30 --- /dev/null +++ b/core/moemail_client.py @@ -0,0 +1,355 @@ +""" +Moemail临时邮箱客户端 + +API文档参考: +- 获取系统配置: GET /api/config +- 生成临时邮箱: POST /api/emails/generate +- 获取邮件列表: GET /api/emails/{emailId} +- 获取单封邮件: GET /api/emails/{emailId}/{messageId} +""" + +import random +import string +import time +from typing import Optional + +import requests + +from core.mail_utils import extract_verification_code +from core.proxy_utils import request_with_proxy_fallback + + +class MoemailClient: + """Moemail临时邮箱客户端""" + + def __init__( + self, + base_url: str = "https://moemail.nanohajimi.mom", + proxy: str = "", + api_key: str = "", + domain: str = "", + log_callback=None, + ) -> None: + self.base_url = base_url.rstrip("/") + self.proxies = {"http": proxy, "https": proxy} if proxy else None + self.api_key = api_key.strip() + self.domain = domain.strip() if domain else "" + self.log_callback = log_callback + + self.email: Optional[str] = None + self.email_id: Optional[str] = None + self.password: Optional[str] = None # 兼容 DuckMailClient 接口 + + # 缓存可用域名列表 + self._available_domains: list = [] + + def set_credentials(self, email: str, password: str = "") -> None: + """设置凭据(兼容 DuckMailClient 接口)""" + self.email = email + self.password = password + + def _request(self, method: str, url: str, **kwargs) -> requests.Response: + """发送请求并打印详细日志""" + headers = kwargs.pop("headers", None) or {} + if self.api_key and "X-API-Key" not in headers: + headers["X-API-Key"] = self.api_key + headers.setdefault("Content-Type", "application/json") + kwargs["headers"] = headers + + self._log("info", f"📤 发送 {method} 请求: {url}") + if "json" in kwargs: + self._log("info", f"📦 请求体: {kwargs['json']}") + + try: + res = request_with_proxy_fallback( + requests.request, + method, + url, + proxies=self.proxies, + timeout=kwargs.pop("timeout", 30), + **kwargs, + ) + self._log("info", f"📥 收到响应: HTTP {res.status_code}") + if res.content and res.status_code >= 400: + try: + self._log("error", f"📄 响应内容: {res.text[:500]}") + except Exception: + pass + return res + except Exception as e: + self._log("error", f"❌ 网络请求失败: {e}") + raise + + def _get_available_domains(self) -> list: + """获取可用的邮箱域名列表""" + if self._available_domains: + return self._available_domains + + try: + res = self._request("GET", f"{self.base_url}/api/config") + if res.status_code == 200: + data = res.json() + email_domains_str = data.get("emailDomains", "") + if email_domains_str: + self._available_domains = [d.strip() for d in email_domains_str.split(",") if d.strip()] + self._log("info", f"🌐 Moemail 可用域名: {self._available_domains}") + return self._available_domains + except Exception as e: + self._log("error", f"❌ 获取可用域名失败: {e}") + + # 默认域名 + self._available_domains = ["moemail.app"] + return self._available_domains + + def register_account(self, domain: Optional[str] = None) -> bool: + """注册新邮箱账号 + + API: POST /api/emails/generate + """ + # 确定使用的域名 + selected_domain = domain + if not selected_domain: + selected_domain = self.domain + + if not selected_domain: + # 从可用域名中随机选择 + available = self._get_available_domains() + if available: + selected_domain = random.choice(available) + else: + selected_domain = "moemail.app" + + self._log("info", f"📧 使用域名: {selected_domain}") + + # 生成随机邮箱名称 + rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=10)) + timestamp = str(int(time.time()))[-4:] + name = f"t{timestamp}{rand}" + + self._log("info", f"🎲 生成邮箱: {name}@{selected_domain}") + + try: + # 设置为 0 表示永久有效 + self._log("info", f"⏰ 设置过期时间: 永久有效") + + res = self._request( + "POST", + f"{self.base_url}/api/emails/generate", + json={ + "name": name, + "expiryTime": 0, + "domain": selected_domain, + }, + ) + + if res.status_code in (200, 201): + data = res.json() if res.content else {} + self.email = data.get("email", "") + self.email_id = data.get("id", "") + self.password = self.email_id # 用 email_id 作为 password 存储 + + if self.email and self.email_id: + self._log("info", f"✅ Moemail 注册成功: {self.email}") + self._log("info", f"🔑 Email ID: {self.email_id}") + return True + + self._log("error", f"❌ Moemail 注册失败: HTTP {res.status_code}") + if res.content: + self._log("error", f"📄 响应内容: {res.text[:500]}") + return False + + except Exception as e: + self._log("error", f"❌ Moemail 注册异常: {e}") + return False + + def login(self) -> bool: + """登录(Moemail 无需登录,返回 True)""" + # Moemail 使用 API Key 认证,无需单独登录 + return True + + def fetch_verification_code(self, since_time=None) -> Optional[str]: + """获取验证码 + + API: GET /api/emails/{emailId} + API: GET /api/emails/{emailId}/{messageId} + """ + if not self.email_id: + self._log("error", "❌ 缺少 email_id,无法获取邮件") + return None + + try: + self._log("info", "📬 正在拉取 Moemail 邮件列表...") + + # 获取邮件列表 + res = self._request( + "GET", + f"{self.base_url}/api/emails/{self.email_id}", + ) + + if res.status_code != 200: + self._log("error", f"❌ 获取邮件列表失败: HTTP {res.status_code}") + return None + + data = res.json() if res.content else {} + messages = data.get("messages", []) + + if not messages: + self._log("info", "📭 邮箱为空,暂无邮件") + return None + + self._log("info", f"📨 收到 {len(messages)} 封邮件,开始检查验证码...") + + from datetime import datetime + + def _parse_message_time(msg_obj) -> Optional[datetime]: + import re + + time_keys = [ + "createdAt", + "receivedAt", + "sentAt", + "created_at", + "received_at", + "sent_at", + ] + raw_time = None + for key in time_keys: + if msg_obj.get(key) is not None: + raw_time = msg_obj.get(key) + break + + if raw_time is None: + return None + + if isinstance(raw_time, (int, float)): + timestamp = float(raw_time) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp) + + if isinstance(raw_time, str): + raw_time = raw_time.strip() + if raw_time.isdigit(): + timestamp = float(raw_time) + if timestamp > 1e12: + timestamp = timestamp / 1000.0 + return datetime.fromtimestamp(timestamp) + + # 处理 ISO 时间字符串 + try: + # 截断纳秒到微秒 + raw_time = re.sub(r"(\.\d{6})\d+", r"\1", raw_time) + return datetime.fromisoformat(raw_time.replace("Z", "+00:00")).astimezone().replace(tzinfo=None) + except Exception: + return None + + return None + + def _looks_like_verification(msg_obj) -> bool: + subject = (msg_obj.get("subject") or "").strip() + if not subject: + return False + import re + return re.search(r"(验证码|验证|verification|verify|passcode|security\s*code|one[-\s]?time|otp)", subject, re.IGNORECASE) is not None + + messages_with_time = [(msg, _parse_message_time(msg)) for msg in messages] + if any(item[1] for item in messages_with_time): + messages_with_time.sort(key=lambda item: item[1] or datetime.min, reverse=True) + messages = [item[0] for item in messages_with_time] + + # 遍历邮件 + for idx, msg in enumerate(messages, 1): + msg_id = msg.get("id") + if not msg_id: + continue + + # 时间过滤 + if since_time: + msg_time = _parse_message_time(msg) + if msg_time: + if msg_time < since_time: + continue + + if not _looks_like_verification(msg): + continue + + # 优先从邮件列表的 content 字段提取验证码(更高效) + list_content = msg.get("content") or "" + if list_content: + code = extract_verification_code(list_content) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + + # 如果列表没有 content,则获取邮件详情 + self._log("info", f"🔍 正在读取邮件 {idx}/{len(messages)} 详情...") + detail_res = self._request( + "GET", + f"{self.base_url}/api/emails/{self.email_id}/{msg_id}", + ) + + if detail_res.status_code != 200: + self._log("warning", f"⚠️ 读取邮件详情失败: HTTP {detail_res.status_code}") + continue + + detail = detail_res.json() if detail_res.content else {} + + # 处理 {'message': {...}} 格式 + if "message" in detail and isinstance(detail["message"], dict): + detail = detail["message"] + + # 获取邮件内容 + text_content = detail.get("text") or detail.get("textContent") or detail.get("content") or "" + html_content = detail.get("html") or detail.get("htmlContent") or "" + + if isinstance(html_content, list): + html_content = "".join(str(item) for item in html_content) + if isinstance(text_content, list): + text_content = "".join(str(item) for item in text_content) + + content = text_content + html_content + if content: + code = extract_verification_code(content) + if code: + self._log("info", f"✅ 找到验证码: {code}") + return code + else: + self._log("info", f"❌ 邮件 {idx} 中未找到验证码") + + self._log("warning", "⚠️ 所有邮件中均未找到验证码") + return None + + except Exception as e: + self._log("error", f"❌ 获取验证码异常: {e}") + return None + + def poll_for_code( + self, + timeout: int = 120, + interval: int = 4, + since_time=None, + ) -> Optional[str]: + """轮询获取验证码""" + max_retries = max(1, timeout // interval) + self._log("info", f"⏱️ 开始轮询验证码 (超时 {timeout}秒, 间隔 {interval}秒, 最多 {max_retries} 次)") + + for i in range(1, max_retries + 1): + self._log("info", f"🔄 第 {i}/{max_retries} 次轮询...") + code = self.fetch_verification_code(since_time=since_time) + if code: + self._log("info", f"🎉 验证码获取成功: {code}") + return code + + if i < max_retries: + self._log("info", f"⏳ 等待 {interval} 秒后重试...") + time.sleep(interval) + + self._log("error", f"⏰ 验证码获取超时 ({timeout}秒)") + return None + + def _log(self, level: str, message: str) -> None: + if self.log_callback: + try: + self.log_callback(level, message) + except Exception: + pass diff --git a/core/proxy_utils.py b/core/proxy_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..d04cb9fc3ea34fc600ca2f905847d9a0de29dda8 --- /dev/null +++ b/core/proxy_utils.py @@ -0,0 +1,222 @@ +""" +代理设置工具函数 + +支持格式: +- http://127.0.0.1:7890 +- http://user:pass@127.0.0.1:7890 +- socks5h://127.0.0.1:7890 +- socks5h://user:pass@127.0.0.1:7890 | no_proxy=localhost,127.0.0.1,.local + +NO_PROXY 格式: +- 逗号分隔的主机名或域名后缀 +- 支持通配符前缀,如 .local 匹配 *.local +""" + +import re +from typing import Tuple, Callable, Any, Optional +from urllib.parse import urlparse +import functools + + +def parse_proxy_setting(proxy_str: str) -> Tuple[str, str]: + """ + 解析代理设置字符串,提取代理 URL 和 NO_PROXY 列表 + + Args: + proxy_str: 代理设置字符串,格式如 "http://127.0.0.1:7890 | no_proxy=localhost,127.0.0.1" + + Returns: + Tuple[str, str]: (proxy_url, no_proxy_list) + - proxy_url: 代理地址,如 "http://127.0.0.1:7890" + - no_proxy_list: NO_PROXY 列表字符串,如 "localhost,127.0.0.1" + """ + if not proxy_str: + return "", "" + + proxy_str = proxy_str.strip() + if not proxy_str: + return "", "" + + # 检查是否包含 no_proxy 设置 + # 支持格式: proxy_url | no_proxy=host1,host2 + no_proxy = "" + proxy_url = proxy_str + + # 使用 | 分隔代理和 no_proxy + if "|" in proxy_str: + parts = proxy_str.split("|", 1) + proxy_url = parts[0].strip() + no_proxy_part = parts[1].strip() + + # 解析 no_proxy=xxx 格式 + no_proxy_match = re.match(r"no_proxy\s*=\s*(.+)", no_proxy_part, re.IGNORECASE) + if no_proxy_match: + no_proxy = no_proxy_match.group(1).strip() + + return normalize_proxy_url(proxy_url), no_proxy + + +def extract_host(url: str) -> str: + """ + 从 URL 中提取主机名 + + Args: + url: 完整 URL,如 "https://mail.chatgpt.org.uk/api/emails" + + Returns: + str: 主机名,如 "mail.chatgpt.org.uk" + """ + if not url: + return "" + + url = url.strip() + if not url: + return "" + + # 如果没有协议前缀,添加一个以便解析 + if not url.startswith(("http://", "https://", "socks5://", "socks5h://")): + url = "http://" + url + + try: + parsed = urlparse(url) + return parsed.hostname or "" + except Exception: + return "" + + +def no_proxy_matches(host: str, no_proxy: str) -> bool: + """ + 检查主机是否在 NO_PROXY 豁免列表中 + + Args: + host: 要检查的主机名,如 "mail.chatgpt.org.uk" + no_proxy: NO_PROXY 列表字符串,如 "localhost,127.0.0.1,.local" + + Returns: + bool: 如果主机在豁免列表中返回 True,否则返回 False + + 匹配规则: + - 精确匹配: "localhost" 匹配 "localhost" + - 域名后缀匹配: ".local" 匹配 "foo.local", "bar.foo.local" + - IP 地址匹配: "127.0.0.1" 精确匹配 + """ + if not host or not no_proxy: + return False + + host = host.lower().strip() + if not host: + return False + + # 解析 no_proxy 列表 + no_proxy_list = [item.strip().lower() for item in no_proxy.split(",") if item.strip()] + + for pattern in no_proxy_list: + if not pattern: + continue + + # 精确匹配 + if host == pattern: + return True + + # 域名后缀匹配 (如 .local 匹配 foo.local) + if pattern.startswith("."): + if host.endswith(pattern) or host == pattern[1:]: + return True + else: + # 也支持不带点的后缀匹配 (如 local 匹配 foo.local) + if host.endswith("." + pattern): + return True + + return False + + +def normalize_proxy_url(proxy_str: str) -> str: + """ + 标准化代理 URL 格式 + + 支持的输入格式: + - http://127.0.0.1:7890 + - http://user:pass@127.0.0.1:7890 + - socks5://127.0.0.1:7890 + - socks5h://127.0.0.1:7890 + - 127.0.0.1:7890 (自动添加 http://) + - host:port:user:pass (旧格式,自动转换) + + Returns: + str: 标准化的代理 URL + """ + if not proxy_str: + return "" + + proxy_str = proxy_str.strip() + if not proxy_str: + return "" + + # 如果已经是标准 URL 格式,直接返回 + if proxy_str.startswith(("http://", "https://", "socks5://", "socks5h://")): + return proxy_str + + # 尝试解析旧格式 host:port:user:pass + parts = proxy_str.split(":") + if len(parts) == 4: + host, port, user, password = parts + return f"http://{user}:{password}@{host}:{port}" + elif len(parts) == 2: + # host:port 格式 + return f"http://{proxy_str}" + + # 无法识别的格式,尝试添加 http:// 前缀 + return f"http://{proxy_str}" + + +def request_with_proxy_fallback(request_func: Callable, *args, **kwargs) -> Any: + """ + 带代理失败回退的请求包装器 + + 如果代理连接失败,自动禁用代理重试一次 + + Args: + request_func: 原始请求函数 + *args, **kwargs: 传递给请求函数的参数 + + Returns: + 请求响应对象 + + Raises: + 原始异常(如果直连也失败) + """ + # 代理相关的错误类型 + PROXY_ERRORS = ( + "ProxyError", + "ConnectTimeout", + "ConnectionError", + "407", # Proxy Authentication Required + "502", # Bad Gateway (代理问题) + "503", # Service Unavailable (代理问题) + ) + + try: + # 首次尝试(使用代理) + return request_func(*args, **kwargs) + except Exception as e: + error_str = str(e) + error_type = type(e).__name__ + + # 检查是否是代理相关错误 + is_proxy_error = any(err in error_str or err in error_type for err in PROXY_ERRORS) + + if is_proxy_error and "proxies" in kwargs: + # 禁用代理重试 + original_proxies = kwargs.get("proxies") + kwargs["proxies"] = None + + try: + # 直连重试 + return request_func(*args, **kwargs) + except Exception: + # 直连也失败,恢复原始代理设置并抛出原始异常 + kwargs["proxies"] = original_proxies + raise e + else: + # 不是代理错误,直接抛出 + raise diff --git a/core/register_service.py b/core/register_service.py new file mode 100644 index 0000000000000000000000000000000000000000..0d5a4d5084d744ac172b2779aa83edc368248e4f --- /dev/null +++ b/core/register_service.py @@ -0,0 +1,289 @@ +import asyncio +import logging +import os +import time +import uuid +from dataclasses import dataclass, field +from typing import Any, Callable, Dict, List, Optional + +from core.account import load_accounts_from_source +from core.base_task_service import BaseTask, BaseTaskService, TaskCancelledError, TaskStatus +from core.config import config +from core.mail_providers import create_temp_mail_client +from core.gemini_automation import GeminiAutomation +from core.proxy_utils import parse_proxy_setting + +logger = logging.getLogger("gemini.register") + + +@dataclass +class RegisterTask(BaseTask): + """注册任务数据类""" + count: int = 0 + domain: Optional[str] = None + mail_provider: Optional[str] = None + + def to_dict(self) -> dict: + """转换为字典""" + base_dict = super().to_dict() + base_dict["count"] = self.count + base_dict["domain"] = self.domain + base_dict["mail_provider"] = self.mail_provider + return base_dict + + +class RegisterService(BaseTaskService[RegisterTask]): + """注册服务类""" + + def __init__( + self, + multi_account_mgr, + http_client, + user_agent: str, + retry_policy, + session_cache_ttl_seconds: int, + global_stats_provider: Callable[[], dict], + set_multi_account_mgr: Optional[Callable[[Any], None]] = None, + ) -> None: + super().__init__( + multi_account_mgr, + http_client, + user_agent, + retry_policy, + session_cache_ttl_seconds, + global_stats_provider, + set_multi_account_mgr, + log_prefix="REGISTER", + ) + + def _get_running_task(self) -> Optional[RegisterTask]: + """获取正在运行或等待中的任务""" + for task in self._tasks.values(): + if isinstance(task, RegisterTask) and task.status in (TaskStatus.PENDING, TaskStatus.RUNNING): + return task + return None + + async def start_register(self, count: Optional[int] = None, domain: Optional[str] = None, mail_provider: Optional[str] = None) -> RegisterTask: + """ + 启动注册任务 - 统一任务管理 + - 如果有正在运行的任务,将新数量添加到该任务 + - 如果没有正在运行的任务,创建新任务 + """ + async with self._lock: + if os.environ.get("ACCOUNTS_CONFIG"): + raise ValueError("已设置 ACCOUNTS_CONFIG 环境变量,注册功能已禁用") + + # 先确定使用哪个邮箱服务提供商 + mail_provider_value = (mail_provider or "").strip().lower() + if not mail_provider_value: + mail_provider_value = (config.basic.temp_mail_provider or "duckmail").lower() + + # 再确定使用哪个域名(只有 DuckMail 使用 register_domain 配置) + domain_value = (domain or "").strip() + if not domain_value: + if mail_provider_value == "duckmail": + domain_value = (config.basic.register_domain or "").strip() or None + else: + domain_value = None + + register_count = count or config.basic.register_default_count + register_count = max(1, int(register_count)) + + # 检查是否有正在运行的任务 + running_task = self._get_running_task() + + if running_task: + # 将新数量添加到现有任务 + running_task.count += register_count + self._append_log( + running_task, + "info", + f"📝 添加 {register_count} 个账户到现有任务 (总计: {running_task.count})" + ) + return running_task + + # 创建新任务 + task = RegisterTask(id=str(uuid.uuid4()), count=register_count, domain=domain_value, mail_provider=mail_provider_value) + self._tasks[task.id] = task + self._append_log(task, "info", f"📝 创建注册任务 (数量: {register_count}, 域名: {domain_value or 'default'}, 提供商: {mail_provider_value})") + + # 直接启动任务 + self._current_task_id = task.id + asyncio.create_task(self._run_task_directly(task)) + return task + + async def _run_task_directly(self, task: RegisterTask) -> None: + """直接执行任务""" + try: + await self._run_one_task(task) + finally: + # 任务完成后清理 + async with self._lock: + if self._current_task_id == task.id: + self._current_task_id = None + + def _execute_task(self, task: RegisterTask): + return self._run_register_async(task, task.domain, task.mail_provider) + + async def _run_register_async(self, task: RegisterTask, domain: Optional[str], mail_provider: Optional[str]) -> None: + """异步执行注册任务(支持取消)。""" + loop = asyncio.get_running_loop() + self._append_log(task, "info", f"🚀 注册任务已启动 (共 {task.count} 个账号)") + + for idx in range(task.count): + if task.cancel_requested: + self._append_log(task, "warning", f"register task cancelled: {task.cancel_reason or 'cancelled'}") + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + return + + try: + self._append_log(task, "info", f"📊 进度: {idx + 1}/{task.count}") + result = await loop.run_in_executor(self._executor, self._register_one, domain, mail_provider, task) + except TaskCancelledError: + task.status = TaskStatus.CANCELLED + task.finished_at = time.time() + return + except Exception as exc: + result = {"success": False, "error": str(exc)} + task.progress += 1 + task.results.append(result) + + if result.get("success"): + task.success_count += 1 + email = result.get('email', '未知') + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + self._append_log(task, "info", f"✅ 注册成功: {email}") + self._append_log(task, "info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + else: + task.fail_count += 1 + error = result.get('error', '未知错误') + self._append_log(task, "error", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + self._append_log(task, "error", f"❌ 注册失败: {error}") + self._append_log(task, "error", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + # 账号之间等待 10 秒,避免资源争抢和风控 + if idx < task.count - 1 and not task.cancel_requested: + self._append_log(task, "info", "⏳ 等待 10 秒后处理下一个账号...") + await asyncio.sleep(10) + + if task.cancel_requested: + task.status = TaskStatus.CANCELLED + else: + task.status = TaskStatus.SUCCESS if task.fail_count == 0 else TaskStatus.FAILED + task.finished_at = time.time() + self._current_task_id = None + self._append_log(task, "info", f"🏁 注册任务完成 (成功: {task.success_count}, 失败: {task.fail_count}, 总计: {task.count})") + + def _register_one(self, domain: Optional[str], mail_provider: Optional[str], task: RegisterTask) -> dict: + """注册单个账户""" + log_cb = lambda level, message: self._append_log(task, level, message) + + log_cb("info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + log_cb("info", "🆕 开始注册新账户") + log_cb("info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + # 使用传递的邮件提供商参数,如果未提供则从配置读取 + temp_mail_provider = (mail_provider or "").strip().lower() + if not temp_mail_provider: + temp_mail_provider = (config.basic.temp_mail_provider or "duckmail").lower() + + log_cb("info", f"📧 步骤 1/3: 注册临时邮箱 (提供商={temp_mail_provider})...") + + if temp_mail_provider == "freemail" and not config.basic.freemail_jwt_token: + log_cb("error", "❌ Freemail JWT Token 未配置") + return {"success": False, "error": "Freemail JWT Token 未配置"} + + client = create_temp_mail_client( + temp_mail_provider, + domain=domain, + log_cb=log_cb, + ) + + if not client.register_account(domain=domain): + log_cb("error", f"❌ {temp_mail_provider} 邮箱注册失败") + return {"success": False, "error": f"{temp_mail_provider} 注册失败"} + + log_cb("info", f"✅ 邮箱注册成功: {client.email}") + + headless = config.basic.browser_headless + proxy_for_auth, _ = parse_proxy_setting(config.basic.proxy_for_auth) + + log_cb("info", f"🌐 步骤 2/3: 启动浏览器 (无头模式={headless})...") + + automation = GeminiAutomation( + user_agent=self.user_agent, + proxy=proxy_for_auth, + headless=headless, + log_callback=log_cb, + ) + # 允许外部取消时立刻关闭浏览器 + self._add_cancel_hook(task.id, lambda: getattr(automation, "stop", lambda: None)()) + + try: + log_cb("info", "🔐 步骤 3/3: 执行 Gemini 自动登录...") + result = automation.login_and_extract(client.email, client, is_new_account=True) + except Exception as exc: + log_cb("error", f"❌ 自动登录异常: {exc}") + return {"success": False, "error": str(exc)} + + if not result.get("success"): + error = result.get("error", "自动化流程失败") + log_cb("error", f"❌ 自动登录失败: {error}") + return {"success": False, "error": error} + + log_cb("info", "✅ Gemini 登录成功,正在保存配置...") + + config_data = result["config"] + config_data["mail_provider"] = temp_mail_provider + config_data["mail_address"] = client.email + + # 保存邮箱自定义配置 + if temp_mail_provider == "freemail": + config_data["mail_password"] = "" + config_data["mail_base_url"] = config.basic.freemail_base_url + config_data["mail_jwt_token"] = config.basic.freemail_jwt_token + config_data["mail_verify_ssl"] = config.basic.freemail_verify_ssl + config_data["mail_domain"] = config.basic.freemail_domain + elif temp_mail_provider == "gptmail": + config_data["mail_password"] = "" + config_data["mail_base_url"] = config.basic.gptmail_base_url + config_data["mail_api_key"] = config.basic.gptmail_api_key + config_data["mail_verify_ssl"] = config.basic.gptmail_verify_ssl + config_data["mail_domain"] = config.basic.gptmail_domain + elif temp_mail_provider == "cfmail": + config_data["mail_password"] = getattr(client, "jwt_token", "") or getattr(client, "password", "") + config_data["mail_base_url"] = config.basic.cfmail_base_url + config_data["mail_api_key"] = config.basic.cfmail_api_key + config_data["mail_verify_ssl"] = config.basic.cfmail_verify_ssl + config_data["mail_domain"] = config.basic.cfmail_domain + elif temp_mail_provider == "moemail": + config_data["mail_password"] = getattr(client, "email_id", "") or getattr(client, "password", "") + config_data["mail_base_url"] = config.basic.moemail_base_url + config_data["mail_api_key"] = config.basic.moemail_api_key + config_data["mail_domain"] = config.basic.moemail_domain + elif temp_mail_provider == "duckmail": + config_data["mail_password"] = getattr(client, "password", "") + config_data["mail_base_url"] = config.basic.duckmail_base_url + config_data["mail_api_key"] = config.basic.duckmail_api_key + else: + config_data["mail_password"] = getattr(client, "password", "") + + accounts_data = load_accounts_from_source() + updated = False + for acc in accounts_data: + if acc.get("id") == config_data["id"]: + acc.update(config_data) + updated = True + break + if not updated: + accounts_data.append(config_data) + + self._apply_accounts_update(accounts_data) + + log_cb("info", "✅ 配置已保存到数据库") + log_cb("info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + log_cb("info", f"🎉 账户注册完成: {client.email}") + log_cb("info", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + return {"success": True, "email": client.email, "config": config_data} diff --git a/core/session_auth.py b/core/session_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..e558bb179d6b4e4918047f66337d9e18af787e7f --- /dev/null +++ b/core/session_auth.py @@ -0,0 +1,68 @@ +""" +Session认证模块 +提供基于Session的登录认证功能 +""" +import secrets +from functools import wraps +from typing import Optional +from fastapi import HTTPException, Request, Response +from fastapi.responses import RedirectResponse + + +def generate_session_secret() -> str: + """生成随机的session密钥""" + return secrets.token_hex(32) + + +def is_logged_in(request: Request) -> bool: + """检查用户是否已登录""" + return request.session.get("authenticated", False) + + +def login_user(request: Request): + """标记用户为已登录状态""" + request.session["authenticated"] = True + + +def logout_user(request: Request): + """清除用户登录状态""" + request.session.clear() + + +def require_login(redirect_to_login: bool = True): + """ + 要求用户登录的装饰器 + + Args: + redirect_to_login: 未登录时是否重定向到登录页面(默认True) + False时返回404错误 + """ + def decorator(func): + @wraps(func) + async def wrapper(*args, request: Request, **kwargs): + if not is_logged_in(request): + if redirect_to_login: + accept_header = (request.headers.get("accept") or "").lower() + wants_html = "text/html" in accept_header or request.url.path.endswith("/html") + + if wants_html: + # 清理掉 URL 中可能重复的 PATH_PREFIX + # 避免重定向路径出现多层前缀 + path = request.url.path + + # 兼容 main 中 PATH_PREFIX 为空的情况 + import main + prefix = main.PATH_PREFIX + + if prefix: + login_url = f"/{prefix}/login" + else: + login_url = "/login" + + return RedirectResponse(url=login_url, status_code=302) + + raise HTTPException(401, "Unauthorized") + + return await func(*args, request=request, **kwargs) + return wrapper + return decorator diff --git a/core/storage.py b/core/storage.py new file mode 100644 index 0000000000000000000000000000000000000000..e4dc995dfa2027af82010923ae7be8abcd04dbb4 --- /dev/null +++ b/core/storage.py @@ -0,0 +1,1127 @@ +""" +Storage abstraction supporting SQLite and PostgreSQL backends. + +Priority: +1) DATABASE_URL -> PostgreSQL +2) SQLITE_PATH -> SQLite (defaults to data.db when DATABASE_URL is empty) +3) No file fallback +""" + +import asyncio +import json +import logging +import os +import sqlite3 +import threading +import time +from typing import Optional + +from dotenv import load_dotenv + +load_dotenv() + +logger = logging.getLogger(__name__) + +_db_pool = None +_db_pool_lock = None +_db_loop = None +_db_thread = None +_db_loop_lock = threading.Lock() + +_sqlite_conn = None +_sqlite_lock = threading.Lock() + + +def _get_database_url() -> str: + return os.environ.get("DATABASE_URL", "").strip() + +def _default_sqlite_path() -> str: + return os.path.join("data", "data.db") + +def _get_sqlite_path() -> str: + env_path = os.environ.get("SQLITE_PATH", "").strip() + if env_path: + return env_path + return _default_sqlite_path() + +def _get_backend() -> str: + if _get_database_url(): + return "postgres" + if _get_sqlite_path(): + return "sqlite" + return "" + +def is_database_enabled() -> bool: + """Return True when a database backend is configured.""" + return bool(_get_backend()) + + +def _data_file_path(name: str) -> str: + return os.path.join("data", name) + + +def _ensure_backend_initialized() -> None: + backend = _get_backend() + if backend == "postgres": + _run_in_db_loop(_get_pool()) + return + if backend == "sqlite": + _get_sqlite_conn() + return + + +async def has_accounts() -> Optional[bool]: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow("SELECT 1 FROM accounts LIMIT 1") + return bool(row) + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute("SELECT 1 FROM accounts LIMIT 1").fetchone() + return bool(row) + return None + + +def has_accounts_sync() -> Optional[bool]: + return _run_in_db_loop(has_accounts()) + + +async def has_settings() -> Optional[bool]: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow( + "SELECT 1 FROM kv_settings WHERE key = $1", + "settings", + ) + return bool(row) + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute( + "SELECT 1 FROM kv_settings WHERE key = ?", + ("settings",), + ).fetchone() + return bool(row) + return None + + +def has_settings_sync() -> Optional[bool]: + return _run_in_db_loop(has_settings()) + + +async def has_stats() -> Optional[bool]: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow( + "SELECT 1 FROM kv_stats WHERE key = $1", + "stats", + ) + return bool(row) + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute( + "SELECT 1 FROM kv_stats WHERE key = ?", + ("stats",), + ).fetchone() + return bool(row) + return None + + +def has_stats_sync() -> Optional[bool]: + return _run_in_db_loop(has_stats()) + + +def _ensure_db_loop() -> asyncio.AbstractEventLoop: + global _db_loop, _db_thread + if _db_loop and _db_thread and _db_thread.is_alive(): + return _db_loop + with _db_loop_lock: + if _db_loop and _db_thread and _db_thread.is_alive(): + return _db_loop + loop = asyncio.new_event_loop() + + def _runner() -> None: + asyncio.set_event_loop(loop) + loop.run_forever() + + thread = threading.Thread(target=_runner, name="storage-db-loop", daemon=True) + thread.start() + _db_loop = loop + _db_thread = thread + return _db_loop + + +def _run_in_db_loop(coro): + loop = _ensure_db_loop() + future = asyncio.run_coroutine_threadsafe(coro, loop) + return future.result() + +def _get_sqlite_conn(): + """Get (or create) the SQLite connection.""" + global _sqlite_conn + if _sqlite_conn is not None: + return _sqlite_conn + with _sqlite_lock: + if _sqlite_conn is not None: + return _sqlite_conn + sqlite_path = _get_sqlite_path() + if not sqlite_path: + raise ValueError("SQLITE_PATH is not set") + os.makedirs(os.path.dirname(sqlite_path) or ".", exist_ok=True) + conn = sqlite3.connect(sqlite_path, check_same_thread=False) + conn.row_factory = sqlite3.Row + _init_sqlite_tables(conn) + _sqlite_conn = conn + logger.info(f"[STORAGE] SQLite initialized at {sqlite_path}") + return _sqlite_conn + + +async def _get_pool(): + """Get (or create) the asyncpg connection pool.""" + global _db_pool, _db_pool_lock + if _db_pool is not None: + return _db_pool + if _db_pool_lock is None: + _db_pool_lock = asyncio.Lock() + async with _db_pool_lock: + if _db_pool is not None: + return _db_pool + db_url = _get_database_url() + if not db_url: + raise ValueError("DATABASE_URL is not set") + try: + import asyncpg + _db_pool = await asyncpg.create_pool( + db_url, + min_size=0, + max_size=10, + command_timeout=30, + ) + await _init_tables(_db_pool) + logger.info("[STORAGE] PostgreSQL pool initialized") + except ImportError: + logger.error("[STORAGE] asyncpg is required for database storage") + raise + except Exception as e: + logger.error(f"[STORAGE] Database connection failed: {e}") + raise + return _db_pool + + +async def _reset_pool(): + """Close and recreate the connection pool (called on stale connection errors).""" + global _db_pool + if _db_pool is not None: + try: + await _db_pool.close() + except Exception: + pass + _db_pool = None + return await _get_pool() + + +from contextlib import asynccontextmanager + +@asynccontextmanager +async def _pg_acquire(): + """Acquire a connection with automatic retry on stale connection errors.""" + import asyncpg + pool = await _get_pool() + try: + async with pool.acquire() as conn: + yield conn + except (asyncpg.ConnectionDoesNotExistError, + asyncpg.InterfaceError, + OSError) as e: + logger.warning(f"[STORAGE] Connection lost, resetting pool: {e}") + await _reset_pool() + raise + + +async def _init_tables(pool) -> None: + """Initialize PostgreSQL tables.""" + async with pool.acquire() as conn: + await conn.execute( + """ + CREATE TABLE IF NOT EXISTS accounts ( + account_id TEXT PRIMARY KEY, + position INTEGER NOT NULL, + data JSONB NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + await conn.execute( + """ + CREATE INDEX IF NOT EXISTS accounts_position_idx + ON accounts(position) + """ + ) + await conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_settings ( + key TEXT PRIMARY KEY, + value JSONB NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + await conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_stats ( + key TEXT PRIMARY KEY, + value JSONB NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + await conn.execute( + """ + CREATE TABLE IF NOT EXISTS task_history ( + id TEXT PRIMARY KEY, + data JSONB NOT NULL, + created_at DOUBLE PRECISION NOT NULL + ) + """ + ) + await conn.execute( + """ + CREATE INDEX IF NOT EXISTS task_history_created_at_idx + ON task_history(created_at DESC) + """ + ) + logger.info("[STORAGE] Database tables initialized") + +def _init_sqlite_tables(conn: sqlite3.Connection) -> None: + """Initialize SQLite tables.""" + with conn: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS accounts ( + account_id TEXT PRIMARY KEY, + position INTEGER NOT NULL, + data TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS accounts_position_idx + ON accounts(position) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_stats ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS task_history ( + id TEXT PRIMARY KEY, + data TEXT NOT NULL, + created_at REAL NOT NULL + ) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS task_history_created_at_idx + ON task_history(created_at) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS request_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp INTEGER NOT NULL, + model TEXT NOT NULL, + ttfb_ms INTEGER, + total_ms INTEGER, + status TEXT NOT NULL, + status_code INTEGER, + created_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS request_logs_timestamp_idx + ON request_logs(timestamp) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS request_logs_model_idx + ON request_logs(model) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS request_logs_status_idx + ON request_logs(status) + """ + ) + + +# ==================== Accounts storage ==================== + +def _normalize_accounts(accounts: list) -> list: + normalized = [] + for index, acc in enumerate(accounts, 1): + if not isinstance(acc, dict): + continue + account_id = acc.get("id") or f"account_{index}" + next_acc = dict(acc) + next_acc.setdefault("id", account_id) + normalized.append(next_acc) + return normalized + +def _parse_account_value(value) -> Optional[dict]: + if value is None: + return None + if isinstance(value, str): + try: + value = json.loads(value) + except Exception: + return None + if isinstance(value, dict): + return value + return None + +async def _load_accounts_from_table() -> Optional[list]: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + rows = await conn.fetch( + "SELECT data FROM accounts ORDER BY position ASC" + ) + if not rows: + return [] + accounts = [] + for row in rows: + value = _parse_account_value(row["data"]) + if value is not None: + accounts.append(value) + return accounts + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + rows = conn.execute( + "SELECT data FROM accounts ORDER BY position ASC" + ).fetchall() + if not rows: + return [] + accounts = [] + for row in rows: + value = _parse_account_value(row["data"]) + if value is not None: + accounts.append(value) + return accounts + return None + +async def _save_accounts_to_table(accounts: list) -> bool: + backend = _get_backend() + if backend == "postgres": + normalized = _normalize_accounts(accounts) + async with _pg_acquire() as conn: + async with conn.transaction(): + await conn.execute("DELETE FROM accounts") + for index, acc in enumerate(normalized, 1): + await conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES ($1, $2, $3, CURRENT_TIMESTAMP) + """, + acc["id"], + index, + json.dumps(acc, ensure_ascii=False), + ) + logger.info(f"[STORAGE] Saved {len(normalized)} accounts to database") + return True + if backend == "sqlite": + conn = _get_sqlite_conn() + normalized = _normalize_accounts(accounts) + with _sqlite_lock, conn: + conn.execute("DELETE FROM accounts") + for index, acc in enumerate(normalized, 1): + conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES (?, ?, ?, CURRENT_TIMESTAMP) + """, + (acc["id"], index, json.dumps(acc, ensure_ascii=False)), + ) + logger.info(f"[STORAGE] Saved {len(normalized)} accounts to database") + return True + return False + +async def load_accounts() -> Optional[list]: + """ + 从数据库加载账户配置(如果启用) + + 注意:不再自动从 kv_store 迁移 + 如需迁移,请手动运行:python scripts/migrate_to_database.py + + 返回 None 表示降级到文件存储 + """ + if not is_database_enabled(): + return None + try: + data = await _load_accounts_from_table() + if data is None: + return None + + if data: + logger.info(f"[STORAGE] 从数据库加载 {len(data)} 个账户") + else: + logger.info("[STORAGE] 数据库中未找到账户") + + return data + except Exception as e: + logger.error(f"[STORAGE] 数据库读取失败: {e}") + return None + + +async def get_accounts_updated_at() -> Optional[float]: + """ + Get the accounts updated_at timestamp (epoch seconds). + Return None if database is not enabled or failed. + """ + if not is_database_enabled(): + return None + backend = _get_backend() + try: + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow( + "SELECT EXTRACT(EPOCH FROM MAX(updated_at)) AS ts FROM accounts" + ) + if not row or row["ts"] is None: + return None + return float(row["ts"]) + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute( + "SELECT STRFTIME('%s', MAX(updated_at)) AS ts FROM accounts" + ).fetchone() + if not row or row["ts"] is None: + return None + return float(row["ts"]) + except Exception as e: + logger.error(f"[STORAGE] Database accounts updated_at failed: {e}") + return None + + +def get_accounts_updated_at_sync() -> Optional[float]: + """Sync wrapper for get_accounts_updated_at.""" + return _run_in_db_loop(get_accounts_updated_at()) + + +async def save_accounts(accounts: list) -> bool: + """Save account configuration to database when enabled.""" + if not is_database_enabled(): + return False + try: + return await _save_accounts_to_table(accounts) + except Exception as e: + logger.error(f"[STORAGE] Database write failed: {e}") + return False + + +def load_accounts_sync() -> Optional[list]: + """Sync wrapper for load_accounts (safe in sync/async call sites).""" + return _run_in_db_loop(load_accounts()) + + +def save_accounts_sync(accounts: list) -> bool: + """Sync wrapper for save_accounts (safe in sync/async call sites).""" + return _run_in_db_loop(save_accounts(accounts)) + +async def _get_account_data(account_id: str) -> Optional[dict]: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow( + "SELECT data FROM accounts WHERE account_id = $1", + account_id, + ) + if not row: + return None + return _parse_account_value(row["data"]) + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute( + "SELECT data FROM accounts WHERE account_id = ?", + (account_id,), + ).fetchone() + if not row: + return None + return _parse_account_value(row["data"]) + return None + +async def _update_account_data(account_id: str, data: dict) -> bool: + backend = _get_backend() + payload = json.dumps(data, ensure_ascii=False) + if backend == "postgres": + async with _pg_acquire() as conn: + result = await conn.execute( + """ + UPDATE accounts + SET data = $2, updated_at = CURRENT_TIMESTAMP + WHERE account_id = $1 + """, + account_id, + payload, + ) + return result.startswith("UPDATE") and not result.endswith("0") + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + cur = conn.execute( + """ + UPDATE accounts + SET data = ?, updated_at = CURRENT_TIMESTAMP + WHERE account_id = ? + """, + (payload, account_id), + ) + return cur.rowcount > 0 + return False + +async def update_account_disabled(account_id: str, disabled: bool) -> bool: + data = await _get_account_data(account_id) + if data is None: + return False + data["disabled"] = disabled + return await _update_account_data(account_id, data) + +def _apply_cooldown_data(data: dict, cooldown_data: dict) -> None: + """应用冷却数据到账户数据(消除重复代码)""" + data["quota_cooldowns"] = cooldown_data.get("quota_cooldowns", {}) + data["conversation_count"] = cooldown_data.get("conversation_count", 0) + data["failure_count"] = cooldown_data.get("failure_count", 0) + data["daily_usage"] = cooldown_data.get("daily_usage", {"text": 0, "images": 0, "videos": 0}) + data["daily_usage_date"] = cooldown_data.get("daily_usage_date", "") + +async def update_account_cooldown(account_id: str, cooldown_data: dict) -> bool: + """更新单个账户的冷却状态和统计数据""" + data = await _get_account_data(account_id) + if data is None: + return False + _apply_cooldown_data(data, cooldown_data) + return await _update_account_data(account_id, data) + +async def bulk_update_accounts_cooldown(updates: list[tuple[str, dict]]) -> tuple[int, list[str]]: + """批量更新账户冷却状态""" + if not updates: + return 0, [] + + account_ids = [account_id for account_id, _ in updates] + cooldown_map = {account_id: cooldown_data for account_id, cooldown_data in updates} + + backend = _get_backend() + existing: dict[str, dict] = {} + updated = 0 + + if backend == "postgres": + async with _pg_acquire() as conn: + # SELECT + UPDATE in one connection to avoid contention + rows = await conn.fetch( + "SELECT account_id, data FROM accounts WHERE account_id = ANY($1)", + account_ids, + ) + for row in rows: + data = _parse_account_value(row["data"]) + if data is not None: + existing[row["account_id"]] = data + + missing = [aid for aid in account_ids if aid not in existing] + if existing: + async with conn.transaction(): + for account_id, data in existing.items(): + cooldown_data = cooldown_map[account_id] + _apply_cooldown_data(data, cooldown_data) + payload = json.dumps(data, ensure_ascii=False) + result = await conn.execute( + """ + UPDATE accounts + SET data = $2, updated_at = CURRENT_TIMESTAMP + WHERE account_id = $1 + """, + account_id, + payload, + ) + if result.startswith("UPDATE") and not result.endswith("0"): + updated += 1 + return updated, missing if existing else account_ids + + elif backend == "sqlite": + conn = _get_sqlite_conn() + placeholders = ",".join(["?"] * len(account_ids)) + with _sqlite_lock: + rows = conn.execute( + f"SELECT account_id, data FROM accounts WHERE account_id IN ({placeholders})", + tuple(account_ids), + ).fetchall() + for row in rows: + data = _parse_account_value(row["data"]) + if data is not None: + existing[row["account_id"]] = data + + missing = [aid for aid in account_ids if aid not in existing] + if not existing: + return 0, missing + + with _sqlite_lock, conn: + for account_id, data in existing.items(): + cooldown_data = cooldown_map[account_id] + _apply_cooldown_data(data, cooldown_data) + payload = json.dumps(data, ensure_ascii=False) + cur = conn.execute( + """ + UPDATE accounts + SET data = ?, updated_at = CURRENT_TIMESTAMP + WHERE account_id = ? + """, + (payload, account_id), + ) + if cur.rowcount > 0: + updated += 1 + return updated, missing + + return 0, account_ids + +async def bulk_update_accounts_disabled(account_ids: list[str], disabled: bool) -> tuple[int, list[str]]: + if not account_ids: + return 0, [] + backend = _get_backend() + existing: dict[str, dict] = {} + if backend == "postgres": + async with _pg_acquire() as conn: + rows = await conn.fetch( + "SELECT account_id, data FROM accounts WHERE account_id = ANY($1)", + account_ids, + ) + for row in rows: + data = _parse_account_value(row["data"]) + if data is not None: + existing[row["account_id"]] = data + elif backend == "sqlite": + conn = _get_sqlite_conn() + placeholders = ",".join(["?"] * len(account_ids)) + with _sqlite_lock: + rows = conn.execute( + f"SELECT account_id, data FROM accounts WHERE account_id IN ({placeholders})", + tuple(account_ids), + ).fetchall() + for row in rows: + data = _parse_account_value(row["data"]) + if data is not None: + existing[row["account_id"]] = data + else: + return 0, account_ids + + missing = [account_id for account_id in account_ids if account_id not in existing] + if not existing: + return 0, missing + + updated = 0 + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + async with conn.transaction(): + for account_id, data in existing.items(): + data["disabled"] = disabled + payload = json.dumps(data, ensure_ascii=False) + result = await conn.execute( + """ + UPDATE accounts + SET data = $2, updated_at = CURRENT_TIMESTAMP + WHERE account_id = $1 + """, + account_id, + payload, + ) + if result.startswith("UPDATE") and not result.endswith("0"): + updated += 1 + elif backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + for account_id, data in existing.items(): + data["disabled"] = disabled + payload = json.dumps(data, ensure_ascii=False) + cur = conn.execute( + """ + UPDATE accounts + SET data = ?, updated_at = CURRENT_TIMESTAMP + WHERE account_id = ? + """, + (payload, account_id), + ) + if cur.rowcount > 0: + updated += 1 + return updated, missing + +async def _renumber_account_positions() -> None: + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + await conn.execute( + """ + WITH ordered AS ( + SELECT account_id, ROW_NUMBER() OVER (ORDER BY position ASC) AS new_pos + FROM accounts + ) + UPDATE accounts AS a + SET position = ordered.new_pos, + updated_at = CURRENT_TIMESTAMP + FROM ordered + WHERE a.account_id = ordered.account_id + """ + ) + return + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + rows = conn.execute( + "SELECT account_id FROM accounts ORDER BY position ASC" + ).fetchall() + for index, row in enumerate(rows, 1): + conn.execute( + "UPDATE accounts SET position = ?, updated_at = CURRENT_TIMESTAMP WHERE account_id = ?", + (index, row["account_id"]), + ) + +async def delete_accounts(account_ids: list[str]) -> int: + if not account_ids: + return 0 + backend = _get_backend() + deleted = 0 + if backend == "postgres": + async with _pg_acquire() as conn: + result = await conn.execute( + "DELETE FROM accounts WHERE account_id = ANY($1)", + account_ids, + ) + try: + deleted = int(result.split()[-1]) + except Exception: + deleted = 0 + elif backend == "sqlite": + conn = _get_sqlite_conn() + placeholders = ",".join(["?"] * len(account_ids)) + with _sqlite_lock, conn: + cur = conn.execute( + f"DELETE FROM accounts WHERE account_id IN ({placeholders})", + tuple(account_ids), + ) + deleted = cur.rowcount or 0 + else: + return 0 + + if deleted > 0: + await _renumber_account_positions() + return deleted + +def update_account_disabled_sync(account_id: str, disabled: bool) -> bool: + return _run_in_db_loop(update_account_disabled(account_id, disabled)) + +def update_account_cooldown_sync(account_id: str, cooldown_data: dict) -> bool: + return _run_in_db_loop(update_account_cooldown(account_id, cooldown_data)) + +def bulk_update_accounts_cooldown_sync(updates: list[tuple[str, dict]]) -> tuple[int, list[str]]: + return _run_in_db_loop(bulk_update_accounts_cooldown(updates)) + +def bulk_update_accounts_disabled_sync(account_ids: list[str], disabled: bool) -> tuple[int, list[str]]: + return _run_in_db_loop(bulk_update_accounts_disabled(account_ids, disabled)) + +def delete_accounts_sync(account_ids: list[str]) -> int: + return _run_in_db_loop(delete_accounts(account_ids)) + + +# ==================== Settings storage ==================== + +async def _load_kv(table_name: str, key: str) -> Optional[dict]: + """加载键值数据""" + backend = _get_backend() + if backend == "postgres": + async with _pg_acquire() as conn: + row = await conn.fetchrow( + f"SELECT value FROM {table_name} WHERE key = $1", + key, + ) + if not row: + return None + value = row["value"] + if isinstance(value, str): + return json.loads(value) + return value + + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + row = conn.execute( + f"SELECT value FROM {table_name} WHERE key = ?", + (key,), + ).fetchone() + if not row: + return None + value = row["value"] + if isinstance(value, str): + return json.loads(value) + return value + return None + + +async def _save_kv(table_name: str, key: str, value: dict) -> bool: + backend = _get_backend() + payload = json.dumps(value, ensure_ascii=False) + if backend == "postgres": + async with _pg_acquire() as conn: + await conn.execute( + f""" + INSERT INTO {table_name} (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + ON CONFLICT (key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = CURRENT_TIMESTAMP + """, + key, + payload, + ) + return True + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + conn.execute( + f""" + INSERT INTO {table_name} (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = CURRENT_TIMESTAMP + """, + (key, payload), + ) + return True + return False + +async def load_settings() -> Optional[dict]: + if not is_database_enabled(): + return None + try: + return await _load_kv("kv_settings", "settings") + except Exception as e: + logger.error(f"[STORAGE] Settings read failed: {e}") + return None + + +async def save_settings(settings: dict) -> bool: + if not is_database_enabled(): + return False + try: + saved = await _save_kv("kv_settings", "settings", settings) + if saved: + logger.info("[STORAGE] Settings saved to database") + return saved + except Exception as e: + logger.error(f"[STORAGE] Settings write failed: {e}") + return False + + +# ==================== Stats storage ==================== + +async def load_stats() -> Optional[dict]: + if not is_database_enabled(): + return None + try: + return await _load_kv("kv_stats", "stats") + except Exception as e: + logger.error(f"[STORAGE] Stats read failed: {e}") + return None + + +async def save_stats(stats: dict) -> bool: + if not is_database_enabled(): + return False + try: + return await _save_kv("kv_stats", "stats", stats) + except Exception as e: + logger.error(f"[STORAGE] Stats write failed: {e}") + return False + + +def load_settings_sync() -> Optional[dict]: + return _run_in_db_loop(load_settings()) + + +def save_settings_sync(settings: dict) -> bool: + return _run_in_db_loop(save_settings(settings)) + + +def load_stats_sync() -> Optional[dict]: + return _run_in_db_loop(load_stats()) + + +def save_stats_sync(stats: dict) -> bool: + return _run_in_db_loop(save_stats(stats)) + + +# ==================== Task history storage ==================== + +async def save_task_history_entry(entry: dict) -> bool: + if not is_database_enabled(): + return False + entry_id = entry.get("id") + if not entry_id: + return False + created_at = float(entry.get("created_at", time.time())) + payload = json.dumps(entry, ensure_ascii=False) + backend = _get_backend() + try: + if backend == "postgres": + async with _pg_acquire() as conn: + await conn.execute( + """ + INSERT INTO task_history (id, data, created_at) + VALUES ($1, $2, $3) + ON CONFLICT (id) DO UPDATE SET + data = EXCLUDED.data, + created_at = EXCLUDED.created_at + """, + entry_id, + payload, + created_at, + ) + await conn.execute( + """ + DELETE FROM task_history + WHERE id IN ( + SELECT id FROM task_history + ORDER BY created_at DESC + OFFSET 100 + ) + """ + ) + return True + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + conn.execute( + """ + INSERT INTO task_history (id, data, created_at) + VALUES (?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + data = excluded.data, + created_at = excluded.created_at + """, + (entry_id, payload, created_at), + ) + conn.execute( + """ + DELETE FROM task_history + WHERE id IN ( + SELECT id FROM task_history + ORDER BY created_at DESC + LIMIT -1 OFFSET 100 + ) + """ + ) + return True + except Exception as e: + logger.error(f"[STORAGE] Task history write failed: {e}") + return False + + +async def load_task_history(limit: int = 100) -> Optional[list]: + if not is_database_enabled(): + return None + backend = _get_backend() + try: + if backend == "postgres": + async with _pg_acquire() as conn: + rows = await conn.fetch( + """ + SELECT data FROM task_history + ORDER BY created_at DESC + LIMIT $1 + """, + limit, + ) + return [_parse_account_value(row["data"]) for row in rows if row and row["data"] is not None] + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock: + rows = conn.execute( + """ + SELECT data FROM task_history + ORDER BY created_at DESC + LIMIT ? + """, + (limit,), + ).fetchall() + results = [] + for row in rows: + value = _parse_account_value(row["data"]) + if value is not None: + results.append(value) + return results + except Exception as e: + logger.error(f"[STORAGE] Task history read failed: {e}") + return None + + +async def clear_task_history() -> int: + if not is_database_enabled(): + return 0 + backend = _get_backend() + try: + if backend == "postgres": + async with _pg_acquire() as conn: + result = await conn.execute("DELETE FROM task_history") + if result.startswith("DELETE"): + parts = result.split() + return int(parts[-1]) if parts else 0 + return 0 + if backend == "sqlite": + conn = _get_sqlite_conn() + with _sqlite_lock, conn: + cur = conn.execute("DELETE FROM task_history") + return cur.rowcount or 0 + except Exception as e: + logger.error(f"[STORAGE] Task history clear failed: {e}") + return 0 + + +def save_task_history_entry_sync(entry: dict) -> bool: + return _run_in_db_loop(save_task_history_entry(entry)) + + +def load_task_history_sync(limit: int = 100) -> Optional[list]: + return _run_in_db_loop(load_task_history(limit)) + + +def clear_task_history_sync() -> int: + return _run_in_db_loop(clear_task_history()) diff --git a/core/uptime.py b/core/uptime.py new file mode 100644 index 0000000000000000000000000000000000000000..38b4d3edf300642db798f37e92875156826adb7c --- /dev/null +++ b/core/uptime.py @@ -0,0 +1,150 @@ +""" +Uptime 实时监控与心跳历史持久化。 +""" + +from collections import deque +from datetime import datetime, timezone, timedelta +from typing import Dict, List, Optional +import json +import os +from threading import Lock + +# 北京时区 UTC+8 +BEIJING_TZ = timezone(timedelta(hours=8)) + +# 每个服务保留最近 60 条心跳 +MAX_HEARTBEATS = 60 +SLOW_THRESHOLD_MS = 40000 +WARNING_STATUS_CODES = {429} + +_storage_path: Optional[str] = None +_storage_lock = Lock() + +# 服务注册表 +SERVICES = { + "api_service": {"name": "API 服务", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "account_pool": {"name": "服务资源", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-2.5-flash": {"name": "Gemini 2.5 Flash", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-2.5-pro": {"name": "Gemini 2.5 Pro", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-3-flash-preview": {"name": "Gemini 3 Flash Preview", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-3-pro-preview": {"name": "Gemini 3 Pro Preview", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-3.1-pro-preview": {"name": "Gemini 3.1 Pro Preview", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-imagen": {"name": "Gemini Imagen", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, + "gemini-veo": {"name": "Gemini Veo", "heartbeats": deque(maxlen=MAX_HEARTBEATS)}, +} + +SUPPORTED_MODELS = [ + "gemini-2.5-flash", + "gemini-2.5-pro", + "gemini-3-flash-preview", + "gemini-3-pro-preview", + "gemini-3.1-pro-preview", + "gemini-imagen", + "gemini-veo", +] + + +def configure_storage(path: Optional[str]) -> None: + """配置心跳持久化路径。""" + global _storage_path + _storage_path = path + + +def _classify_level(success: bool, status_code: Optional[int], latency_ms: Optional[int]) -> str: + if status_code in WARNING_STATUS_CODES: + return "warn" + if success and latency_ms is not None and latency_ms >= SLOW_THRESHOLD_MS: + return "warn" + return "up" if success else "down" + + +def _save_heartbeats() -> None: + if not _storage_path: + return + try: + payload = {} + for service_id, service_data in SERVICES.items(): + payload[service_id] = list(service_data["heartbeats"]) + os.makedirs(os.path.dirname(_storage_path), exist_ok=True) + with _storage_lock, open(_storage_path, "w", encoding="utf-8") as f: + json.dump(payload, f, ensure_ascii=True, indent=2) + except Exception: + return + + +def load_heartbeats() -> None: + if not _storage_path or not os.path.exists(_storage_path): + return + try: + with _storage_lock, open(_storage_path, "r", encoding="utf-8") as f: + payload = json.load(f) + for service_id, heartbeats in payload.items(): + if service_id not in SERVICES: + continue + SERVICES[service_id]["heartbeats"].clear() + for beat in heartbeats[-MAX_HEARTBEATS:]: + SERVICES[service_id]["heartbeats"].append(beat) + except Exception: + return + + +def record_request( + service: str, + success: bool, + latency_ms: Optional[int] = None, + status_code: Optional[int] = None +): + """记录一次心跳。""" + if service not in SERVICES: + return + + level = _classify_level(success, status_code, latency_ms) + heartbeat = { + "time": datetime.now(BEIJING_TZ).strftime("%H:%M:%S"), + "success": success, + "level": level, + } + if latency_ms is not None: + heartbeat["latency_ms"] = latency_ms + if status_code is not None: + heartbeat["status_code"] = status_code + + SERVICES[service]["heartbeats"].append(heartbeat) + _save_heartbeats() + + +def get_realtime_status() -> Dict: + """返回实时监控数据。""" + result = {"services": {}} + + for service_id, service_data in SERVICES.items(): + heartbeats = list(service_data["heartbeats"]) + total = len(heartbeats) + success = sum(1 for h in heartbeats if h.get("success")) + + uptime = (success / total * 100) if total > 0 else 100.0 + + last_status = "unknown" + if heartbeats: + last_level = heartbeats[-1].get("level") + if last_level in {"up", "down", "warn"}: + last_status = last_level + else: + last_status = "up" if heartbeats[-1].get("success") else "down" + + result["services"][service_id] = { + "name": service_data["name"], + "status": last_status, + "uptime": round(uptime, 1), + "total": total, + "success": success, + "heartbeats": heartbeats[-MAX_HEARTBEATS:], + } + + result["updated_at"] = datetime.now(BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S") + return result + + +async def get_uptime_summary(days: int = 90) -> Dict: + """兼容旧接口。""" + return get_realtime_status() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3835ad35e8bbc671a2675faee1aec76c0e07673d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + gemini-api: + image: cooooookk/gemini-business2api:latest + container_name: gemini-business2api + ports: + - "7860:7860" + volumes: + - ./data:/app/data + env_file: + - .env + restart: unless-stopped + # 健康检查 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:7860/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + # 日志限制 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" diff --git a/docs/DISCLAIMER.md b/docs/DISCLAIMER.md new file mode 100644 index 0000000000000000000000000000000000000000..4c24cd181bcb9af8e0bdf572106d1efd59fa6727 --- /dev/null +++ b/docs/DISCLAIMER.md @@ -0,0 +1,43 @@ +# 使用声明与免责条款 + +## ⚠️ 严禁滥用:禁止将本工具用于商业用途或任何形式的滥用(无论规模大小) + +**本工具严禁用于以下行为:** +- 商业用途或盈利性使用 +- 任何形式的批量操作或自动化滥用(无论规模大小) +- 破坏市场秩序或恶意竞争 +- 违反 Google 服务条款的任何行为 +- 违反 Microsoft 服务条款的任何行为 + +**违规后果**:滥用行为可能导致账号永久封禁、法律追责,一切后果由使用者自行承担。 + +## 📖 合法用途 + +本项目仅限于以下场景: +- 个人学习与技术研究 +- 浏览器自动化技术探索 +- 非商业性技术交流 + +## ⚖️ 法律责任 + +1. **使用者责任**:使用本工具产生的一切后果(包括但不限于账号封禁、数据损失、法律纠纷)由使用者完全承担 +2. **合规义务**:使用者必须遵守所在地法律法规及第三方服务条款(包括但不限于 Google Workspace、Microsoft 365 等服务条款) +3. **作者免责**:作者不对任何违规使用、滥用行为或由此产生的后果承担责任 + +## 📋 技术声明 + +- **无担保**:本项目按"现状"提供,不提供任何形式的担保 +- **第三方依赖**:依赖的第三方服务(如 DuckMail API、Microsoft Graph API 等)可用性不受作者控制 +- **维护权利**:作者保留随时停止维护、变更功能或关闭项目的权利 + +## 🔗 相关服务条款 + +使用本工具时,您必须同时遵守以下第三方服务的条款: +- [Google 服务条款](https://policies.google.com/terms) +- [Google Workspace 附加条款](https://workspace.google.com/terms/service-terms.html) +- [Microsoft 服务协议](https://www.microsoft.com/servicesagreement) +- [Microsoft 365 使用条款](https://www.microsoft.com/licensing/terms) + +--- + +**使用本工具即表示您已阅读、理解并同意遵守以上所有条款。** diff --git a/docs/DISCLAIMER_EN.md b/docs/DISCLAIMER_EN.md new file mode 100644 index 0000000000000000000000000000000000000000..0b8c6b7c0dceac03727658f3270d607e7766520f --- /dev/null +++ b/docs/DISCLAIMER_EN.md @@ -0,0 +1,34 @@ +# Terms of Use and Disclaimer + +## ⚠️ Abuse Prohibited + +**This tool is strictly prohibited for the following uses:** +- Commercial use or profit-making activities +- Any form of batch operations or automated abuse (regardless of scale) +- Market disruption or malicious competition +- Any behavior violating Google's Terms of Service + +**Consequences:** Abuse may result in permanent account bans, legal liability, and all consequences are borne by the user. + +## 📖 Legitimate Use + +This project is limited to the following scenarios: +- Personal learning and technical research +- Browser automation technology exploration +- Non-commercial technical exchange + +## ⚖️ Legal Liability + +1. **User Responsibility**: All consequences arising from the use of this tool (including but not limited to account bans, data loss, legal disputes) are entirely borne by the user +2. **Compliance Obligation**: Users must comply with local laws and regulations and third-party service terms +3. **Author Disclaimer**: The author is not responsible for any violations, abuse, or consequences arising therefrom + +## 📋 Technical Disclaimer + +- **No Warranty**: This project is provided "as is" without any form of warranty +- **Third-Party Dependencies**: Depends on third-party services (such as DuckMail API) whose availability is not controlled by the author +- **Maintenance Rights**: The author reserves the right to stop maintenance, change functionality, or shut down the project at any time + +--- + +**By using this tool, you acknowledge that you have read, understood, and agree to comply with all the above terms.** diff --git a/docs/README_EN.md b/docs/README_EN.md new file mode 100644 index 0000000000000000000000000000000000000000..36d8bb36cc69d62ab414b28bc67c18175f3b53cb --- /dev/null +++ b/docs/README_EN.md @@ -0,0 +1,377 @@ +

+ Gemini Business2API logo +

+

Gemini Business2API

+

Empowering AI with seamless integration

+

+ 简体中文 | English +

+

+ +

Convert Gemini Business to OpenAI-compatible API with multi-account load balancing, image generation, video generation, multimodal capabilities, and built-in admin panel.

+ +--- + +## 📜 License & Disclaimer + +**License**: MIT License - See [LICENSE](../LICENSE) for details + +### ⚠️ Prohibited Use & Anti-Abuse Policy + +**This tool is strictly prohibited for:** +- Commercial use or profit-making activities +- Batch operations or automated abuse of any scale +- Market disruption or malicious competition +- Violations of Google's Terms of Service +- Violations of Microsoft's Terms of Service + +**Consequences**: Violations may result in permanent account suspension and legal liability. All consequences are the sole responsibility of the user. + +**Legitimate Use Only**: Personal learning, technical research, and non-commercial educational purposes only. + +📖 **Full Disclaimer**: [DISCLAIMER_EN.md](DISCLAIMER_EN.md) + +--- + +## ✨ Features + +- ✅ Full OpenAI API compatibility - Seamless integration with existing tools +- ✅ Multi-account load balancing - Round-robin with automatic failover +- ✅ Automated account management - Auto registration & login, multiple temp email providers, headless browser mode +- ✅ Streaming output - Real-time responses +- ✅ Multimodal input - 100+ file types (images, PDF, Office docs, audio, video, code, etc.) +- ✅ Image generation & image-to-image - Configurable models, Base64 or URL output +- ✅ Video generation - Dedicated model with HTML/URL/Markdown output formats +- ✅ Smart file handling - Auto file type detection, supports URL and Base64 +- ✅ Logging & monitoring - Real-time status and statistics +- ✅ Proxy support - Configure via admin settings panel +- ✅ Built-in admin panel - Online configuration and account management +- ✅ PostgreSQL / SQLite storage - Persistent accounts/settings/stats + +## 🤖 Model Capabilities + +| Model ID | Vision | Native Web | File Multimodal | Image Gen | Video Gen | +| ------------------------ | ------ | ---------- | --------------- | --------- | --------- | +| `gemini-auto` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-2.5-flash` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-2.5-pro` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-3-flash-preview` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-3-pro-preview` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-3.1-pro-preview` | ✅ | ✅ | ✅ | Optional | - | +| `gemini-imagen` | ✅ | ✅ | ✅ | ✅ | - | +| `gemini-veo` | ✅ | ✅ | ✅ | - | ✅ | + +> `gemini-imagen`: Dedicated image generation model · `gemini-veo`: Dedicated video generation model + +--- + +## 🚀 Quick Start + +### Method 1: Docker Compose (Recommended) + +**Supports ARM64 and AMD64 architectures** + +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +cp .env.example .env +# Edit .env to set ADMIN_KEY + +docker-compose up -d + +# View logs +docker-compose logs -f + +# Update to latest version +docker-compose pull && docker-compose up -d +``` + +--- + +### Method 2: Setup Script + +> **Prerequisites**: Git, Node.js & npm (for frontend build). Script auto-installs Python 3.11 and uv. + +**Linux / macOS / WSL:** +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +bash setup.sh +# Edit .env to set ADMIN_KEY +source .venv/bin/activate +python main.py +# Background with pm2 +pm2 start main.py --name gemini-api --interpreter ./.venv/bin/python3 +``` + +**Windows:** +```cmd +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api +setup.bat +# Edit .env to set ADMIN_KEY +.venv\Scripts\activate.bat +python main.py +# Background with pm2 +pm2 start main.py --name gemini-api --interpreter ./.venv/Scripts/python.exe +``` + +The script handles: uv install, Python 3.11 download, dependency install, frontend build, `.env` creation. +To update, simply re-run the same script. + +--- + +### Method 3: Manual Deployment + +```bash +git clone https://github.com/Dreamy-rain/gemini-business2api.git +cd gemini-business2api + +curl -LsSf https://astral.sh/uv/install.sh | sh +uv python install 3.11 + +cd frontend && npm install && npm run build && cd .. + +uv venv --python 3.11 .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate.bat +uv pip install -r requirements.txt + +cp .env.example .env +# Edit .env to set ADMIN_KEY +python main.py +``` + +--- + +### Access + +- **Admin Panel**: `http://localhost:7860/` (Login with `ADMIN_KEY`) +- **API Endpoint**: `http://localhost:7860/v1/chat/completions` + +--- + +## 🗄️ Database Persistence + +Set `DATABASE_URL` to persist accounts, settings, and stats. Without it, SQLite (`data.db`) is used automatically. + +**Configuration:** +- Local deployment → add to `.env` +- Cloud platforms → set in platform environment variables + +``` +DATABASE_URL=postgresql://user:password@host/dbname?sslmode=require +``` + +**Free PostgreSQL Providers:** + +| Service | Free Tier | How to Get | +|---------|-----------|-----------| +| [Neon](https://neon.tech) | 512MB / 100 CPUH/month | Sign up → Create Project → Copy Connection string | +| [Aiven](https://aiven.io) | More generous | Sign up → Create PostgreSQL service → Copy connection string | + +> Both `postgres://` and `postgresql://` formats are supported natively. + +
+⚠️ FAQ: Periodic save failure / ConnectionDoesNotExistError + +If you see errors like: + +``` +ERROR [COOLDOWN] Save failed: connection was closed in the middle of operation +asyncpg.exceptions.ConnectionDoesNotExistError: connection was closed in the middle of operation +``` + +This happens when free PostgreSQL providers (e.g., Aiven free tier) close idle connections. **It does not affect normal usage** — the next operation will auto-reconnect. If frequent, consider switching to [Neon](https://neon.tech) or upgrading your database plan. + +
+ +
+📦 Database Migration (Upgrading from older versions) + +If you have legacy local files (`accounts.json` / `settings.yaml` / `stats.json`), run: + +```bash +python scripts/migrate_to_database.py +``` + +The script auto-detects the environment (PostgreSQL / SQLite) and renames old files after migration. + +
+ +--- + +## 📡 API Endpoints + +Fully OpenAI API compatible. Works with ChatGPT-Next-Web, LobeChat, OpenCat, and other clients. + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/v1/chat/completions` | POST | Chat completions (streaming supported) | +| `/v1/models` | GET | List available models | +| `/v1/images/generations` | POST | Image generation (text-to-image) | +| `/v1/images/edits` | POST | Image editing (image-to-image) | +| `/health` | GET | Health check | + +**Example:** + +```bash +curl http://localhost:7860/v1/chat/completions \ + -H "Authorization: Bearer your-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [{"role": "user", "content": "Hello"}], + "stream": true + }' +``` + +> `API_KEY` is configured in Admin Panel → System Settings. Leave empty for public access. Multiple keys supported (comma-separated). + +--- + +## 📧 Email Provider Configuration + +The project supports 4 temporary email providers for automatic account registration. Switch and configure them in **Admin Panel → System Settings → Temp Email Provider**. + +### Moemail (Default) + +Open-source temporary email service, ready to use out of the box. + +- **Project**: [github.com/beilunyang/moemail](https://github.com/beilunyang/moemail) +- **Website**: [moemail.app](https://moemail.app) +- **Config**: API URL + API Key + Domain (optional) + +### DuckMail + +Temporary email API service. Custom domain recommended. + +- **Domain Management**: [domain.duckmail.sbs](https://domain.duckmail.sbs/) +- **Config**: API URL + API Key + Registration Domain + +### GPTMail + +Temporary email API service, no password required. + +- **Default URL**: `https://mail.chatgpt.org.uk` +- **Default API Key**: `gpt-test` +- **Config**: API URL + API Key + Domain (optional) + +### Freemail + +Self-hosted temporary email service, for users with their own servers. + +- **Project**: [github.com/idinging/freemail](https://github.com/idinging/freemail) +- **Config**: Self-hosted service URL + JWT Token + Domain (optional) + +> **Tip**: All email settings are configured in the admin panel. Microsoft email login is also handled through the admin panel. + +--- + +## 🌐 Recommended Deployment Platforms + +In addition to local Docker Compose, these platforms support Docker image deployment: + +| Platform | Free Tier | Features | +|----------|-----------|----------| +| [Render](https://render.com) | ✅ Yes | Docker support, auto SSL, free PostgreSQL | +| [Railway](https://railway.app) | $5/month credit | One-click Docker deploy, built-in database | +| [Fly.io](https://fly.io) | ✅ Yes | Global edge deployment, persistent volumes | +| [Claw Cloud](https://claw.cloud) | ✅ Yes | Container cloud, simple and easy | +| Self-hosted VPS (Recommended) | — | Full control with Docker Compose | + +> Docker image: `cooooookk/gemini-business2api:latest` +> +> Set `ADMIN_KEY` and `DATABASE_URL` environment variables when deploying. + +### Zeabur Deployment Guide + +1. Fork this repository to your GitHub +2. Log in to [Zeabur](https://zeabur.com) → **Create Project** → **Shared Cluster** → **Deploy New Service** → **Connect GitHub** → Select your forked repo +3. Add environment variables: + + | Variable | Required | Description | + |----------|----------|-------------| + | `ADMIN_KEY` | ✅ | Admin panel login key | + | `DATABASE_URL` | Optional | PostgreSQL connection string (recommended to avoid data loss on restart) | + +4. **Persistent Storage** (Important): + + Add persistent storage in service settings: + + | Disk ID | Mount Path | + |---------|-----------| + | `data` | `/app/data` | + +5. Click **Redeploy** to apply settings + +**Update**: GitHub repo → **Sync fork** → **Update branch**, Zeabur will auto-redeploy. + +--- + +## 🔄 Standalone Refresh Service + +To deploy the account refresh service separately from the main API, use the [`refresh-worker` branch](https://github.com/Dreamy-rain/gemini-business2api/tree/refresh-worker): + +```bash +git clone -b refresh-worker https://github.com/Dreamy-rain/gemini-business2api.git gemini-refresh-worker +cd gemini-refresh-worker +cp .env.example .env +# Edit .env to set DATABASE_URL +docker-compose up -d +``` + +This service reads accounts from the database and runs scheduled credential refresh independently. Supports cron scheduling, batch processing, and cooldown deduplication. + +--- + +## 🌐 Socks5 Free Proxy Pool + +Configure a proxy when auto-registering/refreshing accounts to improve success rates: + +- **Project**: [github.com/Dreamy-rain/socks5-proxy](https://github.com/Dreamy-rain/socks5-proxy) +- **Note**: Free proxies are not very stable, but can help improve registration success rates +- **Usage**: Configure in Admin Panel → System Settings → Proxy Settings + +--- + +## 📸 Screenshots + +### Admin System + + + + + + + + + + + + + + +
Admin System 1Admin System 2
Admin System 3Admin System 4
Admin System 5Admin System 6
+ +### Image Effects + + + + + + + + + + +
Image Effects 1Image Effects 2
Image Effects 3Image Effects 4
+ +### Documentation + +- Supported file types: [SUPPORTED_FILE_TYPES.md](SUPPORTED_FILE_TYPES.md) + +## ⭐ Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=Dreamy-rain/gemini-business2api&type=date&legend=top-left)](https://www.star-history.com/#Dreamy-rain/gemini-business2api&type=date&legend=top-left) + +**If this project helps you, please give it a ⭐ Star!** diff --git a/docs/SUPPORTED_FILE_TYPES.md b/docs/SUPPORTED_FILE_TYPES.md new file mode 100644 index 0000000000000000000000000000000000000000..07d1f54f06c83cd26f2e0f30a46eb1c83b7c4d44 --- /dev/null +++ b/docs/SUPPORTED_FILE_TYPES.md @@ -0,0 +1,383 @@ +# 支持的文件类型清单 + +本文档列出了 Gemini 可能支持的所有文件类型(可能支持)。 + +**支持的文件类型**(12 个分类,100+ 种格式): + +- 🖼️ **图片文件** - 11 种格式(PNG, JPEG, WebP, GIF, BMP, TIFF, SVG, ICO, HEIC, HEIF, AVIF) +- 📄 **文档文件** - 9 种格式(PDF, TXT, Markdown, HTML, XML, CSV, TSV, RTF, LaTeX) +- 📊 **Microsoft Office** - 6 种格式(.docx, .doc, .xlsx, .xls, .pptx, .ppt) +- 📝 **Google Workspace** - 3 种格式(Docs, Sheets, Slides) +- 💻 **代码文件** - 19 种语言(Python, JavaScript, TypeScript, Java, C/C++, Go, Rust, PHP, Ruby, Swift, Kotlin, Scala, Shell, PowerShell, SQL, R, MATLAB 等) +- 🎨 **Web 开发** - 8 种格式(CSS, SCSS, LESS, JSON, YAML, TOML, Vue, Svelte) +- 🎵 **音频文件** - 10 种格式(MP3, WAV, AAC, M4A, OGG, FLAC, AIFF, WMA, OPUS, AMR) +- 🎬 **视频文件** - 10 种格式(MP4, MOV, AVI, MPEG, WebM, FLV, WMV, MKV, 3GPP, M4V) +- 📦 **数据文件** - 6 种格式(JSON, JSONL, CSV, TSV, Parquet, Avro) +- 🗜️ **压缩文件** - 5 种格式(ZIP, RAR, 7Z, TAR, GZ) +- 🔧 **配置文件** - 5 种格式(YAML, TOML, INI, ENV, Properties) +- 📚 **电子书** - 2 种格式(EPUB, MOBI) + +## 🖼️ 图片文件(Image Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---- | --------------- | --------------- | ---------- | ------------------ | +| PNG | `.png` | `image/png` | ✅ 完全支持 | 无损压缩,支持透明 | +| JPEG | `.jpg`, `.jpeg` | `image/jpeg` | ✅ 完全支持 | 有损压缩,照片常用 | +| WebP | `.webp` | `image/webp` | ✅ 完全支持 | 现代格式,体积小 | +| GIF | `.gif` | `image/gif` | ✅ 完全支持 | 支持动画 | +| BMP | `.bmp` | `image/bmp` | ✅ 支持 | Windows 位图 | +| TIFF | `.tiff`, `.tif` | `image/tiff` | ✅ 支持 | 高质量图像 | +| SVG | `.svg` | `image/svg+xml` | ✅ 支持 | 矢量图形 | +| ICO | `.ico` | `image/x-icon` | ✅ 支持 | 图标文件 | +| HEIC | `.heic` | `image/heic` | ✅ 支持 | Apple 高效图像格式 | +| HEIF | `.heif` | `image/heif` | ✅ 支持 | 高效图像格式 | +| AVIF | `.avif` | `image/avif` | ✅ 支持 | 新一代图像格式 | + +## 📄 文档文件(Document Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| -------- | --------------- | ------------------------------- | ---------- | ---------------------- | +| PDF | `.pdf` | `application/pdf` | ✅ 完全支持 | 可提取文本、图片、表格 | +| 纯文本 | `.txt` | `text/plain` | ✅ 完全支持 | 纯文本文件 | +| Markdown | `.md` | `text/markdown` | ✅ 完全支持 | 标记语言 | +| HTML | `.html`, `.htm` | `text/html` | ✅ 完全支持 | 网页文件 | +| XML | `.xml` | `text/xml` 或 `application/xml` | ✅ 完全支持 | 结构化数据 | +| CSV | `.csv` | `text/csv` | ✅ 完全支持 | 表格数据 | +| TSV | `.tsv` | `text/tab-separated-values` | ✅ 支持 | 制表符分隔 | +| RTF | `.rtf` | `application/rtf` | ✅ 支持 | 富文本格式 | +| LaTeX | `.tex` | `text/x-tex` | ✅ 支持 | 科学文档 | + +## 📊 Microsoft Office 文档 + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| --------------- | ------- | --------------------------------------------------------------------------- | -------- | ---------------- | +| Word (新) | `.docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | ✅ 支持 | 可提取文本和格式 | +| Word (旧) | `.doc` | `application/msword` | ✅ 支持 | 旧版 Word 文档 | +| Excel (新) | `.xlsx` | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | ✅ 支持 | 可读取表格数据 | +| Excel (旧) | `.xls` | `application/vnd.ms-excel` | ✅ 支持 | 旧版 Excel 文档 | +| PowerPoint (新) | `.pptx` | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | ✅ 支持 | 可提取文本和图片 | +| PowerPoint (旧) | `.ppt` | `application/vnd.ms-powerpoint` | ✅ 支持 | 旧版 PPT 文档 | + +## 📝 Google Workspace 文档 + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ------------- | ---------- | ------------------------------------------ | -------- | ------------ | +| Google Docs | `.gdoc` | `application/vnd.google-apps.document` | ✅ 支持 | 需要导出链接 | +| Google Sheets | `.gsheet` | `application/vnd.google-apps.spreadsheet` | ✅ 支持 | 需要导出链接 | +| Google Slides | `.gslides` | `application/vnd.google-apps.presentation` | ✅ 支持 | 需要导出链接 | + +## 💻 代码文件(Code Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---------- | --------------------- | --------------------------------------------- | ---------- | --------------- | +| Python | `.py` | `text/x-python` 或 `application/x-python` | ✅ 完全支持 | Python 代码 | +| JavaScript | `.js` | `text/javascript` 或 `application/javascript` | ✅ 完全支持 | JS 代码 | +| TypeScript | `.ts` | `text/typescript` 或 `application/typescript` | ✅ 完全支持 | TS 代码 | +| JSX/TSX | `.jsx`, `.tsx` | `text/jsx`, `text/tsx` | ✅ 支持 | React 组件 | +| Java | `.java` | `text/x-java-source` | ✅ 完全支持 | Java 代码 | +| C | `.c` | `text/x-c` | ✅ 支持 | C 语言 | +| C++ | `.cpp`, `.cc`, `.cxx` | `text/x-c++` | ✅ 支持 | C++ 代码 | +| C# | `.cs` | `text/x-csharp` | ✅ 支持 | C# 代码 | +| Go | `.go` | `text/x-go` | ✅ 支持 | Go 语言 | +| Rust | `.rs` | `text/x-rust` | ✅ 支持 | Rust 代码 | +| PHP | `.php` | `text/x-php` 或 `application/x-php` | ✅ 支持 | PHP 代码 | +| Ruby | `.rb` | `text/x-ruby` | ✅ 支持 | Ruby 代码 | +| Swift | `.swift` | `text/x-swift` | ✅ 支持 | Swift 代码 | +| Kotlin | `.kt` | `text/x-kotlin` | ✅ 支持 | Kotlin 代码 | +| Scala | `.scala` | `text/x-scala` | ✅ 支持 | Scala 代码 | +| Shell | `.sh`, `.bash` | `text/x-shellscript` | ✅ 支持 | Shell 脚本 | +| PowerShell | `.ps1` | `text/x-powershell` | ✅ 支持 | PowerShell 脚本 | +| SQL | `.sql` | `text/x-sql` 或 `application/sql` | ✅ 支持 | SQL 脚本 | +| R | `.r`, `.R` | `text/x-r` | ✅ 支持 | R 语言 | +| MATLAB | `.m` | `text/x-matlab` | ✅ 支持 | MATLAB 代码 | + +## 🎨 Web 开发文件 + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| --------- | ---------------- | ----------------------------------- | ---------- | ------------ | +| CSS | `.css` | `text/css` | ✅ 完全支持 | 样式表 | +| SCSS/Sass | `.scss`, `.sass` | `text/x-scss`, `text/x-sass` | ✅ 支持 | CSS 预处理器 | +| LESS | `.less` | `text/x-less` | ✅ 支持 | CSS 预处理器 | +| JSON | `.json` | `application/json` | ✅ 完全支持 | 数据交换格式 | +| YAML | `.yaml`, `.yml` | `text/yaml` 或 `application/x-yaml` | ✅ 支持 | 配置文件 | +| TOML | `.toml` | `application/toml` | ✅ 支持 | 配置文件 | +| Vue | `.vue` | `text/x-vue` | ✅ 支持 | Vue 组件 | +| Svelte | `.svelte` | `text/x-svelte` | ✅ 支持 | Svelte 组件 | + +## 🎵 音频文件(Audio Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---- | --------------- | ---------------------------- | ---------- | ------------ | +| MP3 | `.mp3` | `audio/mpeg` 或 `audio/mp3` | ✅ 完全支持 | 最常用格式 | +| WAV | `.wav` | `audio/wav` 或 `audio/x-wav` | ✅ 完全支持 | 无损格式 | +| AAC | `.aac` | `audio/aac` | ✅ 支持 | 高质量压缩 | +| M4A | `.m4a` | `audio/m4a` 或 `audio/mp4` | ✅ 支持 | Apple 格式 | +| OGG | `.ogg` | `audio/ogg` | ✅ 支持 | 开源格式 | +| FLAC | `.flac` | `audio/flac` | ✅ 支持 | 无损压缩 | +| AIFF | `.aiff`, `.aif` | `audio/aiff` | ✅ 支持 | Apple 格式 | +| WMA | `.wma` | `audio/x-ms-wma` | ✅ 支持 | Windows 格式 | +| OPUS | `.opus` | `audio/opus` | ✅ 支持 | 高效编码 | +| AMR | `.amr` | `audio/amr` | ✅ 支持 | 语音编码 | + +**音频功能**: +- 🎤 语音转文字(转录) +- 🗣️ 说话人识别 +- 🌍 语言识别 +- 😊 情感分析 +- 🎵 音乐分析 + +## 🎬 视频文件(Video Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---- | --------------- | ------------------ | ---------- | ------------ | +| MP4 | `.mp4` | `video/mp4` | ✅ 完全支持 | 最常用格式 | +| MOV | `.mov` | `video/quicktime` | ✅ 完全支持 | Apple 格式 | +| AVI | `.avi` | `video/x-msvideo` | ✅ 支持 | Windows 格式 | +| MPEG | `.mpeg`, `.mpg` | `video/mpeg` | ✅ 支持 | 标准格式 | +| WebM | `.webm` | `video/webm` | ✅ 支持 | 网页格式 | +| FLV | `.flv` | `video/x-flv` | ✅ 支持 | Flash 格式 | +| WMV | `.wmv` | `video/x-ms-wmv` | ✅ 支持 | Windows 格式 | +| MKV | `.mkv` | `video/x-matroska` | ✅ 支持 | 开源容器 | +| 3GPP | `.3gp`, `.3gpp` | `video/3gpp` | ✅ 支持 | 移动格式 | +| M4V | `.m4v` | `video/x-m4v` | ✅ 支持 | Apple 格式 | + +**视频功能**: +- 🎬 场景识别 +- 👤 人物检测 +- 🏷️ 对象识别 +- 📝 字幕生成 +- 🎯 动作识别 +- 📊 内容分析 + +## 📦 数据文件(Data Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ------- | ---------- | --------------------------- | ---------- | ----------- | +| JSON | `.json` | `application/json` | ✅ 完全支持 | 数据交换 | +| JSONL | `.jsonl` | `application/jsonlines` | ✅ 支持 | 行分隔 JSON | +| CSV | `.csv` | `text/csv` | ✅ 完全支持 | 表格数据 | +| TSV | `.tsv` | `text/tab-separated-values` | ✅ 支持 | 制表符分隔 | +| Parquet | `.parquet` | `application/x-parquet` | ⚠️ 可能支持 | 列式存储 | +| Avro | `.avro` | `application/avro` | ⚠️ 可能支持 | 数据序列化 | + +## 🗜️ 压缩文件(Archive Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---- | ------ | ------------------------------ | ---------- | -------- | +| ZIP | `.zip` | `application/zip` | ⚠️ 部分支持 | 需要解压 | +| RAR | `.rar` | `application/x-rar-compressed` | ❌ 不支持 | 需要解压 | +| 7Z | `.7z` | `application/x-7z-compressed` | ❌ 不支持 | 需要解压 | +| TAR | `.tar` | `application/x-tar` | ⚠️ 部分支持 | 需要解压 | +| GZ | `.gz` | `application/gzip` | ⚠️ 部分支持 | 需要解压 | + +## 🔧 配置文件(Configuration Files) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---------- | --------------- | ------------------ | ---------- | --------- | +| YAML | `.yaml`, `.yml` | `text/yaml` | ✅ 完全支持 | 配置文件 | +| TOML | `.toml` | `application/toml` | ✅ 支持 | 配置文件 | +| INI | `.ini` | `text/plain` | ✅ 支持 | 配置文件 | +| ENV | `.env` | `text/plain` | ✅ 支持 | 环境变量 | +| Properties | `.properties` | `text/plain` | ✅ 支持 | Java 配置 | + +## 📚 电子书格式(E-book Formats) + +| 格式 | 扩展名 | MIME 类型 | 支持状态 | 说明 | +| ---- | ------- | -------------------------------- | ---------- | ----------- | +| EPUB | `.epub` | `application/epub+zip` | ⚠️ 可能支持 | 电子书格式 | +| MOBI | `.mobi` | `application/x-mobipocket-ebook` | ⚠️ 可能支持 | Kindle 格式 | + +## 📊 文件大小限制 + +| 文件类型 | 推荐大小 | 最大大小 | 处理时间 | +| ----------- | -------- | -------- | ---------- | +| 图片 | < 5 MB | ~20 MB | 秒级 | +| PDF | < 10 MB | ~100 MB | 秒到分钟 | +| Office 文档 | < 10 MB | ~50 MB | 秒到分钟 | +| 文本/代码 | < 1 MB | ~10 MB | 秒级 | +| 音频 | < 20 MB | ~100 MB | 分钟级 | +| 视频 | < 100 MB | ~2 GB | 分钟到小时 | + +## 🎯 使用示例 + +### 1. 图片文件 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "描述这张图片"}, + {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."}} + ] + }] + }' +``` + +### 2. PDF 文档 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "总结这个PDF的主要内容"}, + {"type": "image_url", "image_url": {"url": "https://example.com/report.pdf"}} + ] + }] + }' +``` + +### 3. Office 文档 + +```bash +# Word 文档 +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "总结这个Word文档的内容"}, + {"type": "image_url", "image_url": {"url": "https://example.com/document.docx"}} + ] + }] + }' + +# Excel 表格 +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "分析这个Excel表格的数据"}, + {"type": "image_url", "image_url": {"url": "https://example.com/data.xlsx"}} + ] + }] + }' + +# PowerPoint 演示文稿 +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "总结这个PPT的主要内容"}, + {"type": "image_url", "image_url": {"url": "https://example.com/presentation.pptx"}} + ] + }] + }' +``` + +### 4. 音频文件 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "转录这段音频并总结内容"}, + {"type": "image_url", "image_url": {"url": "https://example.com/audio.mp3"}} + ] + }] + }' +``` + +### 5. 视频文件 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "描述这个视频的主要场景"}, + {"type": "image_url", "image_url": {"url": "https://example.com/video.mp4"}} + ] + }] + }' +``` + +### 6. 代码文件 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "审查这段代码并提出改进建议"}, + {"type": "image_url", "image_url": {"url": "data:text/x-python;base64,ZGVmIGhlbGxvKCk6..."}} + ] + }] + }' +``` + +### 7. 混合多种文件 + +```bash +curl -X POST http://localhost:7860/v1/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your_api_key" \ + -d '{ + "model": "gemini-2.5-pro", + "messages": [{ + "role": "user", + "content": [ + {"type": "text", "text": "比较这些文件的内容"}, + {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}, + {"type": "image_url", "image_url": {"url": "https://example.com/document.pdf"}}, + {"type": "image_url", "image_url": {"url": "https://example.com/audio.mp3"}} + ] + }] + }' +``` + +## ⚠️ 重要说明 + +1. **实际支持范围**:Google Gemini API 的实际支持范围可能比官方文档更广,建议实际测试 +2. **MIME 类型**:必须正确指定 MIME 类型,否则可能处理失败 +3. **文件大小**:超大文件可能导致超时或处理失败 +4. **处理质量**:不同文件类型的处理质量可能不同 +5. **API 版本**:支持的文件类型可能随 API 版本变化 +6. **字段名称**:虽然支持所有文件类型,但仍使用 `image_url` 字段(OpenAI API 标准) + +## 📝 支持状态说明 + +- ✅ **完全支持**:经过充分测试,稳定可用 +- ✅ **支持**:可以使用,但可能有限制 +- ⚠️ **可能支持**:理论上支持,需要实际测试 +- ⚠️ **部分支持**:有条件支持,可能需要特殊处理 +- ❌ **不支持**:当前不支持或需要转换 + +## 🔗 相关链接 + +- [项目主页](https://github.com/Dreamy-rain/gemini-business2api) +- [API 文档](README.md) +- [问题反馈](https://github.com/Dreamy-rain/gemini-business2api/issues) diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..bbe134fc4537151d368c561e9e54cd4baba11e88 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/script/download.js b/docs/script/download.js new file mode 100644 index 0000000000000000000000000000000000000000..f2a256f497951f73c1d5b263da5ddf4046bbe8d9 --- /dev/null +++ b/docs/script/download.js @@ -0,0 +1,110 @@ + // ==UserScript== + // @name Gemini Business Helper + // @version 2.0 + // @description 自动下载配置 + // @match https://business.gemini.google/* + // @grant GM_addStyle + // @grant GM_cookie + // ==/UserScript== + + (function() { + 'use strict'; + + GM_addStyle(` + #gb-btn { + position: fixed; + bottom: 32px; + right: 32px; + width: 60px; + height: 60px; + background: #1a73e8; + border-radius: 50%; + box-shadow: 0 4px 16px rgba(0,0,0,0.2); + cursor: pointer; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + transition: all 0.2s; + } + #gb-btn:hover { + transform: scale(1.1); + background: #1557b0; + } + `); + + const btn = document.createElement('div'); + btn.id = 'gb-btn'; + btn.textContent = '⬇'; + btn.title = '下载配置'; + document.body.appendChild(btn); + + const formatTime = (ts) => { + if (!ts) return null; + const d = new Date((ts - 43200) * 1000); + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`; + }; + + const download = (data, filename) => { + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + btn.onclick = () => { + const pathParts = window.location.pathname.split('/'); + const cidIndex = pathParts.indexOf('cid'); + const config_id = (cidIndex !== -1 && pathParts[cidIndex + 1]) || null; + const csesidx = new URLSearchParams(window.location.search).get('csesidx'); + + let email = localStorage.getItem('gemini_user_email'); + if (!email) { + email = prompt('请输入您的邮箱地址:'); + if (email) { + localStorage.setItem('gemini_user_email', email); + } + } + + GM_cookie('list', {}, (cookies, error) => { + if (error || !config_id || !csesidx || !email) { + alert('❌ 数据不完整'); + return; + } + + const host_c_oses = (cookies.find(c => c.name === '__Host-C_OSES') || {}).value || null; + const sesCookie = cookies.find(c => c.name === '__Secure-C_SES') || {}; + const secure_c_ses = sesCookie.value || null; + + if (!secure_c_ses) { + alert('❌ Cookie 读取失败'); + return; + } + + const data = { + id: email, + csesidx, + config_id, + secure_c_ses, + host_c_oses, + expires_at: formatTime(sesCookie.expirationDate) + }; + + download(JSON.stringify(data, null, 2), `${email}.json`); + + btn.textContent = '✓'; + btn.style.background = '#1e8e3e'; + setTimeout(() => { + btn.textContent = '⬇'; + btn.style.background = '#1a73e8'; + }, 1500); + }); + }; + })(); diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..4274e686d4a63295a0c58f85e431653c6557a825 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +# 启动 Xvfb 在后台 +Xvfb :99 -screen 0 1280x800x24 -ac & + +# 等待 Xvfb 启动 +sleep 1 + +# 设置 DISPLAY 环境变量 +export DISPLAY=:99 + +# 启动 Python 应用 +exec python -u main.py diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a547bf36d8d11a4f89c59c144f24795749086dd1 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000000000000000000000000000000000000..53d8b890b0dc6d67fadcea9230c50610f9818a9c --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,71 @@ +# Frontend - Admin Panel + +Modern admin panel built with Vue 3 + TypeScript + Tailwind CSS. + +## Tech Stack + +- Vue 3 + TypeScript +- Vite +- Vue Router + Pinia +- Tailwind CSS +- Axios +- ECharts + +## Development + +```bash +# Install dependencies +npm install + +# Start dev server +npm run dev +``` + +Visit: http://localhost:5174 + +## Build + +```bash +# Build for production +npm run build + +# Preview build +npm run preview +``` + +Build output: `dist/` + +## Project Structure + +``` +src/ +├── api/ # API requests +├── components/ # UI components +├── views/ # Page components +├── stores/ # Pinia stores +├── router/ # Vue Router +└── types/ # TypeScript types +``` + +## Environment Variables + +Create `.env.local`: + +```bash +VITE_API_BASE_URL=http://localhost:7860 +``` + +## Docker Build + +The root `Dockerfile` automatically builds the frontend: + +```dockerfile +FROM node:20-alpine AS frontend-builder +WORKDIR /frontend +COPY frontend/package*.json ./ +RUN npm ci +COPY frontend/ ./ +RUN npm run build +``` + +Build artifacts are copied to `static/` directory. \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b7d84b8fd18176acd550af1d2ebbcf3387981d91 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + Gemini Business2API + + +
+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..ceec73730ae85651d0d3f47abc130517e0627c9c --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1628 @@ +{ + "name": "gemini-business2api", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "requires": { + "@babel/types": "^7.29.0" + } + }, + "@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + } + }, + "@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "dev": true, + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "dev": true, + "optional": true + }, + "@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, + "@iconify/vue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz", + "integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==", + "dev": true, + "requires": { + "@iconify/types": "^2.0.0" + } + }, + "@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, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@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 + }, + "@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==" + }, + "@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, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==" + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "@vitejs/plugin-vue": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", + "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", + "requires": { + "@rolldown/pluginutils": "1.0.0-rc.2" + } + }, + "@vue/compiler-core": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "requires": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "@vue/compiler-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "requires": { + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "@vue/compiler-sfc": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "requires": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "@vue/compiler-ssr": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "requires": { + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "requires": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "requires": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "requires": { + "rfdc": "^1.4.1" + } + }, + "@vue/reactivity": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "requires": { + "@vue/shared": "3.5.29" + } + }, + "@vue/runtime-core": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "requires": { + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "@vue/runtime-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "requires": { + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" + } + }, + "@vue/server-renderer": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "requires": { + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "@vue/shared": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "requires": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + } + }, + "axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "requires": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==" + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "requires": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "dev": true + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, + "requires": { + "clsx": "^2.1.1" + } + }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "requires": { + "is-what": "^5.2.0" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true + }, + "entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" + }, + "form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, + "fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==" + }, + "jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true + }, + "lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" + }, + "node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "requires": { + "@vue/devtools-api": "^7.7.7" + } + }, + "pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true + }, + "postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "requires": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "requires": { + "lilconfig": "^3.1.1" + } + }, + "postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.1.1" + } + }, + "postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "requires": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true + }, + "rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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==" + }, + "speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==" + }, + "sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + } + }, + "superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "requires": { + "copy-anything": "^4" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "dev": true + }, + "tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + } + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true + }, + "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, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "requires": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "fsevents": "~2.3.3", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "dependencies": { + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } + } + }, + "vue": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "requires": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "vue-observe-visibility": { + "version": "2.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz", + "integrity": "sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==" + }, + "vue-resize": { + "version": "2.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", + "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==" + }, + "vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "requires": { + "@vue/devtools-api": "^6.6.4" + }, + "dependencies": { + "@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" + } + } + }, + "vue-virtual-scroller": { + "version": "2.0.0-beta.8", + "resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz", + "integrity": "sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==", + "requires": { + "mitt": "^2.1.0", + "vue-observe-visibility": "^2.0.0-alpha.1", + "vue-resize": "^2.0.0-alpha.1" + }, + "dependencies": { + "mitt": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz", + "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==" + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fa513cff1404db77da0cc2c15e0036521605935d --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "gemini-business2api", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@iconify/vue": "^5.0.0", + "autoprefixer": "^10.4.23", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "postcss": "^8.5.6", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^3.4.19", + "typescript": "~5.9.3", + "vite": "^7.2.4" + }, + "dependencies": { + "@vitejs/plugin-vue": "^6.0.3", + "axios": "^1.13.2", + "pinia": "^3.0.4", + "vue": "^3.5.26", + "vue-router": "^4.6.4", + "vue-virtual-scroller": "^2.0.0-beta.8" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..2e7af2b7f1a6f391da1631d93968a9d487ba977d --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..bbe134fc4537151d368c561e9e54cd4baba11e88 --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/vendor/echarts/echarts.min.js b/frontend/public/vendor/echarts/echarts.min.js new file mode 100644 index 0000000000000000000000000000000000000000..a3e980511e0ff07a7d6194ae520dafb2ee014e44 --- /dev/null +++ b/frontend/public/vendor/echarts/echarts.min.js @@ -0,0 +1,45 @@ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).echarts={})}(this,(function(t){"use strict"; +/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},e(t,n)};function n(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function i(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(i.prototype=n.prototype,new i)}var i=function(){this.firefox=!1,this.ie=!1,this.edge=!1,this.newEdge=!1,this.weChat=!1},r=new function(){this.browser=new i,this.node=!1,this.wxa=!1,this.worker=!1,this.svgSupported=!1,this.touchEventsSupported=!1,this.pointerEventsSupported=!1,this.domSupported=!1,this.transformSupported=!1,this.transform3dSupported=!1,this.hasGlobalWindow="undefined"!=typeof window};"object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?(r.wxa=!0,r.touchEventsSupported=!0):"undefined"==typeof document&&"undefined"!=typeof self?r.worker=!0:!r.hasGlobalWindow||"Deno"in window?(r.node=!0,r.svgSupported=!0):function(t,e){var n=e.browser,i=t.match(/Firefox\/([\d.]+)/),r=t.match(/MSIE\s([\d.]+)/)||t.match(/Trident\/.+?rv:(([\d.]+))/),o=t.match(/Edge?\/([\d.]+)/),a=/micromessenger/i.test(t);i&&(n.firefox=!0,n.version=i[1]);r&&(n.ie=!0,n.version=r[1]);o&&(n.edge=!0,n.version=o[1],n.newEdge=+o[1].split(".")[0]>18);a&&(n.weChat=!0);e.svgSupported="undefined"!=typeof SVGRect,e.touchEventsSupported="ontouchstart"in window&&!n.ie&&!n.edge,e.pointerEventsSupported="onpointerdown"in window&&(n.edge||n.ie&&+n.version>=11),e.domSupported="undefined"!=typeof document;var s=document.documentElement.style;e.transform3dSupported=(n.ie&&"transition"in s||n.edge||"WebKitCSSMatrix"in window&&"m11"in new WebKitCSSMatrix||"MozPerspective"in s)&&!("OTransition"in s),e.transformSupported=e.transform3dSupported||n.ie&&+n.version>=9}(navigator.userAgent,r);var o="sans-serif",a="12px "+o;var s,l,u=function(t){var e={};if("undefined"==typeof JSON)return e;for(var n=0;n=0)o=r*t.length;else for(var c=0;c>1)%2;a.style.cssText=["position: absolute","visibility: hidden","padding: 0","margin: 0","border-width: 0","user-select: none","width:0","height:0",i[s]+":0",r[l]+":0",i[1-s]+":auto",r[1-l]+":auto",""].join("!important;"),t.appendChild(a),n.push(a)}return n}(e,a),l=function(t,e,n){for(var i=n?"invTrans":"trans",r=e[i],o=e.srcCoords,a=[],s=[],l=!0,u=0;u<4;u++){var h=t[u].getBoundingClientRect(),c=2*u,p=h.left,d=h.top;a.push(p,d),l=l&&o&&p===o[c]&&d===o[c+1],s.push(t[u].offsetLeft,t[u].offsetTop)}return l&&r?r:(e.srcCoords=a,e[i]=n?$t(s,a):$t(a,s))}(s,a,o);if(l)return l(t,n,i),!0}return!1}function ee(t){return"CANVAS"===t.nodeName.toUpperCase()}var ne=/([&<>"'])/g,ie={"&":"&","<":"<",">":">",'"':""","'":"'"};function re(t){return null==t?"":(t+"").replace(ne,(function(t,e){return ie[e]}))}var oe=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ae=[],se=r.browser.firefox&&+r.browser.version.split(".")[0]<39;function le(t,e,n,i){return n=n||{},i?ue(t,e,n):se&&null!=e.layerX&&e.layerX!==e.offsetX?(n.zrX=e.layerX,n.zrY=e.layerY):null!=e.offsetX?(n.zrX=e.offsetX,n.zrY=e.offsetY):ue(t,e,n),n}function ue(t,e,n){if(r.domSupported&&t.getBoundingClientRect){var i=e.clientX,o=e.clientY;if(ee(t)){var a=t.getBoundingClientRect();return n.zrX=i-a.left,void(n.zrY=o-a.top)}if(te(ae,t,i,o))return n.zrX=ae[0],void(n.zrY=ae[1])}n.zrX=n.zrY=0}function he(t){return t||window.event}function ce(t,e,n){if(null!=(e=he(e)).zrX)return e;var i=e.type;if(i&&i.indexOf("touch")>=0){var r="touchend"!==i?e.targetTouches[0]:e.changedTouches[0];r&&le(t,r,e,n)}else{le(t,e,e,n);var o=function(t){var e=t.wheelDelta;if(e)return e;var n=t.deltaX,i=t.deltaY;if(null==n||null==i)return e;return 3*(0!==i?Math.abs(i):Math.abs(n))*(i>0?-1:i<0?1:n>0?-1:1)}(e);e.zrDelta=o?o/120:-(e.detail||0)/3}var a=e.button;return null==e.which&&void 0!==a&&oe.test(e.type)&&(e.which=1&a?1:2&a?3:4&a?2:0),e}function pe(t,e,n,i){t.addEventListener(e,n,i)}var de=function(t){t.preventDefault(),t.stopPropagation(),t.cancelBubble=!0};function fe(t){return 2===t.which||3===t.which}var ge=function(){function t(){this._track=[]}return t.prototype.recognize=function(t,e,n){return this._doTrack(t,e,n),this._recognize(t)},t.prototype.clear=function(){return this._track.length=0,this},t.prototype._doTrack=function(t,e,n){var i=t.touches;if(i){for(var r={points:[],touches:[],target:e,event:t},o=0,a=i.length;o1&&r&&r.length>1){var a=ye(r)/ye(o);!isFinite(a)&&(a=1),e.pinchScale=a;var s=[((i=r)[0][0]+i[1][0])/2,(i[0][1]+i[1][1])/2];return e.pinchX=s[0],e.pinchY=s[1],{type:"pinch",target:t[0].target,event:e}}}}};function me(){return[1,0,0,1,0,0]}function xe(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t}function _e(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function be(t,e,n){var i=e[0]*n[0]+e[2]*n[1],r=e[1]*n[0]+e[3]*n[1],o=e[0]*n[2]+e[2]*n[3],a=e[1]*n[2]+e[3]*n[3],s=e[0]*n[4]+e[2]*n[5]+e[4],l=e[1]*n[4]+e[3]*n[5]+e[5];return t[0]=i,t[1]=r,t[2]=o,t[3]=a,t[4]=s,t[5]=l,t}function we(t,e,n){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+n[0],t[5]=e[5]+n[1],t}function Se(t,e,n,i){void 0===i&&(i=[0,0]);var r=e[0],o=e[2],a=e[4],s=e[1],l=e[3],u=e[5],h=Math.sin(n),c=Math.cos(n);return t[0]=r*c+s*h,t[1]=-r*h+s*c,t[2]=o*c+l*h,t[3]=-o*h+c*l,t[4]=c*(a-i[0])+h*(u-i[1])+i[0],t[5]=c*(u-i[1])-h*(a-i[0])+i[1],t}function Me(t,e,n){var i=n[0],r=n[1];return t[0]=e[0]*i,t[1]=e[1]*r,t[2]=e[2]*i,t[3]=e[3]*r,t[4]=e[4]*i,t[5]=e[5]*r,t}function Ie(t,e){var n=e[0],i=e[2],r=e[4],o=e[1],a=e[3],s=e[5],l=n*a-o*i;return l?(l=1/l,t[0]=a*l,t[1]=-o*l,t[2]=-i*l,t[3]=n*l,t[4]=(i*s-a*r)*l,t[5]=(o*r-n*s)*l,t):null}function Te(t){var e=[1,0,0,1,0,0];return _e(e,t),e}var Ce=Object.freeze({__proto__:null,create:me,identity:xe,copy:_e,mul:be,translate:we,rotate:Se,scale:Me,invert:Ie,clone:Te}),De=function(){function t(t,e){this.x=t||0,this.y=e||0}return t.prototype.copy=function(t){return this.x=t.x,this.y=t.y,this},t.prototype.clone=function(){return new t(this.x,this.y)},t.prototype.set=function(t,e){return this.x=t,this.y=e,this},t.prototype.equal=function(t){return t.x===this.x&&t.y===this.y},t.prototype.add=function(t){return this.x+=t.x,this.y+=t.y,this},t.prototype.scale=function(t){this.x*=t,this.y*=t},t.prototype.scaleAndAdd=function(t,e){this.x+=t.x*e,this.y+=t.y*e},t.prototype.sub=function(t){return this.x-=t.x,this.y-=t.y,this},t.prototype.dot=function(t){return this.x*t.x+this.y*t.y},t.prototype.len=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},t.prototype.lenSquare=function(){return this.x*this.x+this.y*this.y},t.prototype.normalize=function(){var t=this.len();return this.x/=t,this.y/=t,this},t.prototype.distance=function(t){var e=this.x-t.x,n=this.y-t.y;return Math.sqrt(e*e+n*n)},t.prototype.distanceSquare=function(t){var e=this.x-t.x,n=this.y-t.y;return e*e+n*n},t.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},t.prototype.transform=function(t){if(t){var e=this.x,n=this.y;return this.x=t[0]*e+t[2]*n+t[4],this.y=t[1]*e+t[3]*n+t[5],this}},t.prototype.toArray=function(t){return t[0]=this.x,t[1]=this.y,t},t.prototype.fromArray=function(t){this.x=t[0],this.y=t[1]},t.set=function(t,e,n){t.x=e,t.y=n},t.copy=function(t,e){t.x=e.x,t.y=e.y},t.len=function(t){return Math.sqrt(t.x*t.x+t.y*t.y)},t.lenSquare=function(t){return t.x*t.x+t.y*t.y},t.dot=function(t,e){return t.x*e.x+t.y*e.y},t.add=function(t,e,n){t.x=e.x+n.x,t.y=e.y+n.y},t.sub=function(t,e,n){t.x=e.x-n.x,t.y=e.y-n.y},t.scale=function(t,e,n){t.x=e.x*n,t.y=e.y*n},t.scaleAndAdd=function(t,e,n,i){t.x=e.x+n.x*i,t.y=e.y+n.y*i},t.lerp=function(t,e,n,i){var r=1-i;t.x=r*e.x+i*n.x,t.y=r*e.y+i*n.y},t}(),Ae=Math.min,ke=Math.max,Le=new De,Pe=new De,Oe=new De,Re=new De,Ne=new De,Ee=new De,ze=function(){function t(t,e,n,i){n<0&&(t+=n,n=-n),i<0&&(e+=i,i=-i),this.x=t,this.y=e,this.width=n,this.height=i}return t.prototype.union=function(t){var e=Ae(t.x,this.x),n=Ae(t.y,this.y);isFinite(this.x)&&isFinite(this.width)?this.width=ke(t.x+t.width,this.x+this.width)-e:this.width=t.width,isFinite(this.y)&&isFinite(this.height)?this.height=ke(t.y+t.height,this.y+this.height)-n:this.height=t.height,this.x=e,this.y=n},t.prototype.applyTransform=function(e){t.applyTransform(this,this,e)},t.prototype.calculateTransform=function(t){var e=this,n=t.width/e.width,i=t.height/e.height,r=[1,0,0,1,0,0];return we(r,r,[-e.x,-e.y]),Me(r,r,[n,i]),we(r,r,[t.x,t.y]),r},t.prototype.intersect=function(e,n){if(!e)return!1;e instanceof t||(e=t.create(e));var i=this,r=i.x,o=i.x+i.width,a=i.y,s=i.y+i.height,l=e.x,u=e.x+e.width,h=e.y,c=e.y+e.height,p=!(of&&(f=x,gf&&(f=_,v=n.x&&t<=n.x+n.width&&e>=n.y&&e<=n.y+n.height},t.prototype.clone=function(){return new t(this.x,this.y,this.width,this.height)},t.prototype.copy=function(e){t.copy(this,e)},t.prototype.plain=function(){return{x:this.x,y:this.y,width:this.width,height:this.height}},t.prototype.isFinite=function(){return isFinite(this.x)&&isFinite(this.y)&&isFinite(this.width)&&isFinite(this.height)},t.prototype.isZero=function(){return 0===this.width||0===this.height},t.create=function(e){return new t(e.x,e.y,e.width,e.height)},t.copy=function(t,e){t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height},t.applyTransform=function(e,n,i){if(i){if(i[1]<1e-5&&i[1]>-1e-5&&i[2]<1e-5&&i[2]>-1e-5){var r=i[0],o=i[3],a=i[4],s=i[5];return e.x=n.x*r+a,e.y=n.y*o+s,e.width=n.width*r,e.height=n.height*o,e.width<0&&(e.x+=e.width,e.width=-e.width),void(e.height<0&&(e.y+=e.height,e.height=-e.height))}Le.x=Oe.x=n.x,Le.y=Re.y=n.y,Pe.x=Re.x=n.x+n.width,Pe.y=Oe.y=n.y+n.height,Le.transform(i),Re.transform(i),Pe.transform(i),Oe.transform(i),e.x=Ae(Le.x,Pe.x,Oe.x,Re.x),e.y=Ae(Le.y,Pe.y,Oe.y,Re.y);var l=ke(Le.x,Pe.x,Oe.x,Re.x),u=ke(Le.y,Pe.y,Oe.y,Re.y);e.width=l-e.x,e.height=u-e.y}else e!==n&&t.copy(e,n)},t}(),Ve="silent";function Be(){de(this.event)}var Fe=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.handler=null,e}return n(e,t),e.prototype.dispose=function(){},e.prototype.setCursor=function(){},e}(jt),Ge=function(t,e){this.x=t,this.y=e},We=["click","dblclick","mousewheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],He=new ze(0,0,0,0),Ye=function(t){function e(e,n,i,r,o){var a=t.call(this)||this;return a._hovered=new Ge(0,0),a.storage=e,a.painter=n,a.painterRoot=r,a._pointerSize=o,i=i||new Fe,a.proxy=null,a.setHandlerProxy(i),a._draggingMgr=new Zt(a),a}return n(e,t),e.prototype.setHandlerProxy=function(t){this.proxy&&this.proxy.dispose(),t&&(E(We,(function(e){t.on&&t.on(e,this[e],this)}),this),t.handler=this),this.proxy=t},e.prototype.mousemove=function(t){var e=t.zrX,n=t.zrY,i=Ze(this,e,n),r=this._hovered,o=r.target;o&&!o.__zr&&(o=(r=this.findHover(r.x,r.y)).target);var a=this._hovered=i?new Ge(e,n):this.findHover(e,n),s=a.target,l=this.proxy;l.setCursor&&l.setCursor(s?s.cursor:"default"),o&&s!==o&&this.dispatchToElement(r,"mouseout",t),this.dispatchToElement(a,"mousemove",t),s&&s!==o&&this.dispatchToElement(a,"mouseover",t)},e.prototype.mouseout=function(t){var e=t.zrEventControl;"only_globalout"!==e&&this.dispatchToElement(this._hovered,"mouseout",t),"no_globalout"!==e&&this.trigger("globalout",{type:"globalout",event:t})},e.prototype.resize=function(){this._hovered=new Ge(0,0)},e.prototype.dispatch=function(t,e){var n=this[t];n&&n.call(this,e)},e.prototype.dispose=function(){this.proxy.dispose(),this.storage=null,this.proxy=null,this.painter=null},e.prototype.setCursorStyle=function(t){var e=this.proxy;e.setCursor&&e.setCursor(t)},e.prototype.dispatchToElement=function(t,e,n){var i=(t=t||{}).target;if(!i||!i.silent){for(var r="on"+e,o=function(t,e,n){return{type:t,event:n,target:e.target,topTarget:e.topTarget,cancelBubble:!1,offsetX:n.zrX,offsetY:n.zrY,gestureEvent:n.gestureEvent,pinchX:n.pinchX,pinchY:n.pinchY,pinchScale:n.pinchScale,wheelDelta:n.zrDelta,zrByTouch:n.zrByTouch,which:n.which,stop:Be}}(e,t,n);i&&(i[r]&&(o.cancelBubble=!!i[r].call(i,o)),i.trigger(e,o),i=i.__hostTarget?i.__hostTarget:i.parent,!o.cancelBubble););o.cancelBubble||(this.trigger(e,o),this.painter&&this.painter.eachOtherLayer&&this.painter.eachOtherLayer((function(t){"function"==typeof t[r]&&t[r].call(t,o),t.trigger&&t.trigger(e,o)})))}},e.prototype.findHover=function(t,e,n){var i=this.storage.getDisplayList(),r=new Ge(t,e);if(Ue(i,r,t,e,n),this._pointerSize&&!r.target){for(var o=[],a=this._pointerSize,s=a/2,l=new ze(t-s,e-s,a,a),u=i.length-1;u>=0;u--){var h=i[u];h===n||h.ignore||h.ignoreCoarsePointer||h.parent&&h.parent.ignoreCoarsePointer||(He.copy(h.getBoundingRect()),h.transform&&He.applyTransform(h.transform),He.intersect(l)&&o.push(h))}if(o.length)for(var c=Math.PI/12,p=2*Math.PI,d=0;d=0;o--){var a=t[o],s=void 0;if(a!==r&&!a.ignore&&(s=Xe(a,n,i))&&(!e.topTarget&&(e.topTarget=a),s!==Ve)){e.target=a;break}}}function Ze(t,e,n){var i=t.painter;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}E(["click","mousedown","mouseup","mousewheel","dblclick","contextmenu"],(function(t){Ye.prototype[t]=function(e){var n,i,r=e.zrX,o=e.zrY,a=Ze(this,r,o);if("mouseup"===t&&a||(i=(n=this.findHover(r,o)).target),"mousedown"===t)this._downEl=i,this._downPoint=[e.zrX,e.zrY],this._upEl=i;else if("mouseup"===t)this._upEl=i;else if("click"===t){if(this._downEl!==this._upEl||!this._downPoint||Vt(this._downPoint,[e.zrX,e.zrY])>4)return;this._downPoint=null}this.dispatchToElement(n,t,e)}}));function je(t,e,n,i){var r=e+1;if(r===n)return 1;if(i(t[r++],t[e])<0){for(;r=0;)r++;return r-e}function qe(t,e,n,i,r){for(i===e&&i++;i>>1])<0?l=o:s=o+1;var u=i-s;switch(u){case 3:t[s+3]=t[s+2];case 2:t[s+2]=t[s+1];case 1:t[s+1]=t[s];break;default:for(;u>0;)t[s+u]=t[s+u-1],u--}t[s]=a}}function Ke(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])>0){for(s=i-r;l0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}else{for(s=r+1;ls&&(l=s);var u=a;a=r-l,l=r-u}for(a++;a>>1);o(t,e[n+h])>0?a=h+1:l=h}return l}function $e(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])<0){for(s=r+1;ls&&(l=s);var u=a;a=r-l,l=r-u}else{for(s=i-r;l=0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}for(a++;a>>1);o(t,e[n+h])<0?l=h:a=h+1}return l}function Je(t,e){var n,i,r=7,o=0,a=[];function s(s){var l=n[s],u=i[s],h=n[s+1],c=i[s+1];i[s]=u+c,s===o-3&&(n[s+1]=n[s+2],i[s+1]=i[s+2]),o--;var p=$e(t[h],t,l,u,0,e);l+=p,0!==(u-=p)&&0!==(c=Ke(t[l+u-1],t,h,c,c-1,e))&&(u<=c?function(n,i,o,s){var l=0;for(l=0;l=7||d>=7);if(f)break;g<0&&(g=0),g+=2}if((r=g)<1&&(r=1),1===i){for(l=0;l=0;l--)t[d+l]=t[p+l];return void(t[c]=a[h])}var f=r;for(;;){var g=0,y=0,v=!1;do{if(e(a[h],t[u])<0){if(t[c--]=t[u--],g++,y=0,0==--i){v=!0;break}}else if(t[c--]=a[h--],y++,g=0,1==--s){v=!0;break}}while((g|y)=0;l--)t[d+l]=t[p+l];if(0===i){v=!0;break}}if(t[c--]=a[h--],1==--s){v=!0;break}if(0!==(y=s-Ke(t[u],a,0,s,s-1,e))){for(s-=y,d=(c-=y)+1,p=(h-=y)+1,l=0;l=7||y>=7);if(v)break;f<0&&(f=0),f+=2}(r=f)<1&&(r=1);if(1===s){for(d=(c-=i)+1,p=(u-=i)+1,l=i-1;l>=0;l--)t[d+l]=t[p+l];t[c]=a[h]}else{if(0===s)throw new Error;for(p=c-(s-1),l=0;l1;){var t=o-2;if(t>=1&&i[t-1]<=i[t]+i[t+1]||t>=2&&i[t-2]<=i[t]+i[t-1])i[t-1]i[t+1])break;s(t)}},forceMergeRuns:function(){for(;o>1;){var t=o-2;t>0&&i[t-1]=32;)e|=1&t,t>>=1;return t+e}(r);do{if((o=je(t,n,i,e))s&&(l=s),qe(t,n,n+l,n+o,e),o=l}a.pushRun(n,o),a.mergeRuns(),r-=o,n+=o}while(0!==r);a.forceMergeRuns()}}}var tn=!1;function en(){tn||(tn=!0,console.warn("z / z2 / zlevel of displayable is invalid, which may cause unexpected errors"))}function nn(t,e){return t.zlevel===e.zlevel?t.z===e.z?t.z2-e.z2:t.z-e.z:t.zlevel-e.zlevel}var rn=function(){function t(){this._roots=[],this._displayList=[],this._displayListLen=0,this.displayableSortFunc=nn}return t.prototype.traverse=function(t,e){for(var n=0;n0&&(u.__clipPaths=[]),isNaN(u.z)&&(en(),u.z=0),isNaN(u.z2)&&(en(),u.z2=0),isNaN(u.zlevel)&&(en(),u.zlevel=0),this._displayList[this._displayListLen++]=u}var h=t.getDecalElement&&t.getDecalElement();h&&this._updateAndAddDisplayable(h,e,n);var c=t.getTextGuideLine();c&&this._updateAndAddDisplayable(c,e,n);var p=t.getTextContent();p&&this._updateAndAddDisplayable(p,e,n)}},t.prototype.addRoot=function(t){t.__zr&&t.__zr.storage===this||this._roots.push(t)},t.prototype.delRoot=function(t){if(t instanceof Array)for(var e=0,n=t.length;e=0&&this._roots.splice(i,1)}},t.prototype.delAllRoots=function(){this._roots=[],this._displayList=[],this._displayListLen=0},t.prototype.getRoots=function(){return this._roots},t.prototype.dispose=function(){this._displayList=null,this._roots=null},t}(),on=r.hasGlobalWindow&&(window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.msRequestAnimationFrame&&window.msRequestAnimationFrame.bind(window)||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame)||function(t){return setTimeout(t,16)},an={linear:function(t){return t},quadraticIn:function(t){return t*t},quadraticOut:function(t){return t*(2-t)},quadraticInOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)},cubicIn:function(t){return t*t*t},cubicOut:function(t){return--t*t*t+1},cubicInOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},quarticIn:function(t){return t*t*t*t},quarticOut:function(t){return 1- --t*t*t*t},quarticInOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},quinticIn:function(t){return t*t*t*t*t},quinticOut:function(t){return--t*t*t*t*t+1},quinticInOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},sinusoidalIn:function(t){return 1-Math.cos(t*Math.PI/2)},sinusoidalOut:function(t){return Math.sin(t*Math.PI/2)},sinusoidalInOut:function(t){return.5*(1-Math.cos(Math.PI*t))},exponentialIn:function(t){return 0===t?0:Math.pow(1024,t-1)},exponentialOut:function(t){return 1===t?1:1-Math.pow(2,-10*t)},exponentialInOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(2-Math.pow(2,-10*(t-1)))},circularIn:function(t){return 1-Math.sqrt(1-t*t)},circularOut:function(t){return Math.sqrt(1- --t*t)},circularInOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},elasticIn:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4))},elasticOut:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/.4)+1)},elasticInOut:function(t){var e,n=.1,i=.4;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=i*Math.asin(1/n)/(2*Math.PI),(t*=2)<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*.5+1)},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},backInOut:function(t){var e=2.5949095;return(t*=2)<1?t*t*((e+1)*t-e)*.5:.5*((t-=2)*t*((e+1)*t+e)+2)},bounceIn:function(t){return 1-an.bounceOut(1-t)},bounceOut:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},bounceInOut:function(t){return t<.5?.5*an.bounceIn(2*t):.5*an.bounceOut(2*t-1)+.5}},sn=Math.pow,ln=Math.sqrt,un=1e-8,hn=1e-4,cn=ln(3),pn=1/3,dn=Mt(),fn=Mt(),gn=Mt();function yn(t){return t>-1e-8&&tun||t<-1e-8}function mn(t,e,n,i,r){var o=1-r;return o*o*(o*t+3*r*e)+r*r*(r*i+3*o*n)}function xn(t,e,n,i,r){var o=1-r;return 3*(((e-t)*o+2*(n-e)*r)*o+(i-n)*r*r)}function _n(t,e,n,i,r,o){var a=i+3*(e-n)-t,s=3*(n-2*e+t),l=3*(e-t),u=t-r,h=s*s-3*a*l,c=s*l-9*a*u,p=l*l-3*s*u,d=0;if(yn(h)&&yn(c)){if(yn(s))o[0]=0;else(M=-l/s)>=0&&M<=1&&(o[d++]=M)}else{var f=c*c-4*h*p;if(yn(f)){var g=c/h,y=-g/2;(M=-s/a+g)>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y)}else if(f>0){var v=ln(f),m=h*s+1.5*a*(-c+v),x=h*s+1.5*a*(-c-v);(M=(-s-((m=m<0?-sn(-m,pn):sn(m,pn))+(x=x<0?-sn(-x,pn):sn(x,pn))))/(3*a))>=0&&M<=1&&(o[d++]=M)}else{var _=(2*h*s-3*a*c)/(2*ln(h*h*h)),b=Math.acos(_)/3,w=ln(h),S=Math.cos(b),M=(-s-2*w*S)/(3*a),I=(y=(-s+w*(S+cn*Math.sin(b)))/(3*a),(-s+w*(S-cn*Math.sin(b)))/(3*a));M>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y),I>=0&&I<=1&&(o[d++]=I)}}return d}function bn(t,e,n,i,r){var o=6*n-12*e+6*t,a=9*e+3*i-3*t-9*n,s=3*e-3*t,l=0;if(yn(a)){if(vn(o))(h=-s/o)>=0&&h<=1&&(r[l++]=h)}else{var u=o*o-4*a*s;if(yn(u))r[0]=-o/(2*a);else if(u>0){var h,c=ln(u),p=(-o-c)/(2*a);(h=(-o+c)/(2*a))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}function wn(t,e,n,i,r,o){var a=(e-t)*r+t,s=(n-e)*r+e,l=(i-n)*r+n,u=(s-a)*r+a,h=(l-s)*r+s,c=(h-u)*r+u;o[0]=t,o[1]=a,o[2]=u,o[3]=c,o[4]=c,o[5]=h,o[6]=l,o[7]=i}function Sn(t,e,n,i,r,o,a,s,l,u,h){var c,p,d,f,g,y=.005,v=1/0;dn[0]=l,dn[1]=u;for(var m=0;m<1;m+=.05)fn[0]=mn(t,n,r,a,m),fn[1]=mn(e,i,o,s,m),(f=Ft(dn,fn))=0&&f=0&&y=1?1:_n(0,i,o,1,t,s)&&mn(0,r,a,1,s[0])}}}var On=function(){function t(t){this._inited=!1,this._startTime=0,this._pausedTime=0,this._paused=!1,this._life=t.life||1e3,this._delay=t.delay||0,this.loop=t.loop||!1,this.onframe=t.onframe||bt,this.ondestroy=t.ondestroy||bt,this.onrestart=t.onrestart||bt,t.easing&&this.setEasing(t.easing)}return t.prototype.step=function(t,e){if(this._inited||(this._startTime=t+this._delay,this._inited=!0),!this._paused){var n=this._life,i=t-this._startTime-this._pausedTime,r=i/n;r<0&&(r=0),r=Math.min(r,1);var o=this.easingFunc,a=o?o(r):r;if(this.onframe(a),1===r){if(!this.loop)return!0;var s=i%n;this._startTime=t-s,this._pausedTime=0,this.onrestart()}return!1}this._pausedTime+=e},t.prototype.pause=function(){this._paused=!0},t.prototype.resume=function(){this._paused=!1},t.prototype.setEasing=function(t){this.easing=t,this.easingFunc=X(t)?t:an[t]||Pn(t)},t}(),Rn=function(t){this.value=t},Nn=function(){function t(){this._len=0}return t.prototype.insert=function(t){var e=new Rn(t);return this.insertEntry(e),e},t.prototype.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},t.prototype.remove=function(t){var e=t.prev,n=t.next;e?e.next=n:this.head=n,n?n.prev=e:this.tail=e,t.next=t.prev=null,this._len--},t.prototype.len=function(){return this._len},t.prototype.clear=function(){this.head=this.tail=null,this._len=0},t}(),En=function(){function t(t){this._list=new Nn,this._maxSize=10,this._map={},this._maxSize=t}return t.prototype.put=function(t,e){var n=this._list,i=this._map,r=null;if(null==i[t]){var o=n.len(),a=this._lastRemovedEntry;if(o>=this._maxSize&&o>0){var s=n.head;n.remove(s),delete i[s.key],r=s.value,this._lastRemovedEntry=s}a?a.value=e:a=new Rn(e),a.key=t,n.insertEntry(a),i[t]=a}return r},t.prototype.get=function(t){var e=this._map[t],n=this._list;if(null!=e)return e!==n.tail&&(n.remove(e),n.insertEntry(e)),e.value},t.prototype.clear=function(){this._list.clear(),this._map={}},t.prototype.len=function(){return this._list.len()},t}(),zn={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]};function Vn(t){return(t=Math.round(t))<0?0:t>255?255:t}function Bn(t){return t<0?0:t>1?1:t}function Fn(t){var e=t;return e.length&&"%"===e.charAt(e.length-1)?Vn(parseFloat(e)/100*255):Vn(parseInt(e,10))}function Gn(t){var e=t;return e.length&&"%"===e.charAt(e.length-1)?Bn(parseFloat(e)/100):Bn(parseFloat(e))}function Wn(t,e,n){return n<0?n+=1:n>1&&(n-=1),6*n<1?t+(e-t)*n*6:2*n<1?e:3*n<2?t+(e-t)*(2/3-n)*6:t}function Hn(t,e,n){return t+(e-t)*n}function Yn(t,e,n,i,r){return t[0]=e,t[1]=n,t[2]=i,t[3]=r,t}function Xn(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t}var Un=new En(20),Zn=null;function jn(t,e){Zn&&Xn(Zn,e),Zn=Un.put(t,Zn||e.slice())}function qn(t,e){if(t){e=e||[];var n=Un.get(t);if(n)return Xn(e,n);var i=(t+="").replace(/ /g,"").toLowerCase();if(i in zn)return Xn(e,zn[i]),jn(t,e),e;var r,o=i.length;if("#"===i.charAt(0))return 4===o||5===o?(r=parseInt(i.slice(1,4),16))>=0&&r<=4095?(Yn(e,(3840&r)>>4|(3840&r)>>8,240&r|(240&r)>>4,15&r|(15&r)<<4,5===o?parseInt(i.slice(4),16)/15:1),jn(t,e),e):void Yn(e,0,0,0,1):7===o||9===o?(r=parseInt(i.slice(1,7),16))>=0&&r<=16777215?(Yn(e,(16711680&r)>>16,(65280&r)>>8,255&r,9===o?parseInt(i.slice(7),16)/255:1),jn(t,e),e):void Yn(e,0,0,0,1):void 0;var a=i.indexOf("("),s=i.indexOf(")");if(-1!==a&&s+1===o){var l=i.substr(0,a),u=i.substr(a+1,s-(a+1)).split(","),h=1;switch(l){case"rgba":if(4!==u.length)return 3===u.length?Yn(e,+u[0],+u[1],+u[2],1):Yn(e,0,0,0,1);h=Gn(u.pop());case"rgb":return u.length>=3?(Yn(e,Fn(u[0]),Fn(u[1]),Fn(u[2]),3===u.length?h:Gn(u[3])),jn(t,e),e):void Yn(e,0,0,0,1);case"hsla":return 4!==u.length?void Yn(e,0,0,0,1):(u[3]=Gn(u[3]),Kn(u,e),jn(t,e),e);case"hsl":return 3!==u.length?void Yn(e,0,0,0,1):(Kn(u,e),jn(t,e),e);default:return}}Yn(e,0,0,0,1)}}function Kn(t,e){var n=(parseFloat(t[0])%360+360)%360/360,i=Gn(t[1]),r=Gn(t[2]),o=r<=.5?r*(i+1):r+i-r*i,a=2*r-o;return Yn(e=e||[],Vn(255*Wn(a,o,n+1/3)),Vn(255*Wn(a,o,n)),Vn(255*Wn(a,o,n-1/3)),1),4===t.length&&(e[3]=t[3]),e}function $n(t,e){var n=qn(t);if(n){for(var i=0;i<3;i++)n[i]=e<0?n[i]*(1-e)|0:(255-n[i])*e+n[i]|0,n[i]>255?n[i]=255:n[i]<0&&(n[i]=0);return ri(n,4===n.length?"rgba":"rgb")}}function Jn(t,e,n){if(e&&e.length&&t>=0&&t<=1){n=n||[];var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=e[r],s=e[o],l=i-r;return n[0]=Vn(Hn(a[0],s[0],l)),n[1]=Vn(Hn(a[1],s[1],l)),n[2]=Vn(Hn(a[2],s[2],l)),n[3]=Bn(Hn(a[3],s[3],l)),n}}var Qn=Jn;function ti(t,e,n){if(e&&e.length&&t>=0&&t<=1){var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=qn(e[r]),s=qn(e[o]),l=i-r,u=ri([Vn(Hn(a[0],s[0],l)),Vn(Hn(a[1],s[1],l)),Vn(Hn(a[2],s[2],l)),Bn(Hn(a[3],s[3],l))],"rgba");return n?{color:u,leftIndex:r,rightIndex:o,value:i}:u}}var ei=ti;function ni(t,e,n,i){var r=qn(t);if(t)return r=function(t){if(t){var e,n,i=t[0]/255,r=t[1]/255,o=t[2]/255,a=Math.min(i,r,o),s=Math.max(i,r,o),l=s-a,u=(s+a)/2;if(0===l)e=0,n=0;else{n=u<.5?l/(s+a):l/(2-s-a);var h=((s-i)/6+l/2)/l,c=((s-r)/6+l/2)/l,p=((s-o)/6+l/2)/l;i===s?e=p-c:r===s?e=1/3+h-p:o===s&&(e=2/3+c-h),e<0&&(e+=1),e>1&&(e-=1)}var d=[360*e,n,u];return null!=t[3]&&d.push(t[3]),d}}(r),null!=e&&(r[0]=function(t){return(t=Math.round(t))<0?0:t>360?360:t}(e)),null!=n&&(r[1]=Gn(n)),null!=i&&(r[2]=Gn(i)),ri(Kn(r),"rgba")}function ii(t,e){var n=qn(t);if(n&&null!=e)return n[3]=Bn(e),ri(n,"rgba")}function ri(t,e){if(t&&t.length){var n=t[0]+","+t[1]+","+t[2];return"rgba"!==e&&"hsva"!==e&&"hsla"!==e||(n+=","+t[3]),e+"("+n+")"}}function oi(t,e){var n=qn(t);return n?(.299*n[0]+.587*n[1]+.114*n[2])*n[3]/255+(1-n[3])*e:0}var ai=new En(100);function si(t){if(U(t)){var e=ai.get(t);return e||(e=$n(t,-.1),ai.put(t,e)),e}if(Q(t)){var n=A({},t);return n.colorStops=z(t.colorStops,(function(t){return{offset:t.offset,color:$n(t.color,-.1)}})),n}return t}var li=Object.freeze({__proto__:null,parse:qn,lift:$n,toHex:function(t){var e=qn(t);if(e)return((1<<24)+(e[0]<<16)+(e[1]<<8)+ +e[2]).toString(16).slice(1)},fastLerp:Jn,fastMapToColor:Qn,lerp:ti,mapToColor:ei,modifyHSL:ni,modifyAlpha:ii,stringify:ri,lum:oi,random:function(){return ri([Math.round(255*Math.random()),Math.round(255*Math.random()),Math.round(255*Math.random())],"rgb")},liftColor:si}),ui=Math.round;function hi(t){var e;if(t&&"transparent"!==t){if("string"==typeof t&&t.indexOf("rgba")>-1){var n=qn(t);n&&(t="rgb("+n[0]+","+n[1]+","+n[2]+")",e=n[3])}}else t="none";return{color:t,opacity:null==e?1:e}}var ci=1e-4;function pi(t){return t-1e-4}function di(t){return ui(1e3*t)/1e3}function fi(t){return ui(1e4*t)/1e4}var gi={left:"start",right:"end",center:"middle",middle:"middle"};function yi(t){return t&&!!t.image}function vi(t){return yi(t)||function(t){return t&&!!t.svgElement}(t)}function mi(t){return"linear"===t.type}function xi(t){return"radial"===t.type}function _i(t){return t&&("linear"===t.type||"radial"===t.type)}function bi(t){return"url(#"+t+")"}function wi(t){var e=t.getGlobalScale(),n=Math.max(e[0],e[1]);return Math.max(Math.ceil(Math.log(n)/Math.log(10)),1)}function Si(t){var e=t.x||0,n=t.y||0,i=(t.rotation||0)*wt,r=rt(t.scaleX,1),o=rt(t.scaleY,1),a=t.skewX||0,s=t.skewY||0,l=[];return(e||n)&&l.push("translate("+e+"px,"+n+"px)"),i&&l.push("rotate("+i+")"),1===r&&1===o||l.push("scale("+r+","+o+")"),(a||s)&&l.push("skew("+ui(a*wt)+"deg, "+ui(s*wt)+"deg)"),l.join(" ")}var Mi=r.hasGlobalWindow&&X(window.btoa)?function(t){return window.btoa(unescape(encodeURIComponent(t)))}:"undefined"!=typeof Buffer?function(t){return Buffer.from(t).toString("base64")}:function(t){return null},Ii=Array.prototype.slice;function Ti(t,e,n){return(e-t)*n+t}function Ci(t,e,n,i){for(var r=e.length,o=0;oi?e:t,o=Math.min(n,i),a=r[o-1]||{color:[0,0,0,0],offset:0},s=o;sa)i.length=a;else for(var s=o;s=1},t.prototype.getAdditiveTrack=function(){return this._additiveTrack},t.prototype.addKeyframe=function(t,e,n){this._needsSort=!0;var i=this.keyframes,r=i.length,o=!1,a=6,s=e;if(N(e)){var l=function(t){return N(t&&t[0])?2:1}(e);a=l,(1===l&&!j(e[0])||2===l&&!j(e[0][0]))&&(o=!0)}else if(j(e)&&!nt(e))a=0;else if(U(e))if(isNaN(+e)){var u=qn(e);u&&(s=u,a=3)}else a=0;else if(Q(e)){var h=A({},s);h.colorStops=z(e.colorStops,(function(t){return{offset:t.offset,color:qn(t.color)}})),mi(e)?a=4:xi(e)&&(a=5),s=h}0===r?this.valType=a:a===this.valType&&6!==a||(o=!0),this.discrete=this.discrete||o;var c={time:t,value:s,rawValue:e,percent:0};return n&&(c.easing=n,c.easingFunc=X(n)?n:an[n]||Pn(n)),i.push(c),c},t.prototype.prepare=function(t,e){var n=this.keyframes;this._needsSort&&n.sort((function(t,e){return t.time-e.time}));for(var i=this.valType,r=n.length,o=n[r-1],a=this.discrete,s=Ni(i),l=Ri(i),u=0;u=0&&!(l[n].percent<=e);n--);n=d(n,u-2)}else{for(n=p;ne);n++);n=d(n-1,u-2)}r=l[n+1],i=l[n]}if(i&&r){this._lastFr=n,this._lastFrP=e;var f=r.percent-i.percent,g=0===f?1:d((e-i.percent)/f,1);r.easingFunc&&(g=r.easingFunc(g));var y=o?this._additiveValue:c?Ei:t[h];if(!Ni(s)&&!c||y||(y=this._additiveValue=[]),this.discrete)t[h]=g<1?i.rawValue:r.rawValue;else if(Ni(s))1===s?Ci(y,i[a],r[a],g):function(t,e,n,i){for(var r=e.length,o=r&&e[0].length,a=0;a0&&s.addKeyframe(0,Pi(l),i),this._trackKeys.push(a)}s.addKeyframe(t,Pi(e[a]),i)}return this._maxTime=Math.max(this._maxTime,t),this},t.prototype.pause=function(){this._clip.pause(),this._paused=!0},t.prototype.resume=function(){this._clip.resume(),this._paused=!1},t.prototype.isPaused=function(){return!!this._paused},t.prototype.duration=function(t){return this._maxTime=t,this._force=!0,this},t.prototype._doneCallback=function(){this._setTracksFinished(),this._clip=null;var t=this._doneCbs;if(t)for(var e=t.length,n=0;n0)){this._started=1;for(var e=this,n=[],i=this._maxTime||0,r=0;r1){var a=o.pop();r.addKeyframe(a.time,t[i]),r.prepare(this._maxTime,r.getAdditiveTrack())}}}},t}();function Bi(){return(new Date).getTime()}var Fi,Gi,Wi=function(t){function e(e){var n=t.call(this)||this;return n._running=!1,n._time=0,n._pausedTime=0,n._pauseStart=0,n._paused=!1,e=e||{},n.stage=e.stage||{},n}return n(e,t),e.prototype.addClip=function(t){t.animation&&this.removeClip(t),this._head?(this._tail.next=t,t.prev=this._tail,t.next=null,this._tail=t):this._head=this._tail=t,t.animation=this},e.prototype.addAnimator=function(t){t.animation=this;var e=t.getClip();e&&this.addClip(e)},e.prototype.removeClip=function(t){if(t.animation){var e=t.prev,n=t.next;e?e.next=n:this._head=n,n?n.prev=e:this._tail=e,t.next=t.prev=t.animation=null}},e.prototype.removeAnimator=function(t){var e=t.getClip();e&&this.removeClip(e),t.animation=null},e.prototype.update=function(t){for(var e=Bi()-this._pausedTime,n=e-this._time,i=this._head;i;){var r=i.next;i.step(e,n)?(i.ondestroy(),this.removeClip(i),i=r):i=r}this._time=e,t||(this.trigger("frame",n),this.stage.update&&this.stage.update())},e.prototype._startLoop=function(){var t=this;this._running=!0,on((function e(){t._running&&(on(e),!t._paused&&t.update())}))},e.prototype.start=function(){this._running||(this._time=Bi(),this._pausedTime=0,this._startLoop())},e.prototype.stop=function(){this._running=!1},e.prototype.pause=function(){this._paused||(this._pauseStart=Bi(),this._paused=!0)},e.prototype.resume=function(){this._paused&&(this._pausedTime+=Bi()-this._pauseStart,this._paused=!1)},e.prototype.clear=function(){for(var t=this._head;t;){var e=t.next;t.prev=t.next=t.animation=null,t=e}this._head=this._tail=null},e.prototype.isFinished=function(){return null==this._head},e.prototype.animate=function(t,e){e=e||{},this.start();var n=new Vi(t,e.loop);return this.addAnimator(n),n},e}(jt),Hi=r.domSupported,Yi=(Gi={pointerdown:1,pointerup:1,pointermove:1,pointerout:1},{mouse:Fi=["click","dblclick","mousewheel","wheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],touch:["touchstart","touchend","touchmove"],pointer:z(Fi,(function(t){var e=t.replace("mouse","pointer");return Gi.hasOwnProperty(e)?e:t}))}),Xi=["mousemove","mouseup"],Ui=["pointermove","pointerup"],Zi=!1;function ji(t){var e=t.pointerType;return"pen"===e||"touch"===e}function qi(t){t&&(t.zrByTouch=!0)}function Ki(t,e){for(var n=e,i=!1;n&&9!==n.nodeType&&!(i=n.domBelongToZr||n!==e&&n===t.painterRoot);)n=n.parentNode;return i}var $i=function(t,e){this.stopPropagation=bt,this.stopImmediatePropagation=bt,this.preventDefault=bt,this.type=e.type,this.target=this.currentTarget=t.dom,this.pointerType=e.pointerType,this.clientX=e.clientX,this.clientY=e.clientY},Ji={mousedown:function(t){t=ce(this.dom,t),this.__mayPointerCapture=[t.zrX,t.zrY],this.trigger("mousedown",t)},mousemove:function(t){t=ce(this.dom,t);var e=this.__mayPointerCapture;!e||t.zrX===e[0]&&t.zrY===e[1]||this.__togglePointerCapture(!0),this.trigger("mousemove",t)},mouseup:function(t){t=ce(this.dom,t),this.__togglePointerCapture(!1),this.trigger("mouseup",t)},mouseout:function(t){Ki(this,(t=ce(this.dom,t)).toElement||t.relatedTarget)||(this.__pointerCapturing&&(t.zrEventControl="no_globalout"),this.trigger("mouseout",t))},wheel:function(t){Zi=!0,t=ce(this.dom,t),this.trigger("mousewheel",t)},mousewheel:function(t){Zi||(t=ce(this.dom,t),this.trigger("mousewheel",t))},touchstart:function(t){qi(t=ce(this.dom,t)),this.__lastTouchMoment=new Date,this.handler.processGesture(t,"start"),Ji.mousemove.call(this,t),Ji.mousedown.call(this,t)},touchmove:function(t){qi(t=ce(this.dom,t)),this.handler.processGesture(t,"change"),Ji.mousemove.call(this,t)},touchend:function(t){qi(t=ce(this.dom,t)),this.handler.processGesture(t,"end"),Ji.mouseup.call(this,t),+new Date-+this.__lastTouchMoment<300&&Ji.click.call(this,t)},pointerdown:function(t){Ji.mousedown.call(this,t)},pointermove:function(t){ji(t)||Ji.mousemove.call(this,t)},pointerup:function(t){Ji.mouseup.call(this,t)},pointerout:function(t){ji(t)||Ji.mouseout.call(this,t)}};E(["click","dblclick","contextmenu"],(function(t){Ji[t]=function(e){e=ce(this.dom,e),this.trigger(t,e)}}));var Qi={pointermove:function(t){ji(t)||Qi.mousemove.call(this,t)},pointerup:function(t){Qi.mouseup.call(this,t)},mousemove:function(t){this.trigger("mousemove",t)},mouseup:function(t){var e=this.__pointerCapturing;this.__togglePointerCapture(!1),this.trigger("mouseup",t),e&&(t.zrEventControl="only_globalout",this.trigger("mouseout",t))}};function tr(t,e){var n=e.domHandlers;r.pointerEventsSupported?E(Yi.pointer,(function(i){nr(e,i,(function(e){n[i].call(t,e)}))})):(r.touchEventsSupported&&E(Yi.touch,(function(i){nr(e,i,(function(r){n[i].call(t,r),function(t){t.touching=!0,null!=t.touchTimer&&(clearTimeout(t.touchTimer),t.touchTimer=null),t.touchTimer=setTimeout((function(){t.touching=!1,t.touchTimer=null}),700)}(e)}))})),E(Yi.mouse,(function(i){nr(e,i,(function(r){r=he(r),e.touching||n[i].call(t,r)}))})))}function er(t,e){function n(n){nr(e,n,(function(i){i=he(i),Ki(t,i.target)||(i=function(t,e){return ce(t.dom,new $i(t,e),!0)}(t,i),e.domHandlers[n].call(t,i))}),{capture:!0})}r.pointerEventsSupported?E(Ui,n):r.touchEventsSupported||E(Xi,n)}function nr(t,e,n,i){t.mounted[e]=n,t.listenerOpts[e]=i,pe(t.domTarget,e,n,i)}function ir(t){var e,n,i,r,o=t.mounted;for(var a in o)o.hasOwnProperty(a)&&(e=t.domTarget,n=a,i=o[a],r=t.listenerOpts[a],e.removeEventListener(n,i,r));t.mounted={}}var rr=function(t,e){this.mounted={},this.listenerOpts={},this.touching=!1,this.domTarget=t,this.domHandlers=e},or=function(t){function e(e,n){var i=t.call(this)||this;return i.__pointerCapturing=!1,i.dom=e,i.painterRoot=n,i._localHandlerScope=new rr(e,Ji),Hi&&(i._globalHandlerScope=new rr(document,Qi)),tr(i,i._localHandlerScope),i}return n(e,t),e.prototype.dispose=function(){ir(this._localHandlerScope),Hi&&ir(this._globalHandlerScope)},e.prototype.setCursor=function(t){this.dom.style&&(this.dom.style.cursor=t||"default")},e.prototype.__togglePointerCapture=function(t){if(this.__mayPointerCapture=null,Hi&&+this.__pointerCapturing^+t){this.__pointerCapturing=t;var e=this._globalHandlerScope;t?er(this,e):ir(e)}},e}(jt),ar=1;r.hasGlobalWindow&&(ar=Math.max(window.devicePixelRatio||window.screen&&window.screen.deviceXDPI/window.screen.logicalXDPI||1,1));var sr=ar,lr="#333",ur="#ccc",hr=xe,cr=5e-5;function pr(t){return t>cr||t<-5e-5}var dr=[],fr=[],gr=[1,0,0,1,0,0],yr=Math.abs,vr=function(){function t(){}return t.prototype.getLocalTransform=function(e){return t.getLocalTransform(this,e)},t.prototype.setPosition=function(t){this.x=t[0],this.y=t[1]},t.prototype.setScale=function(t){this.scaleX=t[0],this.scaleY=t[1]},t.prototype.setSkew=function(t){this.skewX=t[0],this.skewY=t[1]},t.prototype.setOrigin=function(t){this.originX=t[0],this.originY=t[1]},t.prototype.needLocalTransform=function(){return pr(this.rotation)||pr(this.x)||pr(this.y)||pr(this.scaleX-1)||pr(this.scaleY-1)||pr(this.skewX)||pr(this.skewY)},t.prototype.updateTransform=function(){var t=this.parent&&this.parent.transform,e=this.needLocalTransform(),n=this.transform;e||t?(n=n||[1,0,0,1,0,0],e?this.getLocalTransform(n):hr(n),t&&(e?be(n,t,n):_e(n,t)),this.transform=n,this._resolveGlobalScaleRatio(n)):n&&(hr(n),this.invTransform=null)},t.prototype._resolveGlobalScaleRatio=function(t){var e=this.globalScaleRatio;if(null!=e&&1!==e){this.getGlobalScale(dr);var n=dr[0]<0?-1:1,i=dr[1]<0?-1:1,r=((dr[0]-n)*e+n)/dr[0]||0,o=((dr[1]-i)*e+i)/dr[1]||0;t[0]*=r,t[1]*=r,t[2]*=o,t[3]*=o}this.invTransform=this.invTransform||[1,0,0,1,0,0],Ie(this.invTransform,t)},t.prototype.getComputedTransform=function(){for(var t=this,e=[];t;)e.push(t),t=t.parent;for(;t=e.pop();)t.updateTransform();return this.transform},t.prototype.setLocalTransform=function(t){if(t){var e=t[0]*t[0]+t[1]*t[1],n=t[2]*t[2]+t[3]*t[3],i=Math.atan2(t[1],t[0]),r=Math.PI/2+i-Math.atan2(t[3],t[2]);n=Math.sqrt(n)*Math.cos(r),e=Math.sqrt(e),this.skewX=r,this.skewY=0,this.rotation=-i,this.x=+t[4],this.y=+t[5],this.scaleX=e,this.scaleY=n,this.originX=0,this.originY=0}},t.prototype.decomposeTransform=function(){if(this.transform){var t=this.parent,e=this.transform;t&&t.transform&&(t.invTransform=t.invTransform||[1,0,0,1,0,0],be(fr,t.invTransform,e),e=fr);var n=this.originX,i=this.originY;(n||i)&&(gr[4]=n,gr[5]=i,be(fr,e,gr),fr[4]-=n,fr[5]-=i,e=fr),this.setLocalTransform(e)}},t.prototype.getGlobalScale=function(t){var e=this.transform;return t=t||[],e?(t[0]=Math.sqrt(e[0]*e[0]+e[1]*e[1]),t[1]=Math.sqrt(e[2]*e[2]+e[3]*e[3]),e[0]<0&&(t[0]=-t[0]),e[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},t.prototype.transformCoordToLocal=function(t,e){var n=[t,e],i=this.invTransform;return i&&Wt(n,n,i),n},t.prototype.transformCoordToGlobal=function(t,e){var n=[t,e],i=this.transform;return i&&Wt(n,n,i),n},t.prototype.getLineScale=function(){var t=this.transform;return t&&yr(t[0]-1)>1e-10&&yr(t[3]-1)>1e-10?Math.sqrt(yr(t[0]*t[3]-t[2]*t[1])):1},t.prototype.copyTransform=function(t){xr(this,t)},t.getLocalTransform=function(t,e){e=e||[];var n=t.originX||0,i=t.originY||0,r=t.scaleX,o=t.scaleY,a=t.anchorX,s=t.anchorY,l=t.rotation||0,u=t.x,h=t.y,c=t.skewX?Math.tan(t.skewX):0,p=t.skewY?Math.tan(-t.skewY):0;if(n||i||a||s){var d=n+a,f=i+s;e[4]=-d*r-c*f*o,e[5]=-f*o-p*d*r}else e[4]=e[5]=0;return e[0]=r,e[3]=o,e[1]=p*r,e[2]=c*o,l&&Se(e,e,l),e[4]+=n+u,e[5]+=i+h,e},t.initDefaultProps=function(){var e=t.prototype;e.scaleX=e.scaleY=e.globalScaleRatio=1,e.x=e.y=e.originX=e.originY=e.skewX=e.skewY=e.rotation=e.anchorX=e.anchorY=0}(),t}(),mr=["x","y","originX","originY","anchorX","anchorY","rotation","scaleX","scaleY","skewX","skewY"];function xr(t,e){for(var n=0;n=0?parseFloat(t)/100*e:parseFloat(t):t}function Dr(t,e,n){var i=e.position||"inside",r=null!=e.distance?e.distance:5,o=n.height,a=n.width,s=o/2,l=n.x,u=n.y,h="left",c="top";if(i instanceof Array)l+=Cr(i[0],n.width),u+=Cr(i[1],n.height),h=null,c=null;else switch(i){case"left":l-=r,u+=s,h="right",c="middle";break;case"right":l+=r+a,u+=s,c="middle";break;case"top":l+=a/2,u-=r,h="center",c="bottom";break;case"bottom":l+=a/2,u+=o+r,h="center";break;case"inside":l+=a/2,u+=s,h="center",c="middle";break;case"insideLeft":l+=r,u+=s,c="middle";break;case"insideRight":l+=a-r,u+=s,h="right",c="middle";break;case"insideTop":l+=a/2,u+=r,h="center";break;case"insideBottom":l+=a/2,u+=o-r,h="center",c="bottom";break;case"insideTopLeft":l+=r,u+=r;break;case"insideTopRight":l+=a-r,u+=r,h="right";break;case"insideBottomLeft":l+=r,u+=o-r,c="bottom";break;case"insideBottomRight":l+=a-r,u+=o-r,h="right",c="bottom"}return(t=t||{}).x=l,t.y=u,t.align=h,t.verticalAlign=c,t}var Ar="__zr_normal__",kr=mr.concat(["ignore"]),Lr=V(mr,(function(t,e){return t[e]=!0,t}),{ignore:!1}),Pr={},Or=new ze(0,0,0,0),Rr=function(){function t(t){this.id=M(),this.animators=[],this.currentStates=[],this.states={},this._init(t)}return t.prototype._init=function(t){this.attr(t)},t.prototype.drift=function(t,e,n){switch(this.draggable){case"horizontal":e=0;break;case"vertical":t=0}var i=this.transform;i||(i=this.transform=[1,0,0,1,0,0]),i[4]+=t,i[5]+=e,this.decomposeTransform(),this.markRedraw()},t.prototype.beforeUpdate=function(){},t.prototype.afterUpdate=function(){},t.prototype.update=function(){this.updateTransform(),this.__dirty&&this.updateInnerText()},t.prototype.updateInnerText=function(t){var e=this._textContent;if(e&&(!e.ignore||t)){this.textConfig||(this.textConfig={});var n=this.textConfig,i=n.local,r=e.innerTransformable,o=void 0,a=void 0,s=!1;r.parent=i?this:null;var l=!1;if(r.copyTransform(e),null!=n.position){var u=Or;n.layoutRect?u.copy(n.layoutRect):u.copy(this.getBoundingRect()),i||u.applyTransform(this.transform),this.calculateTextPosition?this.calculateTextPosition(Pr,n,u):Dr(Pr,n,u),r.x=Pr.x,r.y=Pr.y,o=Pr.align,a=Pr.verticalAlign;var h=n.origin;if(h&&null!=n.rotation){var c=void 0,p=void 0;"center"===h?(c=.5*u.width,p=.5*u.height):(c=Cr(h[0],u.width),p=Cr(h[1],u.height)),l=!0,r.originX=-r.x+c+(i?0:u.x),r.originY=-r.y+p+(i?0:u.y)}}null!=n.rotation&&(r.rotation=n.rotation);var d=n.offset;d&&(r.x+=d[0],r.y+=d[1],l||(r.originX=-d[0],r.originY=-d[1]));var f=null==n.inside?"string"==typeof n.position&&n.position.indexOf("inside")>=0:n.inside,g=this._innerTextDefaultStyle||(this._innerTextDefaultStyle={}),y=void 0,v=void 0,m=void 0;f&&this.canBeInsideText()?(y=n.insideFill,v=n.insideStroke,null!=y&&"auto"!==y||(y=this.getInsideTextFill()),null!=v&&"auto"!==v||(v=this.getInsideTextStroke(y),m=!0)):(y=n.outsideFill,v=n.outsideStroke,null!=y&&"auto"!==y||(y=this.getOutsideFill()),null!=v&&"auto"!==v||(v=this.getOutsideStroke(y),m=!0)),(y=y||"#000")===g.fill&&v===g.stroke&&m===g.autoStroke&&o===g.align&&a===g.verticalAlign||(s=!0,g.fill=y,g.stroke=v,g.autoStroke=m,g.align=o,g.verticalAlign=a,e.setDefaultTextStyle(g)),e.__dirty|=1,s&&e.dirtyStyle(!0)}},t.prototype.canBeInsideText=function(){return!0},t.prototype.getInsideTextFill=function(){return"#fff"},t.prototype.getInsideTextStroke=function(t){return"#000"},t.prototype.getOutsideFill=function(){return this.__zr&&this.__zr.isDarkMode()?ur:lr},t.prototype.getOutsideStroke=function(t){var e=this.__zr&&this.__zr.getBackgroundColor(),n="string"==typeof e&&qn(e);n||(n=[255,255,255,1]);for(var i=n[3],r=this.__zr.isDarkMode(),o=0;o<3;o++)n[o]=n[o]*i+(r?0:255)*(1-i);return n[3]=1,ri(n,"rgba")},t.prototype.traverse=function(t,e){},t.prototype.attrKV=function(t,e){"textConfig"===t?this.setTextConfig(e):"textContent"===t?this.setTextContent(e):"clipPath"===t?this.setClipPath(e):"extra"===t?(this.extra=this.extra||{},A(this.extra,e)):this[t]=e},t.prototype.hide=function(){this.ignore=!0,this.markRedraw()},t.prototype.show=function(){this.ignore=!1,this.markRedraw()},t.prototype.attr=function(t,e){if("string"==typeof t)this.attrKV(t,e);else if(q(t))for(var n=G(t),i=0;i0},t.prototype.getState=function(t){return this.states[t]},t.prototype.ensureState=function(t){var e=this.states;return e[t]||(e[t]={}),e[t]},t.prototype.clearStates=function(t){this.useState(Ar,!1,t)},t.prototype.useState=function(t,e,n,i){var r=t===Ar;if(this.hasState()||!r){var o=this.currentStates,a=this.stateTransition;if(!(P(o,t)>=0)||!e&&1!==o.length){var s;if(this.stateProxy&&!r&&(s=this.stateProxy(t)),s||(s=this.states&&this.states[t]),s||r){r||this.saveCurrentToNormalState(s);var l=!!(s&&s.hoverLayer||i);l&&this._toggleHoverLayerFlag(!0),this._applyStateObj(t,s,this._normalState,e,!n&&!this.__inHover&&a&&a.duration>0,a);var u=this._textContent,h=this._textGuide;return u&&u.useState(t,e,n,l),h&&h.useState(t,e,n,l),r?(this.currentStates=[],this._normalState={}):e?this.currentStates.push(t):this.currentStates=[t],this._updateAnimationTargets(),this.markRedraw(),!l&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2),s}I("State "+t+" not exists.")}}},t.prototype.useStates=function(t,e,n){if(t.length){var i=[],r=this.currentStates,o=t.length,a=o===r.length;if(a)for(var s=0;s0,d);var f=this._textContent,g=this._textGuide;f&&f.useStates(t,e,c),g&&g.useStates(t,e,c),this._updateAnimationTargets(),this.currentStates=t.slice(),this.markRedraw(),!c&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2)}else this.clearStates()},t.prototype.isSilent=function(){for(var t=this.silent,e=this.parent;!t&&e;){if(e.silent){t=!0;break}e=e.parent}return t},t.prototype._updateAnimationTargets=function(){for(var t=0;t=0){var n=this.currentStates.slice();n.splice(e,1),this.useStates(n)}},t.prototype.replaceState=function(t,e,n){var i=this.currentStates.slice(),r=P(i,t),o=P(i,e)>=0;r>=0?o?i.splice(r,1):i[r]=e:n&&!o&&i.push(e),this.useStates(i)},t.prototype.toggleState=function(t,e){e?this.useState(t,!0):this.removeState(t)},t.prototype._mergeStates=function(t){for(var e,n={},i=0;i=0&&e.splice(n,1)})),this.animators.push(t),n&&n.animation.addAnimator(t),n&&n.wakeUp()},t.prototype.updateDuringAnimation=function(t){this.markRedraw()},t.prototype.stopAnimation=function(t,e){for(var n=this.animators,i=n.length,r=[],o=0;o0&&n.during&&o[0].during((function(t,e){n.during(e)}));for(var p=0;p0||r.force&&!a.length){var w,S=void 0,M=void 0,I=void 0;if(s){M={},p&&(S={});for(_=0;_=0&&(n.splice(i,0,t),this._doAdd(t))}return this},e.prototype.replace=function(t,e){var n=P(this._children,t);return n>=0&&this.replaceAt(e,n),this},e.prototype.replaceAt=function(t,e){var n=this._children,i=n[e];if(t&&t!==this&&t.parent!==this&&t!==i){n[e]=t,i.parent=null;var r=this.__zr;r&&i.removeSelfFromZr(r),this._doAdd(t)}return this},e.prototype._doAdd=function(t){t.parent&&t.parent.remove(t),t.parent=this;var e=this.__zr;e&&e!==t.__zr&&t.addSelfToZr(e),e&&e.refresh()},e.prototype.remove=function(t){var e=this.__zr,n=this._children,i=P(n,t);return i<0||(n.splice(i,1),t.parent=null,e&&t.removeSelfFromZr(e),e&&e.refresh()),this},e.prototype.removeAll=function(){for(var t=this._children,e=this.__zr,n=0;n0&&(this._stillFrameAccum++,this._stillFrameAccum>this._sleepAfterStill&&this.animation.stop())},t.prototype.setSleepAfterStill=function(t){this._sleepAfterStill=t},t.prototype.wakeUp=function(){this._disposed||(this.animation.start(),this._stillFrameAccum=0)},t.prototype.refreshHover=function(){this._needsRefreshHover=!0},t.prototype.refreshHoverImmediately=function(){this._disposed||(this._needsRefreshHover=!1,this.painter.refreshHover&&"canvas"===this.painter.getType()&&this.painter.refreshHover())},t.prototype.resize=function(t){this._disposed||(t=t||{},this.painter.resize(t.width,t.height),this.handler.resize())},t.prototype.clearAnimation=function(){this._disposed||this.animation.clear()},t.prototype.getWidth=function(){if(!this._disposed)return this.painter.getWidth()},t.prototype.getHeight=function(){if(!this._disposed)return this.painter.getHeight()},t.prototype.setCursorStyle=function(t){this._disposed||this.handler.setCursorStyle(t)},t.prototype.findHover=function(t,e){if(!this._disposed)return this.handler.findHover(t,e)},t.prototype.on=function(t,e,n){return this._disposed||this.handler.on(t,e,n),this},t.prototype.off=function(t,e){this._disposed||this.handler.off(t,e)},t.prototype.trigger=function(t,e){this._disposed||this.handler.trigger(t,e)},t.prototype.clear=function(){if(!this._disposed){for(var t=this.storage.getRoots(),e=0;e0){if(t<=r)return a;if(t>=o)return s}else{if(t>=r)return a;if(t<=o)return s}else{if(t===r)return a;if(t===o)return s}return(t-r)/l*u+a}function $r(t,e){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottom":t="100%"}return U(t)?(n=t,n.replace(/^\s+|\s+$/g,"")).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?NaN:+t;var n}function Jr(t,e,n){return null==e&&(e=10),e=Math.min(Math.max(0,e),20),t=(+t).toFixed(e),n?t:+t}function Qr(t){return t.sort((function(t,e){return t-e})),t}function to(t){if(t=+t,isNaN(t))return 0;if(t>1e-14)for(var e=1,n=0;n<15;n++,e*=10)if(Math.round(t*e)/e===t)return n;return eo(t)}function eo(t){var e=t.toString().toLowerCase(),n=e.indexOf("e"),i=n>0?+e.slice(n+1):0,r=n>0?n:e.length,o=e.indexOf("."),a=o<0?0:r-1-o;return Math.max(0,a-i)}function no(t,e){var n=Math.log,i=Math.LN10,r=Math.floor(n(t[1]-t[0])/i),o=Math.round(n(Math.abs(e[1]-e[0]))/i),a=Math.min(Math.max(-r+o,0),20);return isFinite(a)?a:20}function io(t,e){var n=V(t,(function(t,e){return t+(isNaN(e)?0:e)}),0);if(0===n)return[];for(var i=Math.pow(10,e),r=z(t,(function(t){return(isNaN(t)?0:t)/n*i*100})),o=100*i,a=z(r,(function(t){return Math.floor(t)})),s=V(a,(function(t,e){return t+e}),0),l=z(r,(function(t,e){return t-a[e]}));su&&(u=l[c],h=c);++a[h],l[h]=0,++s}return z(a,(function(t){return t/i}))}function ro(t,e){var n=Math.max(to(t),to(e)),i=t+e;return n>20?i:Jr(i,n)}var oo=9007199254740991;function ao(t){var e=2*Math.PI;return(t%e+e)%e}function so(t){return t>-1e-4&&t=10&&e++,e}function po(t,e){var n=co(t),i=Math.pow(10,n),r=t/i;return t=(e?r<1.5?1:r<2.5?2:r<4?3:r<7?5:10:r<1?1:r<2?2:r<3?3:r<5?5:10)*i,n>=-20?+t.toFixed(n<0?-n:0):t}function fo(t,e){var n=(t.length-1)*e+1,i=Math.floor(n),r=+t[i-1],o=n-i;return o?r+o*(t[i]-r):r}function go(t){t.sort((function(t,e){return s(t,e,0)?-1:1}));for(var e=-1/0,n=1,i=0;i=0||r&&P(r,s)<0)){var l=n.getShallow(s,e);null!=l&&(o[t[a][0]]=l)}}return o}}var ra=ia([["fill","color"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["opacity"],["shadowColor"]]),oa=function(){function t(){}return t.prototype.getAreaStyle=function(t,e){return ra(this,t,e)},t}(),aa=new En(50);function sa(t){if("string"==typeof t){var e=aa.get(t);return e&&e.image}return t}function la(t,e,n,i,r){if(t){if("string"==typeof t){if(e&&e.__zrImageSrc===t||!n)return e;var o=aa.get(t),a={hostEl:n,cb:i,cbPayload:r};return o?!ha(e=o.image)&&o.pending.push(a):((e=h.loadImage(t,ua,ua)).__zrImageSrc=t,aa.put(t,e.__cachedImgObj={image:e,pending:[a]})),e}return t}return e}function ua(){var t=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var e=0;e=a;l++)s-=a;var u=br(n,e);return u>s&&(n="",u=0),s=t-u,r.ellipsis=n,r.ellipsisWidth=u,r.contentWidth=s,r.containerWidth=t,r}function fa(t,e,n){var i=n.containerWidth,r=n.font,o=n.contentWidth;if(!i)return t.textLine="",void(t.isTruncated=!1);var a=br(e,r);if(a<=i)return t.textLine=e,void(t.isTruncated=!1);for(var s=0;;s++){if(a<=o||s>=n.maxIterations){e+=n.ellipsis;break}var l=0===s?ga(e,o,n.ascCharWidth,n.cnCharWidth):a>0?Math.floor(e.length*o/a):0;a=br(e=e.substr(0,l),r)}""===e&&(e=n.placeholder),t.textLine=e,t.isTruncated=!0}function ga(t,e,n,i){for(var r=0,o=0,a=t.length;o0&&f+i.accumWidth>i.width&&(o=e.split("\n"),c=!0),i.accumWidth=f}else{var g=wa(e,h,i.width,i.breakAll,i.accumWidth);i.accumWidth=g.accumWidth+d,a=g.linesWidths,o=g.lines}}else o=e.split("\n");for(var y=0;y=32&&e<=591||e>=880&&e<=4351||e>=4608&&e<=5119||e>=7680&&e<=8303}(t)||!!_a[t]}function wa(t,e,n,i,r){for(var o=[],a=[],s="",l="",u=0,h=0,c=0;cn:r+h+d>n)?h?(s||l)&&(f?(s||(s=l,l="",h=u=0),o.push(s),a.push(h-u),l+=p,s="",h=u+=d):(l&&(s+=l,l="",u=0),o.push(s),a.push(h),s=p,h=d)):f?(o.push(l),a.push(u),l=p,u=d):(o.push(p),a.push(d)):(h+=d,f?(l+=p,u+=d):(l&&(s+=l,l="",u=0),s+=p))}else l&&(s+=l,h+=u),o.push(s),a.push(h),s="",l="",u=0,h=0}return o.length||s||(s=t,l="",u=0),l&&(s+=l),s&&(o.push(s),a.push(h)),1===o.length&&(h+=r),{accumWidth:h,lines:o,linesWidths:a}}var Sa="__zr_style_"+Math.round(10*Math.random()),Ma={shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,shadowColor:"#000",opacity:1,blend:"source-over"},Ia={style:{shadowBlur:!0,shadowOffsetX:!0,shadowOffsetY:!0,shadowColor:!0,opacity:!0}};Ma[Sa]=!0;var Ta=["z","z2","invisible"],Ca=["invisible"],Da=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype._init=function(e){for(var n=G(e),i=0;i1e-4)return s[0]=t-n,s[1]=e-i,l[0]=t+n,void(l[1]=e+i);if(Ea[0]=Ra(r)*n+t,Ea[1]=Oa(r)*i+e,za[0]=Ra(o)*n+t,za[1]=Oa(o)*i+e,u(s,Ea,za),h(l,Ea,za),(r%=Na)<0&&(r+=Na),(o%=Na)<0&&(o+=Na),r>o&&!a?o+=Na:rr&&(Va[0]=Ra(d)*n+t,Va[1]=Oa(d)*i+e,u(s,Va,s),h(l,Va,l))}var Ua={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},Za=[],ja=[],qa=[],Ka=[],$a=[],Ja=[],Qa=Math.min,ts=Math.max,es=Math.cos,ns=Math.sin,is=Math.abs,rs=Math.PI,os=2*rs,as="undefined"!=typeof Float32Array,ss=[];function ls(t){return Math.round(t/rs*1e8)/1e8%2*rs}function us(t,e){var n=ls(t[0]);n<0&&(n+=os);var i=n-t[0],r=t[1];r+=i,!e&&r-n>=os?r=n+os:e&&n-r>=os?r=n-os:!e&&n>r?r=n+(os-ls(n-r)):e&&n0&&(this._ux=is(n/sr/t)||0,this._uy=is(n/sr/e)||0)},t.prototype.setDPR=function(t){this.dpr=t},t.prototype.setContext=function(t){this._ctx=t},t.prototype.getContext=function(){return this._ctx},t.prototype.beginPath=function(){return this._ctx&&this._ctx.beginPath(),this.reset(),this},t.prototype.reset=function(){this._saveData&&(this._len=0),this._pathSegLen&&(this._pathSegLen=null,this._pathLen=0),this._version++},t.prototype.moveTo=function(t,e){return this._drawPendingPt(),this.addData(Ua.M,t,e),this._ctx&&this._ctx.moveTo(t,e),this._x0=t,this._y0=e,this._xi=t,this._yi=e,this},t.prototype.lineTo=function(t,e){var n=is(t-this._xi),i=is(e-this._yi),r=n>this._ux||i>this._uy;if(this.addData(Ua.L,t,e),this._ctx&&r&&this._ctx.lineTo(t,e),r)this._xi=t,this._yi=e,this._pendingPtDist=0;else{var o=n*n+i*i;o>this._pendingPtDist&&(this._pendingPtX=t,this._pendingPtY=e,this._pendingPtDist=o)}return this},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){return this._drawPendingPt(),this.addData(Ua.C,t,e,n,i,r,o),this._ctx&&this._ctx.bezierCurveTo(t,e,n,i,r,o),this._xi=r,this._yi=o,this},t.prototype.quadraticCurveTo=function(t,e,n,i){return this._drawPendingPt(),this.addData(Ua.Q,t,e,n,i),this._ctx&&this._ctx.quadraticCurveTo(t,e,n,i),this._xi=n,this._yi=i,this},t.prototype.arc=function(t,e,n,i,r,o){this._drawPendingPt(),ss[0]=i,ss[1]=r,us(ss,o),i=ss[0];var a=(r=ss[1])-i;return this.addData(Ua.A,t,e,n,n,i,a,0,o?0:1),this._ctx&&this._ctx.arc(t,e,n,i,r,o),this._xi=es(r)*n+t,this._yi=ns(r)*n+e,this},t.prototype.arcTo=function(t,e,n,i,r){return this._drawPendingPt(),this._ctx&&this._ctx.arcTo(t,e,n,i,r),this},t.prototype.rect=function(t,e,n,i){return this._drawPendingPt(),this._ctx&&this._ctx.rect(t,e,n,i),this.addData(Ua.R,t,e,n,i),this},t.prototype.closePath=function(){this._drawPendingPt(),this.addData(Ua.Z);var t=this._ctx,e=this._x0,n=this._y0;return t&&t.closePath(),this._xi=e,this._yi=n,this},t.prototype.fill=function(t){t&&t.fill(),this.toStatic()},t.prototype.stroke=function(t){t&&t.stroke(),this.toStatic()},t.prototype.len=function(){return this._len},t.prototype.setData=function(t){var e=t.length;this.data&&this.data.length===e||!as||(this.data=new Float32Array(e));for(var n=0;nu.length&&(this._expandData(),u=this.data);for(var h=0;h0&&(this._ctx&&this._ctx.lineTo(this._pendingPtX,this._pendingPtY),this._pendingPtDist=0)},t.prototype._expandData=function(){if(!(this.data instanceof Array)){for(var t=[],e=0;e11&&(this.data=new Float32Array(t)))}},t.prototype.getBoundingRect=function(){qa[0]=qa[1]=$a[0]=$a[1]=Number.MAX_VALUE,Ka[0]=Ka[1]=Ja[0]=Ja[1]=-Number.MAX_VALUE;var t,e=this.data,n=0,i=0,r=0,o=0;for(t=0;tn||is(y)>i||c===e-1)&&(f=Math.sqrt(A*A+y*y),r=g,o=x);break;case Ua.C:var v=t[c++],m=t[c++],x=(g=t[c++],t[c++]),_=t[c++],b=t[c++];f=Mn(r,o,v,m,g,x,_,b,10),r=_,o=b;break;case Ua.Q:f=kn(r,o,v=t[c++],m=t[c++],g=t[c++],x=t[c++],10),r=g,o=x;break;case Ua.A:var w=t[c++],S=t[c++],M=t[c++],I=t[c++],T=t[c++],C=t[c++],D=C+T;c+=1,d&&(a=es(T)*M+w,s=ns(T)*I+S),f=ts(M,I)*Qa(os,Math.abs(C)),r=es(D)*M+w,o=ns(D)*I+S;break;case Ua.R:a=r=t[c++],s=o=t[c++],f=2*t[c++]+2*t[c++];break;case Ua.Z:var A=a-r;y=s-o;f=Math.sqrt(A*A+y*y),r=a,o=s}f>=0&&(l[h++]=f,u+=f)}return this._pathLen=u,u},t.prototype.rebuildPath=function(t,e){var n,i,r,o,a,s,l,u,h,c,p=this.data,d=this._ux,f=this._uy,g=this._len,y=e<1,v=0,m=0,x=0;if(!y||(this._pathSegLen||this._calculateLength(),l=this._pathSegLen,u=e*this._pathLen))t:for(var _=0;_0&&(t.lineTo(h,c),x=0),b){case Ua.M:n=r=p[_++],i=o=p[_++],t.moveTo(r,o);break;case Ua.L:a=p[_++],s=p[_++];var S=is(a-r),M=is(s-o);if(S>d||M>f){if(y){if(v+(j=l[m++])>u){var I=(u-v)/j;t.lineTo(r*(1-I)+a*I,o*(1-I)+s*I);break t}v+=j}t.lineTo(a,s),r=a,o=s,x=0}else{var T=S*S+M*M;T>x&&(h=a,c=s,x=T)}break;case Ua.C:var C=p[_++],D=p[_++],A=p[_++],k=p[_++],L=p[_++],P=p[_++];if(y){if(v+(j=l[m++])>u){wn(r,C,A,L,I=(u-v)/j,Za),wn(o,D,k,P,I,ja),t.bezierCurveTo(Za[1],ja[1],Za[2],ja[2],Za[3],ja[3]);break t}v+=j}t.bezierCurveTo(C,D,A,k,L,P),r=L,o=P;break;case Ua.Q:C=p[_++],D=p[_++],A=p[_++],k=p[_++];if(y){if(v+(j=l[m++])>u){Dn(r,C,A,I=(u-v)/j,Za),Dn(o,D,k,I,ja),t.quadraticCurveTo(Za[1],ja[1],Za[2],ja[2]);break t}v+=j}t.quadraticCurveTo(C,D,A,k),r=A,o=k;break;case Ua.A:var O=p[_++],R=p[_++],N=p[_++],E=p[_++],z=p[_++],V=p[_++],B=p[_++],F=!p[_++],G=N>E?N:E,W=is(N-E)>.001,H=z+V,Y=!1;if(y)v+(j=l[m++])>u&&(H=z+V*(u-v)/j,Y=!0),v+=j;if(W&&t.ellipse?t.ellipse(O,R,N,E,B,z,H,F):t.arc(O,R,G,z,H,F),Y)break t;w&&(n=es(z)*N+O,i=ns(z)*E+R),r=es(H)*N+O,o=ns(H)*E+R;break;case Ua.R:n=r=p[_],i=o=p[_+1],a=p[_++],s=p[_++];var X=p[_++],U=p[_++];if(y){if(v+(j=l[m++])>u){var Z=u-v;t.moveTo(a,s),t.lineTo(a+Qa(Z,X),s),(Z-=X)>0&&t.lineTo(a+X,s+Qa(Z,U)),(Z-=U)>0&&t.lineTo(a+ts(X-Z,0),s+U),(Z-=X)>0&&t.lineTo(a,s+ts(U-Z,0));break t}v+=j}t.rect(a,s,X,U);break;case Ua.Z:if(y){var j;if(v+(j=l[m++])>u){I=(u-v)/j;t.lineTo(r*(1-I)+n*I,o*(1-I)+i*I);break t}v+=j}t.closePath(),r=n,o=i}}},t.prototype.clone=function(){var e=new t,n=this.data;return e.data=n.slice?n.slice():Array.prototype.slice.call(n),e._len=this._len,e},t.CMD=Ua,t.initDefaultProps=function(){var e=t.prototype;e._saveData=!0,e._ux=0,e._uy=0,e._pendingPtDist=0,e._version=0}(),t}();function cs(t,e,n,i,r,o,a){if(0===r)return!1;var s=r,l=0;if(a>e+s&&a>i+s||at+s&&o>n+s||oe+c&&h>i+c&&h>o+c&&h>s+c||ht+c&&u>n+c&&u>r+c&&u>a+c||ue+u&&l>i+u&&l>o+u||lt+u&&s>n+u&&s>r+u||sn||h+ur&&(r+=ys);var p=Math.atan2(l,s);return p<0&&(p+=ys),p>=i&&p<=r||p+ys>=i&&p+ys<=r}function ms(t,e,n,i,r,o){if(o>e&&o>i||or?s:0}var xs=hs.CMD,_s=2*Math.PI;var bs=[-1,-1,-1],ws=[-1,-1];function Ss(t,e,n,i,r,o,a,s,l,u){if(u>e&&u>i&&u>o&&u>s||u1&&(h=void 0,h=ws[0],ws[0]=ws[1],ws[1]=h),f=mn(e,i,o,s,ws[0]),d>1&&(g=mn(e,i,o,s,ws[1]))),2===d?ve&&s>i&&s>o||s=0&&h<=1&&(r[l++]=h);else{var u=a*a-4*o*s;if(yn(u))(h=-a/(2*o))>=0&&h<=1&&(r[l++]=h);else if(u>0){var h,c=ln(u),p=(-a-c)/(2*o);(h=(-a+c)/(2*o))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}(e,i,o,s,bs);if(0===l)return 0;var u=Cn(e,i,o);if(u>=0&&u<=1){for(var h=0,c=In(e,i,o,u),p=0;pn||s<-n)return 0;var l=Math.sqrt(n*n-s*s);bs[0]=-l,bs[1]=l;var u=Math.abs(i-r);if(u<1e-4)return 0;if(u>=_s-1e-4){i=0,r=_s;var h=o?1:-1;return a>=bs[0]+t&&a<=bs[1]+t?h:0}if(i>r){var c=i;i=r,r=c}i<0&&(i+=_s,r+=_s);for(var p=0,d=0;d<2;d++){var f=bs[d];if(f+t>a){var g=Math.atan2(s,f);h=o?1:-1;g<0&&(g=_s+g),(g>=i&&g<=r||g+_s>=i&&g+_s<=r)&&(g>Math.PI/2&&g<1.5*Math.PI&&(h=-h),p+=h)}}return p}function Ts(t,e,n,i,r){for(var o,a,s,l,u=t.data,h=t.len(),c=0,p=0,d=0,f=0,g=0,y=0;y1&&(n||(c+=ms(p,d,f,g,i,r))),m&&(f=p=u[y],g=d=u[y+1]),v){case xs.M:p=f=u[y++],d=g=u[y++];break;case xs.L:if(n){if(cs(p,d,u[y],u[y+1],e,i,r))return!0}else c+=ms(p,d,u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case xs.C:if(n){if(ps(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=Ss(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case xs.Q:if(n){if(ds(p,d,u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=Ms(p,d,u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case xs.A:var x=u[y++],_=u[y++],b=u[y++],w=u[y++],S=u[y++],M=u[y++];y+=1;var I=!!(1-u[y++]);o=Math.cos(S)*b+x,a=Math.sin(S)*w+_,m?(f=o,g=a):c+=ms(p,d,o,a,i,r);var T=(i-x)*w/b+x;if(n){if(vs(x,_,w,S,S+M,I,e,T,r))return!0}else c+=Is(x,_,w,S,S+M,I,T,r);p=Math.cos(S+M)*b+x,d=Math.sin(S+M)*w+_;break;case xs.R:if(f=p=u[y++],g=d=u[y++],o=f+u[y++],a=g+u[y++],n){if(cs(f,g,o,g,e,i,r)||cs(o,g,o,a,e,i,r)||cs(o,a,f,a,e,i,r)||cs(f,a,f,g,e,i,r))return!0}else c+=ms(o,g,o,a,i,r),c+=ms(f,a,f,g,i,r);break;case xs.Z:if(n){if(cs(p,d,f,g,e,i,r))return!0}else c+=ms(p,d,f,g,i,r);p=f,d=g}}return n||(s=d,l=g,Math.abs(s-l)<1e-4)||(c+=ms(p,d,f,g,i,r)||0),0!==c}var Cs=k({fill:"#000",stroke:null,strokePercent:1,fillOpacity:1,strokeOpacity:1,lineDashOffset:0,lineWidth:1,lineCap:"butt",miterLimit:10,strokeNoScale:!1,strokeFirst:!1},Ma),Ds={style:k({fill:!0,stroke:!0,strokePercent:!0,fillOpacity:!0,strokeOpacity:!0,lineDashOffset:!0,lineWidth:!0,miterLimit:!0},Ia.style)},As=mr.concat(["invisible","culling","z","z2","zlevel","parent"]),ks=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype.update=function(){var n=this;t.prototype.update.call(this);var i=this.style;if(i.decal){var r=this._decalEl=this._decalEl||new e;r.buildPath===e.prototype.buildPath&&(r.buildPath=function(t){n.buildPath(t,n.shape)}),r.silent=!0;var o=r.style;for(var a in i)o[a]!==i[a]&&(o[a]=i[a]);o.fill=i.fill?i.decal:null,o.decal=null,o.shadowColor=null,i.strokeFirst&&(o.stroke=null);for(var s=0;s.5?lr:e>.2?"#eee":ur}if(t)return ur}return lr},e.prototype.getInsideTextStroke=function(t){var e=this.style.fill;if(U(e)){var n=this.__zr;if(!(!n||!n.isDarkMode())===oi(t,0)<.4)return e}},e.prototype.buildPath=function(t,e,n){},e.prototype.pathUpdated=function(){this.__dirty&=-5},e.prototype.getUpdatedPathProxy=function(t){return!this.path&&this.createPathProxy(),this.path.beginPath(),this.buildPath(this.path,this.shape,t),this.path},e.prototype.createPathProxy=function(){this.path=new hs(!1)},e.prototype.hasStroke=function(){var t=this.style,e=t.stroke;return!(null==e||"none"===e||!(t.lineWidth>0))},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&"none"!==t},e.prototype.getBoundingRect=function(){var t=this._rect,e=this.style,n=!t;if(n){var i=!1;this.path||(i=!0,this.createPathProxy());var r=this.path;(i||4&this.__dirty)&&(r.beginPath(),this.buildPath(r,this.shape,!1),this.pathUpdated()),t=r.getBoundingRect()}if(this._rect=t,this.hasStroke()&&this.path&&this.path.len()>0){var o=this._rectStroke||(this._rectStroke=t.clone());if(this.__dirty||n){o.copy(t);var a=e.strokeNoScale?this.getLineScale():1,s=e.lineWidth;if(!this.hasFill()){var l=this.strokeContainThreshold;s=Math.max(s,null==l?4:l)}a>1e-10&&(o.width+=s/a,o.height+=s/a,o.x-=s/a/2,o.y-=s/a/2)}return o}return t},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect(),r=this.style;if(t=n[0],e=n[1],i.contain(t,e)){var o=this.path;if(this.hasStroke()){var a=r.lineWidth,s=r.strokeNoScale?this.getLineScale():1;if(s>1e-10&&(this.hasFill()||(a=Math.max(a,this.strokeContainThreshold)),function(t,e,n,i){return Ts(t,e,!0,n,i)}(o,a/s,t,e)))return!0}if(this.hasFill())return function(t,e,n){return Ts(t,0,!1,e,n)}(o,t,e)}return!1},e.prototype.dirtyShape=function(){this.__dirty|=4,this._rect&&(this._rect=null),this._decalEl&&this._decalEl.dirtyShape(),this.markRedraw()},e.prototype.dirty=function(){this.dirtyStyle(),this.dirtyShape()},e.prototype.animateShape=function(t){return this.animate("shape",t)},e.prototype.updateDuringAnimation=function(t){"style"===t?this.dirtyStyle():"shape"===t?this.dirtyShape():this.markRedraw()},e.prototype.attrKV=function(e,n){"shape"===e?this.setShape(n):t.prototype.attrKV.call(this,e,n)},e.prototype.setShape=function(t,e){var n=this.shape;return n||(n=this.shape={}),"string"==typeof t?n[t]=e:A(n,t),this.dirtyShape(),this},e.prototype.shapeChanged=function(){return!!(4&this.__dirty)},e.prototype.createStyle=function(t){return mt(Cs,t)},e.prototype._innerSaveToNormal=function(e){t.prototype._innerSaveToNormal.call(this,e);var n=this._normalState;e.shape&&!n.shape&&(n.shape=A({},this.shape))},e.prototype._applyStateObj=function(e,n,i,r,o,a){t.prototype._applyStateObj.call(this,e,n,i,r,o,a);var s,l=!(n&&r);if(n&&n.shape?o?r?s=n.shape:(s=A({},i.shape),A(s,n.shape)):(s=A({},r?this.shape:i.shape),A(s,n.shape)):l&&(s=i.shape),s)if(o){this.shape=A({},this.shape);for(var u={},h=G(s),c=0;c0},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&"none"!==t},e.prototype.createStyle=function(t){return mt(Ls,t)},e.prototype.setBoundingRect=function(t){this._rect=t},e.prototype.getBoundingRect=function(){var t=this.style;if(!this._rect){var e=t.text;null!=e?e+="":e="";var n=Sr(e,t.font,t.textAlign,t.textBaseline);if(n.x+=t.x||0,n.y+=t.y||0,this.hasStroke()){var i=t.lineWidth;n.x-=i/2,n.y-=i/2,n.width+=i,n.height+=i}this._rect=n}return this._rect},e.initDefaultProps=void(e.prototype.dirtyRectTolerance=10),e}(Da);Ps.prototype.type="tspan";var Os=k({x:0,y:0},Ma),Rs={style:k({x:!0,y:!0,width:!0,height:!0,sx:!0,sy:!0,sWidth:!0,sHeight:!0},Ia.style)};var Ns=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.createStyle=function(t){return mt(Os,t)},e.prototype._getSize=function(t){var e=this.style,n=e[t];if(null!=n)return n;var i,r=(i=e.image)&&"string"!=typeof i&&i.width&&i.height?e.image:this.__image;if(!r)return 0;var o="width"===t?"height":"width",a=e[o];return null==a?r[t]:r[t]/r[o]*a},e.prototype.getWidth=function(){return this._getSize("width")},e.prototype.getHeight=function(){return this._getSize("height")},e.prototype.getAnimationStyleProps=function(){return Rs},e.prototype.getBoundingRect=function(){var t=this.style;return this._rect||(this._rect=new ze(t.x||0,t.y||0,this.getWidth(),this.getHeight())),this._rect},e}(Da);Ns.prototype.type="image";var Es=Math.round;function zs(t,e,n){if(e){var i=e.x1,r=e.x2,o=e.y1,a=e.y2;t.x1=i,t.x2=r,t.y1=o,t.y2=a;var s=n&&n.lineWidth;return s?(Es(2*i)===Es(2*r)&&(t.x1=t.x2=Bs(i,s,!0)),Es(2*o)===Es(2*a)&&(t.y1=t.y2=Bs(o,s,!0)),t):t}}function Vs(t,e,n){if(e){var i=e.x,r=e.y,o=e.width,a=e.height;t.x=i,t.y=r,t.width=o,t.height=a;var s=n&&n.lineWidth;return s?(t.x=Bs(i,s,!0),t.y=Bs(r,s,!0),t.width=Math.max(Bs(i+o,s,!1)-t.x,0===o?0:1),t.height=Math.max(Bs(r+a,s,!1)-t.y,0===a?0:1),t):t}}function Bs(t,e,n){if(!e)return t;var i=Es(2*t);return(i+Es(e))%2==0?i/2:(i+(n?1:-1))/2}var Fs=function(){this.x=0,this.y=0,this.width=0,this.height=0},Gs={},Ws=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Fs},e.prototype.buildPath=function(t,e){var n,i,r,o;if(this.subPixelOptimize){var a=Vs(Gs,e,this.style);n=a.x,i=a.y,r=a.width,o=a.height,a.r=e.r,e=a}else n=e.x,i=e.y,r=e.width,o=e.height;e.r?function(t,e){var n,i,r,o,a,s=e.x,l=e.y,u=e.width,h=e.height,c=e.r;u<0&&(s+=u,u=-u),h<0&&(l+=h,h=-h),"number"==typeof c?n=i=r=o=c:c instanceof Array?1===c.length?n=i=r=o=c[0]:2===c.length?(n=r=c[0],i=o=c[1]):3===c.length?(n=c[0],i=o=c[1],r=c[2]):(n=c[0],i=c[1],r=c[2],o=c[3]):n=i=r=o=0,n+i>u&&(n*=u/(a=n+i),i*=u/a),r+o>u&&(r*=u/(a=r+o),o*=u/a),i+r>h&&(i*=h/(a=i+r),r*=h/a),n+o>h&&(n*=h/(a=n+o),o*=h/a),t.moveTo(s+n,l),t.lineTo(s+u-i,l),0!==i&&t.arc(s+u-i,l+i,i,-Math.PI/2,0),t.lineTo(s+u,l+h-r),0!==r&&t.arc(s+u-r,l+h-r,r,0,Math.PI/2),t.lineTo(s+o,l+h),0!==o&&t.arc(s+o,l+h-o,o,Math.PI/2,Math.PI),t.lineTo(s,l+n),0!==n&&t.arc(s+n,l+n,n,Math.PI,1.5*Math.PI)}(t,e):t.rect(n,i,r,o)},e.prototype.isZeroArea=function(){return!this.shape.width||!this.shape.height},e}(ks);Ws.prototype.type="rect";var Hs={fill:"#000"},Ys={style:k({fill:!0,stroke:!0,fillOpacity:!0,strokeOpacity:!0,lineWidth:!0,fontSize:!0,lineHeight:!0,width:!0,height:!0,textShadowColor:!0,textShadowBlur:!0,textShadowOffsetX:!0,textShadowOffsetY:!0,backgroundColor:!0,padding:!0,borderColor:!0,borderWidth:!0,borderRadius:!0},Ia.style)},Xs=function(t){function e(e){var n=t.call(this)||this;return n.type="text",n._children=[],n._defaultStyle=Hs,n.attr(e),n}return n(e,t),e.prototype.childrenRef=function(){return this._children},e.prototype.update=function(){t.prototype.update.call(this),this.styleChanged()&&this._updateSubTexts();for(var e=0;ef&&h){var g=Math.floor(f/l);c=c||n.length>g,n=n.slice(0,g)}if(t&&a&&null!=p)for(var y=da(p,o,e.ellipsis,{minChar:e.truncateMinChar,placeholder:e.placeholder}),v={},m=0;m0,T=null!=t.width&&("truncate"===t.overflow||"break"===t.overflow||"breakAll"===t.overflow),C=i.calculatedLineHeight,D=0;Dl&&xa(n,t.substring(l,u),e,s),xa(n,i[2],e,s,i[1]),l=ca.lastIndex}lo){var D=n.lines.length;w>0?(x.tokens=x.tokens.slice(0,w),v(x,b,_),n.lines=n.lines.slice(0,m+1)):n.lines=n.lines.slice(0,m),n.isTruncated=n.isTruncated||n.lines.length=0&&"right"===(C=x[T]).align;)this._placeToken(C,t,b,f,I,"right",y),w-=C.width,I-=C.width,T--;for(M+=(n-(M-d)-(g-I)-w)/2;S<=T;)C=x[S],this._placeToken(C,t,b,f,M+C.width/2,"center",y),M+=C.width,S++;f+=b}},e.prototype._placeToken=function(t,e,n,i,r,o,s){var l=e.rich[t.styleName]||{};l.text=t.text;var u=t.verticalAlign,h=i+n/2;"top"===u?h=i+t.height/2:"bottom"===u&&(h=i+n-t.height/2),!t.isLineHolder&&il(l)&&this._renderBackground(l,e,"right"===o?r-t.width:"center"===o?r-t.width/2:r,h-t.height/2,t.width,t.height);var c=!!l.backgroundColor,p=t.textPadding;p&&(r=el(r,o,p),h-=t.height/2-p[0]-t.innerHeight/2);var d=this._getOrCreateChild(Ps),f=d.createStyle();d.useStyle(f);var g=this._defaultStyle,y=!1,v=0,m=tl("fill"in l?l.fill:"fill"in e?e.fill:(y=!0,g.fill)),x=Qs("stroke"in l?l.stroke:"stroke"in e?e.stroke:c||s||g.autoStroke&&!y?null:(v=2,g.stroke)),_=l.textShadowBlur>0||e.textShadowBlur>0;f.text=t.text,f.x=r,f.y=h,_&&(f.shadowBlur=l.textShadowBlur||e.textShadowBlur||0,f.shadowColor=l.textShadowColor||e.textShadowColor||"transparent",f.shadowOffsetX=l.textShadowOffsetX||e.textShadowOffsetX||0,f.shadowOffsetY=l.textShadowOffsetY||e.textShadowOffsetY||0),f.textAlign=o,f.textBaseline="middle",f.font=t.font||a,f.opacity=ot(l.opacity,e.opacity,1),Ks(f,l),x&&(f.lineWidth=ot(l.lineWidth,e.lineWidth,v),f.lineDash=rt(l.lineDash,e.lineDash),f.lineDashOffset=e.lineDashOffset||0,f.stroke=x),m&&(f.fill=m);var b=t.contentWidth,w=t.contentHeight;d.setBoundingRect(new ze(Mr(f.x,b,f.textAlign),Ir(f.y,w,f.textBaseline),b,w))},e.prototype._renderBackground=function(t,e,n,i,r,o){var a,s,l,u=t.backgroundColor,h=t.borderWidth,c=t.borderColor,p=u&&u.image,d=u&&!p,f=t.borderRadius,g=this;if(d||t.lineHeight||h&&c){(a=this._getOrCreateChild(Ws)).useStyle(a.createStyle()),a.style.fill=null;var y=a.shape;y.x=n,y.y=i,y.width=r,y.height=o,y.r=f,a.dirtyShape()}if(d)(l=a.style).fill=u||null,l.fillOpacity=rt(t.fillOpacity,1);else if(p){(s=this._getOrCreateChild(Ns)).onload=function(){g.dirtyStyle()};var v=s.style;v.image=u.image,v.x=n,v.y=i,v.width=r,v.height=o}h&&c&&((l=a.style).lineWidth=h,l.stroke=c,l.strokeOpacity=rt(t.strokeOpacity,1),l.lineDash=t.borderDash,l.lineDashOffset=t.borderDashOffset||0,a.strokeContainThreshold=0,a.hasFill()&&a.hasStroke()&&(l.strokeFirst=!0,l.lineWidth*=2));var m=(a||s).style;m.shadowBlur=t.shadowBlur||0,m.shadowColor=t.shadowColor||"transparent",m.shadowOffsetX=t.shadowOffsetX||0,m.shadowOffsetY=t.shadowOffsetY||0,m.opacity=ot(t.opacity,e.opacity,1)},e.makeFont=function(t){var e="";return $s(t)&&(e=[t.fontStyle,t.fontWeight,qs(t.fontSize),t.fontFamily||"sans-serif"].join(" ")),e&&ut(e)||t.textFont||t.font},e}(Da),Us={left:!0,right:1,center:1},Zs={top:1,bottom:1,middle:1},js=["fontStyle","fontWeight","fontSize","fontFamily"];function qs(t){return"string"!=typeof t||-1===t.indexOf("px")&&-1===t.indexOf("rem")&&-1===t.indexOf("em")?isNaN(+t)?"12px":t+"px":t}function Ks(t,e){for(var n=0;n=0,o=!1;if(t instanceof ks){var a=ll(t),s=r&&a.selectFill||a.normalFill,l=r&&a.selectStroke||a.normalStroke;if(ml(s)||ml(l)){var u=(i=i||{}).style||{};"inherit"===u.fill?(o=!0,i=A({},i),(u=A({},u)).fill=s):!ml(u.fill)&&ml(s)?(o=!0,i=A({},i),(u=A({},u)).fill=si(s)):!ml(u.stroke)&&ml(l)&&(o||(i=A({},i),u=A({},u)),u.stroke=si(l)),i.style=u}}if(i&&null==i.z2){o||(i=A({},i));var h=t.z2EmphasisLift;i.z2=t.z2+(null!=h?h:pl)}return i}(this,0,e,n);if("blur"===t)return function(t,e,n){var i=P(t.currentStates,e)>=0,r=t.style.opacity,o=i?null:function(t,e,n,i){for(var r=t.style,o={},a=0;a0){var o={dataIndex:r,seriesIndex:t.seriesIndex};null!=i&&(o.dataType=i),e.push(o)}}))})),e}function Ul(t,e,n){Jl(t,!0),Cl(t,kl),jl(t,e,n)}function Zl(t,e,n,i){i?function(t){Jl(t,!1)}(t):Ul(t,e,n)}function jl(t,e,n){var i=rl(t);null!=e?(i.focus=e,i.blurScope=n):i.focus&&(i.focus=null)}var ql=["emphasis","blur","select"],Kl={itemStyle:"getItemStyle",lineStyle:"getLineStyle",areaStyle:"getAreaStyle"};function $l(t,e,n,i){n=n||"itemStyle";for(var r=0;r1&&(a*=su(f),s*=su(f));var g=(r===o?-1:1)*su((a*a*(s*s)-a*a*(d*d)-s*s*(p*p))/(a*a*(d*d)+s*s*(p*p)))||0,y=g*a*d/s,v=g*-s*p/a,m=(t+n)/2+uu(c)*y-lu(c)*v,x=(e+i)/2+lu(c)*y+uu(c)*v,_=du([1,0],[(p-y)/a,(d-v)/s]),b=[(p-y)/a,(d-v)/s],w=[(-1*p-y)/a,(-1*d-v)/s],S=du(b,w);if(pu(b,w)<=-1&&(S=hu),pu(b,w)>=1&&(S=0),S<0){var M=Math.round(S/hu*1e6)/1e6;S=2*hu+M%2*hu}h.addData(u,m,x,a,s,_,S,c,o)}var gu=/([mlvhzcqtsa])([^mlvhzcqtsa]*)/gi,yu=/-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g;var vu=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.applyTransform=function(t){},e}(ks);function mu(t){return null!=t.setData}function xu(t,e){var n=function(t){var e=new hs;if(!t)return e;var n,i=0,r=0,o=i,a=r,s=hs.CMD,l=t.match(gu);if(!l)return e;for(var u=0;uk*k+L*L&&(M=T,I=C),{cx:M,cy:I,x0:-h,y0:-c,x1:M*(r/b-1),y1:I*(r/b-1)}}function Vu(t,e){var n,i=Ru(e.r,0),r=Ru(e.r0||0,0),o=i>0;if(o||r>0){if(o||(i=r,r=0),r>i){var a=i;i=r,r=a}var s=e.startAngle,l=e.endAngle;if(!isNaN(s)&&!isNaN(l)){var u=e.cx,h=e.cy,c=!!e.clockwise,p=Pu(l-s),d=p>Cu&&p%Cu;if(d>Eu&&(p=d),i>Eu)if(p>Cu-Eu)t.moveTo(u+i*Au(s),h+i*Du(s)),t.arc(u,h,i,s,l,!c),r>Eu&&(t.moveTo(u+r*Au(l),h+r*Du(l)),t.arc(u,h,r,l,s,c));else{var f=void 0,g=void 0,y=void 0,v=void 0,m=void 0,x=void 0,_=void 0,b=void 0,w=void 0,S=void 0,M=void 0,I=void 0,T=void 0,C=void 0,D=void 0,A=void 0,k=i*Au(s),L=i*Du(s),P=r*Au(l),O=r*Du(l),R=p>Eu;if(R){var N=e.cornerRadius;N&&(n=function(t){var e;if(Y(t)){var n=t.length;if(!n)return t;e=1===n?[t[0],t[0],0,0]:2===n?[t[0],t[0],t[1],t[1]]:3===n?t.concat(t[2]):t}else e=[t,t,t,t];return e}(N),f=n[0],g=n[1],y=n[2],v=n[3]);var E=Pu(i-r)/2;if(m=Nu(E,y),x=Nu(E,v),_=Nu(E,f),b=Nu(E,g),M=w=Ru(m,x),I=S=Ru(_,b),(w>Eu||S>Eu)&&(T=i*Au(l),C=i*Du(l),D=r*Au(s),A=r*Du(s),pEu){var X=Nu(y,M),U=Nu(v,M),Z=zu(D,A,k,L,i,X,c),j=zu(T,C,P,O,i,U,c);t.moveTo(u+Z.cx+Z.x0,h+Z.cy+Z.y0),M0&&t.arc(u+Z.cx,h+Z.cy,X,Lu(Z.y0,Z.x0),Lu(Z.y1,Z.x1),!c),t.arc(u,h,i,Lu(Z.cy+Z.y1,Z.cx+Z.x1),Lu(j.cy+j.y1,j.cx+j.x1),!c),U>0&&t.arc(u+j.cx,h+j.cy,U,Lu(j.y1,j.x1),Lu(j.y0,j.x0),!c))}else t.moveTo(u+k,h+L),t.arc(u,h,i,s,l,!c);else t.moveTo(u+k,h+L);if(r>Eu&&R)if(I>Eu){X=Nu(f,I),Z=zu(P,O,T,C,r,-(U=Nu(g,I)),c),j=zu(k,L,D,A,r,-X,c);t.lineTo(u+Z.cx+Z.x0,h+Z.cy+Z.y0),I0&&t.arc(u+Z.cx,h+Z.cy,U,Lu(Z.y0,Z.x0),Lu(Z.y1,Z.x1),!c),t.arc(u,h,r,Lu(Z.cy+Z.y1,Z.cx+Z.x1),Lu(j.cy+j.y1,j.cx+j.x1),c),X>0&&t.arc(u+j.cx,h+j.cy,X,Lu(j.y1,j.x1),Lu(j.y0,j.x0),!c))}else t.lineTo(u+P,h+O),t.arc(u,h,r,l,s,c);else t.lineTo(u+P,h+O)}else t.moveTo(u,h);t.closePath()}}}var Bu=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0,this.cornerRadius=0},Fu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Bu},e.prototype.buildPath=function(t,e){Vu(t,e)},e.prototype.isZeroArea=function(){return this.shape.startAngle===this.shape.endAngle||this.shape.r===this.shape.r0},e}(ks);Fu.prototype.type="sector";var Gu=function(){this.cx=0,this.cy=0,this.r=0,this.r0=0},Wu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Gu},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=2*Math.PI;t.moveTo(n+e.r,i),t.arc(n,i,e.r,0,r,!1),t.moveTo(n+e.r0,i),t.arc(n,i,e.r0,0,r,!0)},e}(ks);function Hu(t,e,n){var i=e.smooth,r=e.points;if(r&&r.length>=2){if(i){var o=function(t,e,n,i){var r,o,a,s,l=[],u=[],h=[],c=[];if(i){a=[1/0,1/0],s=[-1/0,-1/0];for(var p=0,d=t.length;plh[1]){if(a=!1,r)return a;var u=Math.abs(lh[0]-sh[1]),h=Math.abs(sh[0]-lh[1]);Math.min(u,h)>i.len()&&(u0){var c={duration:h.duration,delay:h.delay||0,easing:h.easing,done:o,force:!!o||!!a,setToFinal:!u,scope:t,during:a};l?e.animateFrom(n,c):e.animateTo(n,c)}else e.stopAnimation(),!l&&e.attr(n),a&&a(1),o&&o()}function vh(t,e,n,i,r,o){yh("update",t,e,n,i,r,o)}function mh(t,e,n,i,r,o){yh("enter",t,e,n,i,r,o)}function xh(t){if(!t.__zr)return!0;for(var e=0;eMath.abs(o[1])?o[0]>0?"right":"left":o[1]>0?"bottom":"top"}function Wh(t){return!t.isGroup}function Hh(t,e,n){if(t&&e){var i,r=(i={},t.traverse((function(t){Wh(t)&&t.anid&&(i[t.anid]=t)})),i);e.traverse((function(t){if(Wh(t)&&t.anid){var e=r[t.anid];if(e){var i=o(t);t.attr(o(e)),vh(t,i,n,rl(t).dataIndex)}}}))}function o(t){var e={x:t.x,y:t.y,rotation:t.rotation};return function(t){return null!=t.shape}(t)&&(e.shape=A({},t.shape)),e}}function Yh(t,e){return z(t,(function(t){var n=t[0];n=Mh(n,e.x),n=Ih(n,e.x+e.width);var i=t[1];return i=Mh(i,e.y),[n,i=Ih(i,e.y+e.height)]}))}function Xh(t,e){var n=Mh(t.x,e.x),i=Ih(t.x+t.width,e.x+e.width),r=Mh(t.y,e.y),o=Ih(t.y+t.height,e.y+e.height);if(i>=n&&o>=r)return{x:n,y:r,width:i-n,height:o-r}}function Uh(t,e,n){var i=A({rectHover:!0},e),r=i.style={strokeNoScale:!0};if(n=n||{x:-1,y:-1,width:2,height:2},t)return 0===t.indexOf("image://")?(r.image=t.slice(8),k(r,n),new Ns(i)):Ph(t.replace("path://",""),i,n,"center")}function Zh(t,e,n,i,r){for(var o=0,a=r[r.length-1];o=-1e-6)return!1;var f=t-r,g=e-o,y=qh(f,g,u,h)/d;if(y<0||y>1)return!1;var v=qh(f,g,c,p)/d;return!(v<0||v>1)}function qh(t,e,n,i){return t*i-n*e}function Kh(t){var e=t.itemTooltipOption,n=t.componentModel,i=t.itemName,r=U(e)?{formatter:e}:e,o=n.mainType,a=n.componentIndex,s={componentType:o,name:i,$vars:["name"]};s[o+"Index"]=a;var l=t.formatterParamsExtra;l&&E(G(l),(function(t){_t(s,t)||(s[t]=l[t],s.$vars.push(t))}));var u=rl(t.el);u.componentMainType=o,u.componentIndex=a,u.tooltipConfig={name:i,option:k({content:i,encodeHTMLContent:!0,formatterParams:s},r)}}function $h(t,e){var n;t.isGroup&&(n=e(t)),n||t.traverse(e)}function Jh(t,e){if(t)if(Y(t))for(var n=0;n-1?Lc:Oc;function zc(t,e){t=t.toUpperCase(),Nc[t]=new Cc(e),Rc[t]=e}function Vc(t){return Nc[t]}zc(Pc,{time:{month:["January","February","March","April","May","June","July","August","September","October","November","December"],monthAbbr:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayOfWeek:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayOfWeekAbbr:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},legend:{selector:{all:"All",inverse:"Inv"}},toolbox:{brush:{title:{rect:"Box Select",polygon:"Lasso Select",lineX:"Horizontally Select",lineY:"Vertically Select",keep:"Keep Selections",clear:"Clear Selections"}},dataView:{title:"Data View",lang:["Data View","Close","Refresh"]},dataZoom:{title:{zoom:"Zoom",back:"Zoom Reset"}},magicType:{title:{line:"Switch to Line Chart",bar:"Switch to Bar Chart",stack:"Stack",tiled:"Tile"}},restore:{title:"Restore"},saveAsImage:{title:"Save as Image",lang:["Right Click to Save Image"]}},series:{typeNames:{pie:"Pie chart",bar:"Bar chart",line:"Line chart",scatter:"Scatter plot",effectScatter:"Ripple scatter plot",radar:"Radar chart",tree:"Tree",treemap:"Treemap",boxplot:"Boxplot",candlestick:"Candlestick",k:"K line chart",heatmap:"Heat map",map:"Map",parallel:"Parallel coordinate map",lines:"Line graph",graph:"Relationship graph",sankey:"Sankey diagram",funnel:"Funnel chart",gauge:"Gauge",pictorialBar:"Pictorial bar",themeRiver:"Theme River Map",sunburst:"Sunburst",custom:"Custom chart",chart:"Chart"}},aria:{general:{withTitle:'This is a chart about "{title}"',withoutTitle:"This is a chart"},series:{single:{prefix:"",withName:" with type {seriesType} named {seriesName}.",withoutName:" with type {seriesType}."},multiple:{prefix:". It consists of {seriesCount} series count.",withName:" The {seriesId} series is a {seriesType} representing {seriesName}.",withoutName:" The {seriesId} series is a {seriesType}.",separator:{middle:"",end:""}}},data:{allData:"The data is as follows: ",partialData:"The first {displayCnt} items are: ",withName:"the data for {name} is {value}",withoutName:"{value}",separator:{middle:", ",end:". "}}}}),zc(Lc,{time:{month:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthAbbr:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayOfWeek:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayOfWeekAbbr:["日","一","二","三","四","五","六"]},legend:{selector:{all:"全选",inverse:"反选"}},toolbox:{brush:{title:{rect:"矩形选择",polygon:"圈选",lineX:"横向选择",lineY:"纵向选择",keep:"保持选择",clear:"清除选择"}},dataView:{title:"数据视图",lang:["数据视图","关闭","刷新"]},dataZoom:{title:{zoom:"区域缩放",back:"区域缩放还原"}},magicType:{title:{line:"切换为折线图",bar:"切换为柱状图",stack:"切换为堆叠",tiled:"切换为平铺"}},restore:{title:"还原"},saveAsImage:{title:"保存为图片",lang:["右键另存为图片"]}},series:{typeNames:{pie:"饼图",bar:"柱状图",line:"折线图",scatter:"散点图",effectScatter:"涟漪散点图",radar:"雷达图",tree:"树图",treemap:"矩形树图",boxplot:"箱型图",candlestick:"K线图",k:"K线图",heatmap:"热力图",map:"地图",parallel:"平行坐标图",lines:"线图",graph:"关系图",sankey:"桑基图",funnel:"漏斗图",gauge:"仪表盘图",pictorialBar:"象形柱图",themeRiver:"主题河流图",sunburst:"旭日图",custom:"自定义图表",chart:"图表"}},aria:{general:{withTitle:"这是一个关于“{title}”的图表。",withoutTitle:"这是一个图表,"},series:{single:{prefix:"",withName:"图表类型是{seriesType},表示{seriesName}。",withoutName:"图表类型是{seriesType}。"},multiple:{prefix:"它由{seriesCount}个图表系列组成。",withName:"第{seriesId}个系列是一个表示{seriesName}的{seriesType},",withoutName:"第{seriesId}个系列是一个{seriesType},",separator:{middle:";",end:"。"}}},data:{allData:"其数据是——",partialData:"其中,前{displayCnt}项是——",withName:"{name}的数据是{value}",withoutName:"{value}",separator:{middle:",",end:""}}}});var Bc=1e3,Fc=6e4,Gc=36e5,Wc=864e5,Hc=31536e6,Yc={year:"{yyyy}",month:"{MMM}",day:"{d}",hour:"{HH}:{mm}",minute:"{HH}:{mm}",second:"{HH}:{mm}:{ss}",millisecond:"{HH}:{mm}:{ss} {SSS}",none:"{yyyy}-{MM}-{dd} {HH}:{mm}:{ss} {SSS}"},Xc="{yyyy}-{MM}-{dd}",Uc={year:"{yyyy}",month:"{yyyy}-{MM}",day:Xc,hour:Xc+" "+Yc.hour,minute:Xc+" "+Yc.minute,second:Xc+" "+Yc.second,millisecond:Yc.none},Zc=["year","month","day","hour","minute","second","millisecond"],jc=["year","half-year","quarter","month","week","half-week","day","half-day","quarter-day","hour","minute","second","millisecond"];function qc(t,e){return"0000".substr(0,e-(t+="").length)+t}function Kc(t){switch(t){case"half-year":case"quarter":return"month";case"week":case"half-week":return"day";case"half-day":case"quarter-day":return"hour";default:return t}}function $c(t){return t===Kc(t)}function Jc(t,e,n,i){var r=uo(t),o=r[ep(n)](),a=r[np(n)]()+1,s=Math.floor((a-1)/3)+1,l=r[ip(n)](),u=r["get"+(n?"UTC":"")+"Day"](),h=r[rp(n)](),c=(h-1)%12+1,p=r[op(n)](),d=r[ap(n)](),f=r[sp(n)](),g=h>=12?"pm":"am",y=g.toUpperCase(),v=(i instanceof Cc?i:Vc(i||Ec)||Nc[Oc]).getModel("time"),m=v.get("month"),x=v.get("monthAbbr"),_=v.get("dayOfWeek"),b=v.get("dayOfWeekAbbr");return(e||"").replace(/{a}/g,g+"").replace(/{A}/g,y+"").replace(/{yyyy}/g,o+"").replace(/{yy}/g,qc(o%100+"",2)).replace(/{Q}/g,s+"").replace(/{MMMM}/g,m[a-1]).replace(/{MMM}/g,x[a-1]).replace(/{MM}/g,qc(a,2)).replace(/{M}/g,a+"").replace(/{dd}/g,qc(l,2)).replace(/{d}/g,l+"").replace(/{eeee}/g,_[u]).replace(/{ee}/g,b[u]).replace(/{e}/g,u+"").replace(/{HH}/g,qc(h,2)).replace(/{H}/g,h+"").replace(/{hh}/g,qc(c+"",2)).replace(/{h}/g,c+"").replace(/{mm}/g,qc(p,2)).replace(/{m}/g,p+"").replace(/{ss}/g,qc(d,2)).replace(/{s}/g,d+"").replace(/{SSS}/g,qc(f,3)).replace(/{S}/g,f+"")}function Qc(t,e){var n=uo(t),i=n[np(e)]()+1,r=n[ip(e)](),o=n[rp(e)](),a=n[op(e)](),s=n[ap(e)](),l=0===n[sp(e)](),u=l&&0===s,h=u&&0===a,c=h&&0===o,p=c&&1===r;return p&&1===i?"year":p?"month":c?"day":h?"hour":u?"minute":l?"second":"millisecond"}function tp(t,e,n){var i=j(t)?uo(t):t;switch(e=e||Qc(t,n)){case"year":return i[ep(n)]();case"half-year":return i[np(n)]()>=6?1:0;case"quarter":return Math.floor((i[np(n)]()+1)/4);case"month":return i[np(n)]();case"day":return i[ip(n)]();case"half-day":return i[rp(n)]()/24;case"hour":return i[rp(n)]();case"minute":return i[op(n)]();case"second":return i[ap(n)]();case"millisecond":return i[sp(n)]()}}function ep(t){return t?"getUTCFullYear":"getFullYear"}function np(t){return t?"getUTCMonth":"getMonth"}function ip(t){return t?"getUTCDate":"getDate"}function rp(t){return t?"getUTCHours":"getHours"}function op(t){return t?"getUTCMinutes":"getMinutes"}function ap(t){return t?"getUTCSeconds":"getSeconds"}function sp(t){return t?"getUTCMilliseconds":"getMilliseconds"}function lp(t){return t?"setUTCFullYear":"setFullYear"}function up(t){return t?"setUTCMonth":"setMonth"}function hp(t){return t?"setUTCDate":"setDate"}function cp(t){return t?"setUTCHours":"setHours"}function pp(t){return t?"setUTCMinutes":"setMinutes"}function dp(t){return t?"setUTCSeconds":"setSeconds"}function fp(t){return t?"setUTCMilliseconds":"setMilliseconds"}function gp(t){if(!vo(t))return U(t)?t:"-";var e=(t+"").split(".");return e[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(e.length>1?"."+e[1]:"")}function yp(t,e){return t=(t||"").toLowerCase().replace(/-(.)/g,(function(t,e){return e.toUpperCase()})),e&&t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),t}var vp=st;function mp(t,e,n){function i(t){return t&&ut(t)?t:"-"}function r(t){return!(null==t||isNaN(t)||!isFinite(t))}var o="time"===e,a=t instanceof Date;if(o||a){var s=o?uo(t):t;if(!isNaN(+s))return Jc(s,"{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}",n);if(a)return"-"}if("ordinal"===e)return Z(t)?i(t):j(t)&&r(t)?t+"":"-";var l=yo(t);return r(l)?gp(l):Z(t)?i(t):"boolean"==typeof t?t+"":"-"}var xp=["a","b","c","d","e","f","g"],_p=function(t,e){return"{"+t+(null==e?"":e)+"}"};function bp(t,e,n){Y(e)||(e=[e]);var i=e.length;if(!i)return"";for(var r=e[0].$vars||[],o=0;o':'':{renderMode:o,content:"{"+(n.markerId||"markerX")+"|} ",style:"subItem"===r?{width:4,height:4,borderRadius:2,backgroundColor:i}:{width:10,height:10,borderRadius:5,backgroundColor:i}}:""}function Sp(t,e){return e=e||"transparent",U(t)?t:q(t)&&t.colorStops&&(t.colorStops[0]||{}).color||e}function Mp(t,e){if("_blank"===e||"blank"===e){var n=window.open();n.opener=null,n.location.href=t}else window.open(t,e)}var Ip=E,Tp=["left","right","top","bottom","width","height"],Cp=[["width","left","right"],["height","top","bottom"]];function Dp(t,e,n,i,r){var o=0,a=0;null==i&&(i=1/0),null==r&&(r=1/0);var s=0;e.eachChild((function(l,u){var h,c,p=l.getBoundingRect(),d=e.childAt(u+1),f=d&&d.getBoundingRect();if("horizontal"===t){var g=p.width+(f?-f.x+p.x:0);(h=o+g)>i||l.newline?(o=0,h=g,a+=s+n,s=p.height):s=Math.max(s,p.height)}else{var y=p.height+(f?-f.y+p.y:0);(c=a+y)>r||l.newline?(o+=s+n,a=0,c=y,s=p.width):s=Math.max(s,p.width)}l.newline||(l.x=o,l.y=a,l.markRedraw(),"horizontal"===t?o=h+n:a=c+n)}))}var Ap=Dp;H(Dp,"vertical"),H(Dp,"horizontal");function kp(t,e,n){n=vp(n||0);var i=e.width,r=e.height,o=$r(t.left,i),a=$r(t.top,r),s=$r(t.right,i),l=$r(t.bottom,r),u=$r(t.width,i),h=$r(t.height,r),c=n[2]+n[0],p=n[1]+n[3],d=t.aspect;switch(isNaN(u)&&(u=i-s-p-o),isNaN(h)&&(h=r-l-c-a),null!=d&&(isNaN(u)&&isNaN(h)&&(d>i/r?u=.8*i:h=.8*r),isNaN(u)&&(u=d*h),isNaN(h)&&(h=u/d)),isNaN(o)&&(o=i-s-u-p),isNaN(a)&&(a=r-l-h-c),t.left||t.right){case"center":o=i/2-u/2-n[3];break;case"right":o=i-u-p}switch(t.top||t.bottom){case"middle":case"center":a=r/2-h/2-n[0];break;case"bottom":a=r-h-c}o=o||0,a=a||0,isNaN(u)&&(u=i-p-o-(s||0)),isNaN(h)&&(h=r-c-a-(l||0));var f=new ze(o+n[3],a+n[0],u,h);return f.margin=n,f}function Lp(t,e,n,i,r,o){var a,s=!r||!r.hv||r.hv[0],l=!r||!r.hv||r.hv[1],u=r&&r.boundingMode||"all";if((o=o||t).x=t.x,o.y=t.y,!s&&!l)return!1;if("raw"===u)a="group"===t.type?new ze(0,0,+e.width||0,+e.height||0):t.getBoundingRect();else if(a=t.getBoundingRect(),t.needLocalTransform()){var h=t.getLocalTransform();(a=a.clone()).applyTransform(h)}var c=kp(k({width:a.width,height:a.height},e),n,i),p=s?c.x-a.x:0,d=l?c.y-a.y:0;return"raw"===u?(o.x=p,o.y=d):(o.x+=p,o.y+=d),o===t&&t.markRedraw(),!0}function Pp(t){var e=t.layoutMode||t.constructor.layoutMode;return q(e)?e:e?{type:e}:null}function Op(t,e,n){var i=n&&n.ignoreSize;!Y(i)&&(i=[i,i]);var r=a(Cp[0],0),o=a(Cp[1],1);function a(n,r){var o={},a=0,u={},h=0;if(Ip(n,(function(e){u[e]=t[e]})),Ip(n,(function(t){s(e,t)&&(o[t]=u[t]=e[t]),l(o,t)&&a++,l(u,t)&&h++})),i[r])return l(e,n[1])?u[n[2]]=null:l(e,n[2])&&(u[n[1]]=null),u;if(2!==h&&a){if(a>=2)return o;for(var c=0;c=0;a--)o=C(o,n[a],!0);e.defaultOption=o}return e.defaultOption},e.prototype.getReferringComponents=function(t,e){var n=t+"Index",i=t+"Id";return Yo(this.ecModel,t,{index:this.get(n,!0),id:this.get(i,!0)},e)},e.prototype.getBoxLayoutParams=function(){var t=this;return{left:t.get("left"),top:t.get("top"),right:t.get("right"),bottom:t.get("bottom"),width:t.get("width"),height:t.get("height")}},e.prototype.getZLevelKey=function(){return""},e.prototype.setZLevel=function(t){this.option.zlevel=t},e.protoInitialize=function(){var t=e.prototype;t.type="component",t.id="",t.name="",t.mainType="",t.subType="",t.componentIndex=0}(),e}(Cc);Jo(zp,Cc),na(zp),function(t){var e={};t.registerSubTypeDefaulter=function(t,n){var i=Ko(t);e[i.main]=n},t.determineSubType=function(n,i){var r=i.type;if(!r){var o=Ko(n).main;t.hasSubTypes(n)&&e[o]&&(r=e[o](i))}return r}}(zp),function(t,e){function n(t,e){return t[e]||(t[e]={predecessor:[],successor:[]}),t[e]}t.topologicalTravel=function(t,i,r,o){if(t.length){var a=function(t){var i={},r=[];return E(t,(function(o){var a=n(i,o),s=function(t,e){var n=[];return E(t,(function(t){P(e,t)>=0&&n.push(t)})),n}(a.originalDeps=e(o),t);a.entryCount=s.length,0===a.entryCount&&r.push(o),E(s,(function(t){P(a.predecessor,t)<0&&a.predecessor.push(t);var e=n(i,t);P(e.successor,t)<0&&e.successor.push(o)}))})),{graph:i,noEntryList:r}}(i),s=a.graph,l=a.noEntryList,u={};for(E(t,(function(t){u[t]=!0}));l.length;){var h=l.pop(),c=s[h],p=!!u[h];p&&(r.call(o,h,c.originalDeps.slice()),delete u[h]),E(c.successor,p?f:d)}E(u,(function(){var t="";throw new Error(t)}))}function d(t){s[t].entryCount--,0===s[t].entryCount&&l.push(t)}function f(t){u[t]=!0,d(t)}}}(zp,(function(t){var e=[];E(zp.getClassesByMainType(t),(function(t){e=e.concat(t.dependencies||t.prototype.dependencies||[])})),e=z(e,(function(t){return Ko(t).main})),"dataset"!==t&&P(e,"dataset")<=0&&e.unshift("dataset");return e}));var Vp="";"undefined"!=typeof navigator&&(Vp=navigator.platform||"");var Bp="rgba(0, 0, 0, 0.2)",Fp={darkMode:"auto",colorBy:"series",color:["#5470c6","#91cc75","#fac858","#ee6666","#73c0de","#3ba272","#fc8452","#9a60b4","#ea7ccc"],gradientColor:["#f6efa6","#d88273","#bf444c"],aria:{decal:{decals:[{color:Bp,dashArrayX:[1,0],dashArrayY:[2,5],symbolSize:1,rotation:Math.PI/6},{color:Bp,symbol:"circle",dashArrayX:[[8,8],[0,8,8,0]],dashArrayY:[6,0],symbolSize:.8},{color:Bp,dashArrayX:[1,0],dashArrayY:[4,3],rotation:-Math.PI/4},{color:Bp,dashArrayX:[[6,6],[0,6,6,0]],dashArrayY:[6,0]},{color:Bp,dashArrayX:[[1,0],[1,6]],dashArrayY:[1,0,6,0],rotation:Math.PI/4},{color:Bp,symbol:"triangle",dashArrayX:[[9,9],[0,9,9,0]],dashArrayY:[7,2],symbolSize:.75}]}},textStyle:{fontFamily:Vp.match(/^Win/)?"Microsoft YaHei":"sans-serif",fontSize:12,fontStyle:"normal",fontWeight:"normal"},blendMode:null,stateAnimation:{duration:300,easing:"cubicOut"},animation:"auto",animationDuration:1e3,animationDurationUpdate:500,animationEasing:"cubicInOut",animationEasingUpdate:"cubicInOut",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1},Gp=yt(["tooltip","label","itemName","itemId","itemGroupId","itemChildGroupId","seriesName"]),Wp="original",Hp="arrayRows",Yp="objectRows",Xp="keyedColumns",Up="typedArray",Zp="unknown",jp="column",qp="row",Kp=1,$p=2,Jp=3,Qp=Vo();function td(t,e,n){var i={},r=nd(e);if(!r||!t)return i;var o,a,s=[],l=[],u=e.ecModel,h=Qp(u).datasetMap,c=r.uid+"_"+n.seriesLayoutBy;E(t=t.slice(),(function(e,n){var r=q(e)?e:t[n]={name:e};"ordinal"===r.type&&null==o&&(o=n,a=f(r)),i[r.name]=[]}));var p=h.get(c)||h.set(c,{categoryWayDim:a,valueWayDim:0});function d(t,e,n){for(var i=0;ie)return t[i];return t[n-1]}(i,a):n;if((h=h||n)&&h.length){var c=h[l];return r&&(u[r]=c),s.paletteIdx=(l+1)%h.length,c}}var fd="\0_ec_inner";var gd=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(t,e,n,i,r,o){i=i||{},this.option=null,this._theme=new Cc(i),this._locale=new Cc(r),this._optionManager=o},e.prototype.setOption=function(t,e,n){var i=md(e);this._optionManager.setOption(t,n,i),this._resetOption(null,i)},e.prototype.resetOption=function(t,e){return this._resetOption(t,md(e))},e.prototype._resetOption=function(t,e){var n=!1,i=this._optionManager;if(!t||"recreate"===t){var r=i.mountOption("recreate"===t);0,this.option&&"recreate"!==t?(this.restoreData(),this._mergeOption(r,e)):ld(this,r),n=!0}if("timeline"!==t&&"media"!==t||this.restoreData(),!t||"recreate"===t||"timeline"===t){var o=i.getTimelineOption(this);o&&(n=!0,this._mergeOption(o,e))}if(!t||"recreate"===t||"media"===t){var a=i.getMediaOption(this);a.length&&E(a,(function(t){n=!0,this._mergeOption(t,e)}),this)}return n},e.prototype.mergeOption=function(t){this._mergeOption(t,null)},e.prototype._mergeOption=function(t,e){var n=this.option,i=this._componentsMap,r=this._componentsCount,o=[],a=yt(),s=e&&e.replaceMergeMainTypeMap;Qp(this).datasetMap=yt(),E(t,(function(t,e){null!=t&&(zp.hasClass(e)?e&&(o.push(e),a.set(e,!0)):n[e]=null==n[e]?T(t):C(n[e],t,!0))})),s&&s.each((function(t,e){zp.hasClass(e)&&!a.get(e)&&(o.push(e),a.set(e,!0))})),zp.topologicalTravel(o,zp.getAllClassMainTypes(),(function(e){var o=function(t,e,n){var i=od.get(e);if(!i)return n;var r=i(t);return r?n.concat(r):n}(this,e,To(t[e])),a=i.get(e),l=a?s&&s.get(e)?"replaceMerge":"normalMerge":"replaceAll",u=Lo(a,o,l);(function(t,e,n){E(t,(function(t){var i=t.newOption;q(i)&&(t.keyInfo.mainType=e,t.keyInfo.subType=function(t,e,n,i){return e.type?e.type:n?n.subType:i.determineSubType(t,e)}(e,i,t.existing,n))}))})(u,e,zp),n[e]=null,i.set(e,null),r.set(e,0);var h,c=[],p=[],d=0;E(u,(function(t,n){var i=t.existing,r=t.newOption;if(r){var o="series"===e,a=zp.getClass(e,t.keyInfo.subType,!o);if(!a)return;if("tooltip"===e){if(h)return void 0;h=!0}if(i&&i.constructor===a)i.name=t.keyInfo.name,i.mergeOption(r,this),i.optionUpdated(r,!1);else{var s=A({componentIndex:n},t.keyInfo);A(i=new a(r,this,this,s),s),t.brandNew&&(i.__requireNewView=!0),i.init(r,this,this),i.optionUpdated(null,!0)}}else i&&(i.mergeOption({},this),i.optionUpdated({},!1));i?(c.push(i.option),p.push(i),d++):(c.push(void 0),p.push(void 0))}),this),n[e]=c,i.set(e,p),r.set(e,d),"series"===e&&ad(this)}),this),this._seriesIndices||ad(this)},e.prototype.getOption=function(){var t=T(this.option);return E(t,(function(e,n){if(zp.hasClass(n)){for(var i=To(e),r=i.length,o=!1,a=r-1;a>=0;a--)i[a]&&!Eo(i[a])?o=!0:(i[a]=null,!o&&r--);i.length=r,t[n]=i}})),delete t[fd],t},e.prototype.getTheme=function(){return this._theme},e.prototype.getLocaleModel=function(){return this._locale},e.prototype.setUpdatePayload=function(t){this._payload=t},e.prototype.getUpdatePayload=function(){return this._payload},e.prototype.getComponent=function(t,e){var n=this._componentsMap.get(t);if(n){var i=n[e||0];if(i)return i;if(null==e)for(var r=0;r=e:"max"===n?t<=e:t===e})(i[a],t,o)||(r=!1)}})),r}var Td=E,Cd=q,Dd=["areaStyle","lineStyle","nodeStyle","linkStyle","chordStyle","label","labelLine"];function Ad(t){var e=t&&t.itemStyle;if(e)for(var n=0,i=Dd.length;n=0;g--){var y=t[g];if(s||(p=y.data.rawIndexOf(y.stackedByDimension,c)),p>=0){var v=y.data.getByRawIndex(y.stackResultDimension,p);if("all"===l||"positive"===l&&v>0||"negative"===l&&v<0||"samesign"===l&&d>=0&&v>0||"samesign"===l&&d<=0&&v<0){d=ro(d,v),f=v;break}}}return i[0]=d,i[1]=f,i}))}))}var Zd,jd,qd,Kd,$d,Jd=function(t){this.data=t.data||(t.sourceFormat===Xp?{}:[]),this.sourceFormat=t.sourceFormat||Zp,this.seriesLayoutBy=t.seriesLayoutBy||jp,this.startIndex=t.startIndex||0,this.dimensionsDetectedCount=t.dimensionsDetectedCount,this.metaRawOption=t.metaRawOption;var e=this.dimensionsDefine=t.dimensionsDefine;if(e)for(var n=0;nu&&(u=d)}s[0]=l,s[1]=u}},i=function(){return this._data?this._data.length/this._dimSize:0};function r(t){for(var e=0;e=0&&(s=o.interpolatedValue[l])}return null!=s?s+"":""})):void 0},t.prototype.getRawValue=function(t,e){return mf(this.getData(e),t)},t.prototype.formatTooltip=function(t,e,n){},t}();function bf(t){var e,n;return q(t)?t.type&&(n=t):e=t,{text:e,frag:n}}function wf(t){return new Sf(t)}var Sf=function(){function t(t){t=t||{},this._reset=t.reset,this._plan=t.plan,this._count=t.count,this._onDirty=t.onDirty,this._dirty=!0}return t.prototype.perform=function(t){var e,n=this._upstream,i=t&&t.skip;if(this._dirty&&n){var r=this.context;r.data=r.outputData=n.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this),this._plan&&!i&&(e=this._plan(this.context));var o,a=h(this._modBy),s=this._modDataCount||0,l=h(t&&t.modBy),u=t&&t.modDataCount||0;function h(t){return!(t>=1)&&(t=1),t}a===l&&s===u||(e="reset"),(this._dirty||"reset"===e)&&(this._dirty=!1,o=this._doReset(i)),this._modBy=l,this._modDataCount=u;var c=t&&t.step;if(this._dueEnd=n?n._outputDueEnd:this._count?this._count(this.context):1/0,this._progress){var p=this._dueIndex,d=Math.min(null!=c?this._dueIndex+c:1/0,this._dueEnd);if(!i&&(o||p1&&i>0?s:a}};return o;function a(){return e=t?null:oe},gte:function(t,e){return t>=e}},Af=function(){function t(t,e){if(!j(e)){var n="";0,wo(n)}this._opFn=Df[t],this._rvalFloat=yo(e)}return t.prototype.evaluate=function(t){return j(t)?this._opFn(t,this._rvalFloat):this._opFn(yo(t),this._rvalFloat)},t}(),kf=function(){function t(t,e){var n="desc"===t;this._resultLT=n?1:-1,null==e&&(e=n?"min":"max"),this._incomparable="min"===e?-1/0:1/0}return t.prototype.evaluate=function(t,e){var n=j(t)?t:yo(t),i=j(e)?e:yo(e),r=isNaN(n),o=isNaN(i);if(r&&(n=this._incomparable),o&&(i=this._incomparable),r&&o){var a=U(t),s=U(e);a&&(n=s?t:0),s&&(i=a?e:0)}return ni?-this._resultLT:0},t}(),Lf=function(){function t(t,e){this._rval=e,this._isEQ=t,this._rvalTypeof=typeof e,this._rvalFloat=yo(e)}return t.prototype.evaluate=function(t){var e=t===this._rval;if(!e){var n=typeof t;n===this._rvalTypeof||"number"!==n&&"number"!==this._rvalTypeof||(e=yo(t)===this._rvalFloat)}return this._isEQ?e:!e},t}();function Pf(t,e){return"eq"===t||"ne"===t?new Lf("eq"===t,e):_t(Df,t)?new Af(t,e):null}var Of=function(){function t(){}return t.prototype.getRawData=function(){throw new Error("not supported")},t.prototype.getRawDataItem=function(t){throw new Error("not supported")},t.prototype.cloneRawData=function(){},t.prototype.getDimensionInfo=function(t){},t.prototype.cloneAllDimensionInfo=function(){},t.prototype.count=function(){},t.prototype.retrieveValue=function(t,e){},t.prototype.retrieveValueFromItem=function(t,e){},t.prototype.convertValue=function(t,e){return If(t,e)},t}();function Rf(t){var e=t.sourceFormat;if(!Ff(e)){var n="";0,wo(n)}return t.data}function Nf(t){var e=t.sourceFormat,n=t.data;if(!Ff(e)){var i="";0,wo(i)}if(e===Hp){for(var r=[],o=0,a=n.length;o65535?Hf:Yf}function qf(t,e,n,i,r){var o=Zf[n||"float"];if(r){var a=t[e],s=a&&a.length;if(s!==i){for(var l=new o(i),u=0;ug[1]&&(g[1]=f)}return this._rawCount=this._count=s,{start:a,end:s}},t.prototype._initDataFromProvider=function(t,e,n){for(var i=this._provider,r=this._chunks,o=this._dimensions,a=o.length,s=this._rawExtent,l=z(o,(function(t){return t.property})),u=0;uy[1]&&(y[1]=g)}}!i.persistent&&i.clean&&i.clean(),this._rawCount=this._count=e,this._extent=[]},t.prototype.count=function(){return this._count},t.prototype.get=function(t,e){if(!(e>=0&&e=0&&e=this._rawCount||t<0)return-1;if(!this._indices)return t;var e=this._indices,n=e[t];if(null!=n&&nt))return o;r=o-1}}return-1},t.prototype.indicesOfNearest=function(t,e,n){var i=this._chunks[t],r=[];if(!i)return r;null==n&&(n=1/0);for(var o=1/0,a=-1,s=0,l=0,u=this.count();l=0&&a<0)&&(o=c,a=h,s=0),h===a&&(r[s++]=l))}return r.length=s,r},t.prototype.getIndices=function(){var t,e=this._indices;if(e){var n=e.constructor,i=this._count;if(n===Array){t=new n(i);for(var r=0;r=u&&x<=h||isNaN(x))&&(a[s++]=d),d++}p=!0}else if(2===r){f=c[i[0]];var y=c[i[1]],v=t[i[1]][0],m=t[i[1]][1];for(g=0;g=u&&x<=h||isNaN(x))&&(_>=v&&_<=m||isNaN(_))&&(a[s++]=d),d++}p=!0}}if(!p)if(1===r)for(g=0;g=u&&x<=h||isNaN(x))&&(a[s++]=b)}else for(g=0;gt[M][1])&&(w=!1)}w&&(a[s++]=e.getRawIndex(g))}return sy[1]&&(y[1]=g)}}}},t.prototype.lttbDownSample=function(t,e){var n,i,r,o=this.clone([t],!0),a=o._chunks[t],s=this.count(),l=0,u=Math.floor(1/e),h=this.getRawIndex(0),c=new(jf(this._rawCount))(Math.min(2*(Math.ceil(s/u)+2),s));c[l++]=h;for(var p=1;pn&&(n=i,r=I)}M>0&&M<_-x&&(c[l++]=Math.min(S,r),r=Math.max(S,r)),c[l++]=r,h=r}return c[l++]=this.getRawIndex(s-1),o._count=l,o._indices=c,o.getRawIndex=this._getRawIdx,o},t.prototype.minmaxDownSample=function(t,e){for(var n=this.clone([t],!0),i=n._chunks,r=Math.floor(1/e),o=i[t],a=this.count(),s=new(jf(this._rawCount))(2*Math.ceil(a/r)),l=0,u=0;ua&&(f=a-u);for(var g=0;gd&&(d=y,p=u+g)}var v=this.getRawIndex(h),m=this.getRawIndex(p);hu-d&&(s=u-d,a.length=s);for(var f=0;fh[1]&&(h[1]=y),c[p++]=v}return r._count=p,r._indices=c,r._updateGetRawIdx(),r},t.prototype.each=function(t,e){if(this._count)for(var n=t.length,i=this._chunks,r=0,o=this.count();ra&&(a=l)}return i=[o,a],this._extent[t]=i,i},t.prototype.getRawDataItem=function(t){var e=this.getRawIndex(t);if(this._provider.persistent)return this._provider.getItem(e);for(var n=[],i=this._chunks,r=0;r=0?this._indices[t]:-1},t.prototype._updateGetRawIdx=function(){this.getRawIndex=this._indices?this._getRawIdx:this._getRawIdxIdentity},t.internalField=function(){function t(t,e,n,i){return If(t[i],this._dimensions[i])}Gf={arrayRows:t,objectRows:function(t,e,n,i){return If(t[e],this._dimensions[i])},keyedColumns:t,original:function(t,e,n,i){var r=t&&(null==t.value?t:t.value);return If(r instanceof Array?r[i]:r,this._dimensions[i])},typedArray:function(t,e,n,i){return t[i]}}}(),t}(),$f=function(){function t(t){this._sourceList=[],this._storeList=[],this._upstreamSignList=[],this._versionSignBase=0,this._dirty=!0,this._sourceHost=t}return t.prototype.dirty=function(){this._setLocalSource([],[]),this._storeList=[],this._dirty=!0},t.prototype._setLocalSource=function(t,e){this._sourceList=t,this._upstreamSignList=e,this._versionSignBase++,this._versionSignBase>9e10&&(this._versionSignBase=0)},t.prototype._getVersionSign=function(){return this._sourceHost.uid+"_"+this._versionSignBase},t.prototype.prepareSource=function(){this._isDirty()&&(this._createSource(),this._dirty=!1)},t.prototype._createSource=function(){this._setLocalSource([],[]);var t,e,n=this._sourceHost,i=this._getUpstreamSourceManagers(),r=!!i.length;if(Qf(n)){var o=n,a=void 0,s=void 0,l=void 0;if(r){var u=i[0];u.prepareSource(),a=(l=u.getSource()).data,s=l.sourceFormat,e=[u._getVersionSign()]}else s=$(a=o.get("data",!0))?Up:Wp,e=[];var h=this._getSourceMetaRawOption()||{},c=l&&l.metaRawOption||{},p=rt(h.seriesLayoutBy,c.seriesLayoutBy)||null,d=rt(h.sourceHeader,c.sourceHeader),f=rt(h.dimensions,c.dimensions);t=p!==c.seriesLayoutBy||!!d!=!!c.sourceHeader||f?[tf(a,{seriesLayoutBy:p,sourceHeader:d,dimensions:f},s)]:[]}else{var g=n;if(r){var y=this._applyTransform(i);t=y.sourceList,e=y.upstreamSignList}else{t=[tf(g.get("source",!0),this._getSourceMetaRawOption(),null)],e=[]}}this._setLocalSource(t,e)},t.prototype._applyTransform=function(t){var e,n=this._sourceHost,i=n.get("transform",!0),r=n.get("fromTransformResult",!0);if(null!=r){var o="";1!==t.length&&tg(o)}var a,s=[],l=[];return E(t,(function(t){t.prepareSource();var e=t.getSource(r||0),n="";null==r||e||tg(n),s.push(e),l.push(t._getVersionSign())})),i?e=function(t,e,n){var i=To(t),r=i.length,o="";r||wo(o);for(var a=0,s=r;a1||n>0&&!t.noHeader;return E(t.blocks,(function(t){var n=lg(t);n>=e&&(e=n+ +(i&&(!n||ag(t)&&!t.noHeader)))})),e}return 0}function ug(t,e,n,i){var r,o=e.noHeader,a=(r=lg(e),{html:ig[r],richText:rg[r]}),s=[],l=e.blocks||[];lt(!l||Y(l)),l=l||[];var u=t.orderMode;if(e.sortBlocks&&u){l=l.slice();var h={valueAsc:"asc",valueDesc:"desc"};if(_t(h,u)){var c=new kf(h[u],null);l.sort((function(t,e){return c.evaluate(t.sortParam,e.sortParam)}))}else"seriesDesc"===u&&l.reverse()}E(l,(function(n,r){var o=e.valueFormatter,l=sg(n)(o?A(A({},t),{valueFormatter:o}):t,n,r>0?a.html:0,i);null!=l&&s.push(l)}));var p="richText"===t.renderMode?s.join(a.richText):pg(i,s.join(""),o?n:a.html);if(o)return p;var d=mp(e.header,"ordinal",t.useUTC),f=ng(i,t.renderMode).nameStyle,g=eg(i);return"richText"===t.renderMode?dg(t,d,f)+a.richText+p:pg(i,'
'+re(d)+"
"+p,n)}function hg(t,e,n,i){var r=t.renderMode,o=e.noName,a=e.noValue,s=!e.markerType,l=e.name,u=t.useUTC,h=e.valueFormatter||t.valueFormatter||function(t){return z(t=Y(t)?t:[t],(function(t,e){return mp(t,Y(d)?d[e]:d,u)}))};if(!o||!a){var c=s?"":t.markupStyleCreator.makeTooltipMarker(e.markerType,e.markerColor||"#333",r),p=o?"":mp(l,"ordinal",u),d=e.valueType,f=a?[]:h(e.value,e.dataIndex),g=!s||!o,y=!s&&o,v=ng(i,r),m=v.nameStyle,x=v.valueStyle;return"richText"===r?(s?"":c)+(o?"":dg(t,p,m))+(a?"":function(t,e,n,i,r){var o=[r],a=i?10:20;return n&&o.push({padding:[0,0,0,a],align:"right"}),t.markupStyleCreator.wrapRichTextStyle(Y(e)?e.join(" "):e,o)}(t,f,g,y,x)):pg(i,(s?"":c)+(o?"":function(t,e,n){return''+re(t)+""}(p,!s,m))+(a?"":function(t,e,n,i){var r=n?"10px":"20px",o=e?"float:right;margin-left:"+r:"";return t=Y(t)?t:[t],''+z(t,(function(t){return re(t)})).join("  ")+""}(f,g,y,x)),n)}}function cg(t,e,n,i,r,o){if(t)return sg(t)({useUTC:r,renderMode:n,orderMode:i,markupStyleCreator:e,valueFormatter:t.valueFormatter},t,0,o)}function pg(t,e,n){return'
'+e+'
'}function dg(t,e,n){return t.markupStyleCreator.wrapRichTextStyle(e,n)}function fg(t,e){return Sp(t.getData().getItemVisual(e,"style")[t.visualDrawType])}function gg(t,e){var n=t.get("padding");return null!=n?n:"richText"===e?[8,10]:10}var yg=function(){function t(){this.richTextStyles={},this._nextStyleNameId=mo()}return t.prototype._generateStyleName=function(){return"__EC_aUTo_"+this._nextStyleNameId++},t.prototype.makeTooltipMarker=function(t,e,n){var i="richText"===n?this._generateStyleName():null,r=wp({color:e,type:t,renderMode:n,markerId:i});return U(r)?r:(this.richTextStyles[i]=r.style,r.content)},t.prototype.wrapRichTextStyle=function(t,e){var n={};Y(e)?E(e,(function(t){return A(n,t)})):A(n,e);var i=this._generateStyleName();return this.richTextStyles[i]=n,"{"+i+"|"+t+"}"},t}();function vg(t){var e,n,i,r,o=t.series,a=t.dataIndex,s=t.multipleSeries,l=o.getData(),u=l.mapDimensionsAll("defaultedTooltip"),h=u.length,c=o.getRawValue(a),p=Y(c),d=fg(o,a);if(h>1||p&&!h){var f=function(t,e,n,i,r){var o=e.getData(),a=V(t,(function(t,e,n){var i=o.getDimensionInfo(n);return t||i&&!1!==i.tooltip&&null!=i.displayName}),!1),s=[],l=[],u=[];function h(t,e){var n=o.getDimensionInfo(e);n&&!1!==n.otherDims.tooltip&&(a?u.push(og("nameValue",{markerType:"subItem",markerColor:r,name:n.displayName,value:t,valueType:n.type})):(s.push(t),l.push(n.type)))}return i.length?E(i,(function(t){h(mf(o,n,t),t)})):E(t,h),{inlineValues:s,inlineValueTypes:l,blocks:u}}(c,o,a,u,d);e=f.inlineValues,n=f.inlineValueTypes,i=f.blocks,r=f.inlineValues[0]}else if(h){var g=l.getDimensionInfo(u[0]);r=e=mf(l,a,u[0]),n=g.type}else r=e=p?c[0]:c;var y=No(o),v=y&&o.name||"",m=l.getName(a),x=s?v:m;return og("section",{header:v,noHeader:s||!y,sortParam:r,blocks:[og("nameValue",{markerType:"item",markerColor:d,name:x,noName:!ut(x),value:e,valueType:n,dataIndex:a})].concat(i||[])})}var mg=Vo();function xg(t,e){return t.getName(e)||t.getId(e)}var _g="__universalTransitionEnabled",bg=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._selectedDataIndicesMap={},e}return n(e,t),e.prototype.init=function(t,e,n){this.seriesIndex=this.componentIndex,this.dataTask=wf({count:Sg,reset:Mg}),this.dataTask.context={model:this},this.mergeDefaultAndTheme(t,n),(mg(this).sourceManager=new $f(this)).prepareSource();var i=this.getInitialData(t,n);Tg(i,this),this.dataTask.context.data=i,mg(this).dataBeforeProcessed=i,wg(this),this._initSelectedMapFromData(i)},e.prototype.mergeDefaultAndTheme=function(t,e){var n=Pp(this),i=n?Rp(t):{},r=this.subType;zp.hasClass(r)&&(r+="Series"),C(t,e.getTheme().get(this.subType)),C(t,this.getDefaultOption()),Co(t,"label",["show"]),this.fillDataTextStyle(t.data),n&&Op(t,i,n)},e.prototype.mergeOption=function(t,e){t=C(this.option,t,!0),this.fillDataTextStyle(t.data);var n=Pp(this);n&&Op(this.option,t,n);var i=mg(this).sourceManager;i.dirty(),i.prepareSource();var r=this.getInitialData(t,e);Tg(r,this),this.dataTask.dirty(),this.dataTask.context.data=r,mg(this).dataBeforeProcessed=r,wg(this),this._initSelectedMapFromData(r)},e.prototype.fillDataTextStyle=function(t){if(t&&!$(t))for(var e=["show"],n=0;nthis.getShallow("animationThreshold")&&(e=!1),!!e},e.prototype.restoreData=function(){this.dataTask.dirty()},e.prototype.getColorFromPalette=function(t,e,n){var i=this.ecModel,r=cd.prototype.getColorFromPalette.call(this,t,e,n);return r||(r=i.getColorFromPalette(t,e,n)),r},e.prototype.coordDimToDataDim=function(t){return this.getRawData().mapDimensionsAll(t)},e.prototype.getProgressive=function(){return this.get("progressive")},e.prototype.getProgressiveThreshold=function(){return this.get("progressiveThreshold")},e.prototype.select=function(t,e){this._innerSelect(this.getData(e),t)},e.prototype.unselect=function(t,e){var n=this.option.selectedMap;if(n){var i=this.option.selectedMode,r=this.getData(e);if("series"===i||"all"===n)return this.option.selectedMap={},void(this._selectedDataIndicesMap={});for(var o=0;o=0&&n.push(r)}return n},e.prototype.isSelected=function(t,e){var n=this.option.selectedMap;if(!n)return!1;var i=this.getData(e);return("all"===n||n[xg(i,t)])&&!i.getItemModel(t).get(["select","disabled"])},e.prototype.isUniversalTransitionEnabled=function(){if(this[_g])return!0;var t=this.option.universalTransition;return!!t&&(!0===t||t&&t.enabled)},e.prototype._innerSelect=function(t,e){var n,i,r=this.option,o=r.selectedMode,a=e.length;if(o&&a)if("series"===o)r.selectedMap="all";else if("multiple"===o){q(r.selectedMap)||(r.selectedMap={});for(var s=r.selectedMap,l=0;l0&&this._innerSelect(t,e)}},e.registerClass=function(t){return zp.registerClass(t)},e.protoInitialize=function(){var t=e.prototype;t.type="series.__base__",t.seriesIndex=0,t.ignoreStyleOnData=!1,t.hasSymbolVisual=!1,t.defaultSymbol="circle",t.visualStyleAccessPath="itemStyle",t.visualDrawType="fill"}(),e}(zp);function wg(t){var e=t.name;No(t)||(t.name=function(t){var e=t.getRawData(),n=e.mapDimensionsAll("seriesName"),i=[];return E(n,(function(t){var n=e.getDimensionInfo(t);n.displayName&&i.push(n.displayName)})),i.join(" ")}(t)||e)}function Sg(t){return t.model.getRawData().count()}function Mg(t){var e=t.model;return e.setData(e.getRawData().cloneShallow()),Ig}function Ig(t,e){e.outputData&&t.end>e.outputData.count()&&e.model.getRawData().cloneShallow(e.outputData)}function Tg(t,e){E(vt(t.CHANGABLE_METHODS,t.DOWNSAMPLE_METHODS),(function(n){t.wrapMethod(n,H(Cg,e))}))}function Cg(t,e){var n=Dg(t);return n&&n.setOutputEnd((e||this).count()),e}function Dg(t){var e=(t.ecModel||{}).scheduler,n=e&&e.getPipeline(t.uid);if(n){var i=n.currentTask;if(i){var r=i.agentStubMap;r&&(i=r.get(t.uid))}return i}}R(bg,_f),R(bg,cd),Jo(bg,zp);var Ag=function(){function t(){this.group=new Br,this.uid=Ac("viewComponent")}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){},t.prototype.updateLayout=function(t,e,n,i){},t.prototype.updateVisual=function(t,e,n,i){},t.prototype.toggleBlurSeries=function(t,e,n){},t.prototype.eachRendered=function(t){var e=this.group;e&&e.traverse(t)},t}();function kg(){var t=Vo();return function(e){var n=t(e),i=e.pipelineContext,r=!!n.large,o=!!n.progressiveRender,a=n.large=!(!i||!i.large),s=n.progressiveRender=!(!i||!i.progressiveRender);return!(r===a&&o===s)&&"reset"}}$o(Ag),na(Ag);var Lg=Vo(),Pg=kg(),Og=function(){function t(){this.group=new Br,this.uid=Ac("viewChart"),this.renderTask=wf({plan:Eg,reset:zg}),this.renderTask.context={view:this}}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){0},t.prototype.highlight=function(t,e,n,i){var r=t.getData(i&&i.dataType);r&&Ng(r,i,"emphasis")},t.prototype.downplay=function(t,e,n,i){var r=t.getData(i&&i.dataType);r&&Ng(r,i,"normal")},t.prototype.remove=function(t,e){this.group.removeAll()},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateLayout=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateVisual=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.eachRendered=function(t){Jh(this.group,t)},t.markUpdateMethod=function(t,e){Lg(t).updateMethod=e},t.protoInitialize=void(t.prototype.type="chart"),t}();function Rg(t,e,n){t&&Ql(t)&&("emphasis"===e?Ol:Rl)(t,n)}function Ng(t,e,n){var i=zo(t,e),r=e&&null!=e.highlightKey?function(t){var e=sl[t];return null==e&&al<=32&&(e=sl[t]=al++),e}(e.highlightKey):null;null!=i?E(To(i),(function(e){Rg(t.getItemGraphicEl(e),n,r)})):t.eachItemGraphicEl((function(t){Rg(t,n,r)}))}function Eg(t){return Pg(t.model)}function zg(t){var e=t.model,n=t.ecModel,i=t.api,r=t.payload,o=e.pipelineContext.progressiveRender,a=t.view,s=r&&Lg(r).updateMethod,l=o?"incrementalPrepareRender":s&&a[s]?s:"render";return"render"!==l&&a[l](e,n,i,r),Vg[l]}$o(Og),na(Og);var Vg={incrementalPrepareRender:{progress:function(t,e){e.view.incrementalRender(t,e.model,e.ecModel,e.api,e.payload)}},render:{forceFirstProgress:!0,progress:function(t,e){e.view.render(e.model,e.ecModel,e.api,e.payload)}}},Bg="\0__throttleOriginMethod",Fg="\0__throttleRate",Gg="\0__throttleType";function Wg(t,e,n){var i,r,o,a,s,l=0,u=0,h=null;function c(){u=(new Date).getTime(),h=null,t.apply(o,a||[])}e=e||0;var p=function(){for(var t=[],p=0;p=0?c():h=setTimeout(c,-r),l=i};return p.clear=function(){h&&(clearTimeout(h),h=null)},p.debounceNextCall=function(t){s=t},p}function Hg(t,e,n,i){var r=t[e];if(r){var o=r[Bg]||r,a=r[Gg];if(r[Fg]!==n||a!==i){if(null==n||!i)return t[e]=o;(r=t[e]=Wg(o,n,"debounce"===i))[Bg]=o,r[Gg]=i,r[Fg]=n}return r}}function Yg(t,e){var n=t[e];n&&n[Bg]&&(n.clear&&n.clear(),t[e]=n[Bg])}var Xg=Vo(),Ug={itemStyle:ia(Mc,!0),lineStyle:ia(bc,!0)},Zg={lineStyle:"stroke",itemStyle:"fill"};function jg(t,e){var n=t.visualStyleMapper||Ug[e];return n||(console.warn("Unknown style type '"+e+"'."),Ug.itemStyle)}function qg(t,e){var n=t.visualDrawType||Zg[e];return n||(console.warn("Unknown style type '"+e+"'."),"fill")}var Kg={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData(),i=t.visualStyleAccessPath||"itemStyle",r=t.getModel(i),o=jg(t,i)(r),a=r.getShallow("decal");a&&(n.setVisual("decal",a),a.dirty=!0);var s=qg(t,i),l=o[s],u=X(l)?l:null,h="auto"===o.fill||"auto"===o.stroke;if(!o[s]||u||h){var c=t.getColorFromPalette(t.name,null,e.getSeriesCount());o[s]||(o[s]=c,n.setVisual("colorFromPalette",!0)),o.fill="auto"===o.fill||X(o.fill)?c:o.fill,o.stroke="auto"===o.stroke||X(o.stroke)?c:o.stroke}if(n.setVisual("style",o),n.setVisual("drawType",s),!e.isSeriesFiltered(t)&&u)return n.setVisual("colorFromPalette",!1),{dataEach:function(e,n){var i=t.getDataParams(n),r=A({},o);r[s]=u(i),e.setItemVisual(n,"style",r)}}}},$g=new Cc,Jg={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){if(!t.ignoreStyleOnData&&!e.isSeriesFiltered(t)){var n=t.getData(),i=t.visualStyleAccessPath||"itemStyle",r=jg(t,i),o=n.getVisual("drawType");return{dataEach:n.hasItemOption?function(t,e){var n=t.getRawDataItem(e);if(n&&n[i]){$g.option=n[i];var a=r($g);A(t.ensureUniqueItemVisual(e,"style"),a),$g.option.decal&&(t.setItemVisual(e,"decal",$g.option.decal),$g.option.decal.dirty=!0),o in a&&t.setItemVisual(e,"colorFromPalette",!1)}}:null}}}},Qg={performRawSeries:!0,overallReset:function(t){var e=yt();t.eachSeries((function(t){var n=t.getColorBy();if(!t.isColorBySeries()){var i=t.type+"-"+n,r=e.get(i);r||(r={},e.set(i,r)),Xg(t).scope=r}})),t.eachSeries((function(e){if(!e.isColorBySeries()&&!t.isSeriesFiltered(e)){var n=e.getRawData(),i={},r=e.getData(),o=Xg(e).scope,a=e.visualStyleAccessPath||"itemStyle",s=qg(e,a);r.each((function(t){var e=r.getRawIndex(t);i[e]=t})),n.each((function(t){var a=i[t];if(r.getItemVisual(a,"colorFromPalette")){var l=r.ensureUniqueItemVisual(a,"style"),u=n.getName(t)||t+"",h=n.count();l[s]=e.getColorFromPalette(u,o,h)}}))}}))}},ty=Math.PI;var ey=function(){function t(t,e,n,i){this._stageTaskMap=yt(),this.ecInstance=t,this.api=e,n=this._dataProcessorHandlers=n.slice(),i=this._visualHandlers=i.slice(),this._allHandlers=n.concat(i)}return t.prototype.restoreData=function(t,e){t.restoreData(e),this._stageTaskMap.each((function(t){var e=t.overallTask;e&&e.dirty()}))},t.prototype.getPerformArgs=function(t,e){if(t.__pipeline){var n=this._pipelineMap.get(t.__pipeline.id),i=n.context,r=!e&&n.progressiveEnabled&&(!i||i.progressiveRender)&&t.__idxInPipeline>n.blockIndex?n.step:null,o=i&&i.modDataCount;return{step:r,modBy:null!=o?Math.ceil(o/r):null,modDataCount:o}}},t.prototype.getPipeline=function(t){return this._pipelineMap.get(t)},t.prototype.updateStreamModes=function(t,e){var n=this._pipelineMap.get(t.uid),i=t.getData().count(),r=n.progressiveEnabled&&e.incrementalPrepareRender&&i>=n.threshold,o=t.get("large")&&i>=t.get("largeThreshold"),a="mod"===t.get("progressiveChunkMode")?i:null;t.pipelineContext=n.context={progressiveRender:r,modDataCount:a,large:o}},t.prototype.restorePipelines=function(t){var e=this,n=e._pipelineMap=yt();t.eachSeries((function(t){var i=t.getProgressive(),r=t.uid;n.set(r,{id:r,head:null,tail:null,threshold:t.getProgressiveThreshold(),progressiveEnabled:i&&!(t.preventIncremental&&t.preventIncremental()),blockIndex:-1,step:Math.round(i||700),count:0}),e._pipe(t,t.dataTask)}))},t.prototype.prepareStageTasks=function(){var t=this._stageTaskMap,e=this.api.getModel(),n=this.api;E(this._allHandlers,(function(i){var r=t.get(i.uid)||t.set(i.uid,{}),o="";lt(!(i.reset&&i.overallReset),o),i.reset&&this._createSeriesStageTask(i,r,e,n),i.overallReset&&this._createOverallStageTask(i,r,e,n)}),this)},t.prototype.prepareView=function(t,e,n,i){var r=t.renderTask,o=r.context;o.model=e,o.ecModel=n,o.api=i,r.__block=!t.incrementalPrepareRender,this._pipe(e,r)},t.prototype.performDataProcessorTasks=function(t,e){this._performStageTasks(this._dataProcessorHandlers,t,e,{block:!0})},t.prototype.performVisualTasks=function(t,e,n){this._performStageTasks(this._visualHandlers,t,e,n)},t.prototype._performStageTasks=function(t,e,n,i){i=i||{};var r=!1,o=this;function a(t,e){return t.setDirty&&(!t.dirtyMap||t.dirtyMap.get(e.__pipeline.id))}E(t,(function(t,s){if(!i.visualType||i.visualType===t.visualType){var l=o._stageTaskMap.get(t.uid),u=l.seriesTaskMap,h=l.overallTask;if(h){var c,p=h.agentStubMap;p.each((function(t){a(i,t)&&(t.dirty(),c=!0)})),c&&h.dirty(),o.updatePayload(h,n);var d=o.getPerformArgs(h,i.block);p.each((function(t){t.perform(d)})),h.perform(d)&&(r=!0)}else u&&u.each((function(s,l){a(i,s)&&s.dirty();var u=o.getPerformArgs(s,i.block);u.skip=!t.performRawSeries&&e.isSeriesFiltered(s.context.model),o.updatePayload(s,n),s.perform(u)&&(r=!0)}))}})),this.unfinished=r||this.unfinished},t.prototype.performSeriesTasks=function(t){var e;t.eachSeries((function(t){e=t.dataTask.perform()||e})),this.unfinished=e||this.unfinished},t.prototype.plan=function(){this._pipelineMap.each((function(t){var e=t.tail;do{if(e.__block){t.blockIndex=e.__idxInPipeline;break}e=e.getUpstream()}while(e)}))},t.prototype.updatePayload=function(t,e){"remain"!==e&&(t.context.payload=e)},t.prototype._createSeriesStageTask=function(t,e,n,i){var r=this,o=e.seriesTaskMap,a=e.seriesTaskMap=yt(),s=t.seriesType,l=t.getTargetSeries;function u(e){var s=e.uid,l=a.set(s,o&&o.get(s)||wf({plan:ay,reset:sy,count:hy}));l.context={model:e,ecModel:n,api:i,useClearVisual:t.isVisual&&!t.isLayout,plan:t.plan,reset:t.reset,scheduler:r},r._pipe(e,l)}t.createOnAllSeries?n.eachRawSeries(u):s?n.eachRawSeriesByType(s,u):l&&l(n,i).each(u)},t.prototype._createOverallStageTask=function(t,e,n,i){var r=this,o=e.overallTask=e.overallTask||wf({reset:ny});o.context={ecModel:n,api:i,overallReset:t.overallReset,scheduler:r};var a=o.agentStubMap,s=o.agentStubMap=yt(),l=t.seriesType,u=t.getTargetSeries,h=!0,c=!1,p="";function d(t){var e=t.uid,n=s.set(e,a&&a.get(e)||(c=!0,wf({reset:iy,onDirty:oy})));n.context={model:t,overallProgress:h},n.agent=o,n.__block=h,r._pipe(t,n)}lt(!t.createOnAllSeries,p),l?n.eachRawSeriesByType(l,d):u?u(n,i).each(d):(h=!1,E(n.getSeries(),d)),c&&o.dirty()},t.prototype._pipe=function(t,e){var n=t.uid,i=this._pipelineMap.get(n);!i.head&&(i.head=e),i.tail&&i.tail.pipe(e),i.tail=e,e.__idxInPipeline=i.count++,e.__pipeline=i},t.wrapStageHandler=function(t,e){return X(t)&&(t={overallReset:t,seriesType:cy(t)}),t.uid=Ac("stageHandler"),e&&(t.visualType=e),t},t}();function ny(t){t.overallReset(t.ecModel,t.api,t.payload)}function iy(t){return t.overallProgress&&ry}function ry(){this.agent.dirty(),this.getDownstream().dirty()}function oy(){this.agent&&this.agent.dirty()}function ay(t){return t.plan?t.plan(t.model,t.ecModel,t.api,t.payload):null}function sy(t){t.useClearVisual&&t.data.clearAllVisual();var e=t.resetDefines=To(t.reset(t.model,t.ecModel,t.api,t.payload));return e.length>1?z(e,(function(t,e){return uy(e)})):ly}var ly=uy(0);function uy(t){return function(e,n){var i=n.data,r=n.resetDefines[t];if(r&&r.dataEach)for(var o=e.start;o0&&h===r.length-u.length){var c=r.slice(0,h);"data"!==c&&(e.mainType=c,e[u.toLowerCase()]=t,s=!0)}}a.hasOwnProperty(r)&&(n[r]=t,s=!0),s||(i[r]=t)}))}return{cptQuery:e,dataQuery:n,otherQuery:i}},t.prototype.filter=function(t,e){var n=this.eventInfo;if(!n)return!0;var i=n.targetEl,r=n.packedEvent,o=n.model,a=n.view;if(!o||!a)return!0;var s=e.cptQuery,l=e.dataQuery;return u(s,o,"mainType")&&u(s,o,"subType")&&u(s,o,"index","componentIndex")&&u(s,o,"name")&&u(s,o,"id")&&u(l,r,"name")&&u(l,r,"dataIndex")&&u(l,r,"dataType")&&(!a.filterForExposedEvent||a.filterForExposedEvent(t,e.otherQuery,i,r));function u(t,e,n,i){return null==t[n]||e[i||n]===t[n]}},t.prototype.afterTrigger=function(){this.eventInfo=null},t}(),My=["symbol","symbolSize","symbolRotate","symbolOffset"],Iy=My.concat(["symbolKeepAspect"]),Ty={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData();if(t.legendIcon&&n.setVisual("legendIcon",t.legendIcon),t.hasSymbolVisual){for(var i={},r={},o=!1,a=0;a=0&&jy(l)?l:.5,t.createRadialGradient(a,s,0,a,s,l)}(t,e,n):function(t,e,n){var i=null==e.x?0:e.x,r=null==e.x2?1:e.x2,o=null==e.y?0:e.y,a=null==e.y2?0:e.y2;return e.global||(i=i*n.width+n.x,r=r*n.width+n.x,o=o*n.height+n.y,a=a*n.height+n.y),i=jy(i)?i:0,r=jy(r)?r:1,o=jy(o)?o:0,a=jy(a)?a:0,t.createLinearGradient(i,o,r,a)}(t,e,n),r=e.colorStops,o=0;o0&&(e=i.lineDash,n=i.lineWidth,e&&"solid"!==e&&n>0?"dashed"===e?[4*n,2*n]:"dotted"===e?[n]:j(e)?[e]:Y(e)?e:null:null),o=i.lineDashOffset;if(r){var a=i.strokeNoScale&&t.getLineScale?t.getLineScale():1;a&&1!==a&&(r=z(r,(function(t){return t/a})),o/=a)}return[r,o]}var Qy=new hs(!0);function tv(t){var e=t.stroke;return!(null==e||"none"===e||!(t.lineWidth>0))}function ev(t){return"string"==typeof t&&"none"!==t}function nv(t){var e=t.fill;return null!=e&&"none"!==e}function iv(t,e){if(null!=e.fillOpacity&&1!==e.fillOpacity){var n=t.globalAlpha;t.globalAlpha=e.fillOpacity*e.opacity,t.fill(),t.globalAlpha=n}else t.fill()}function rv(t,e){if(null!=e.strokeOpacity&&1!==e.strokeOpacity){var n=t.globalAlpha;t.globalAlpha=e.strokeOpacity*e.opacity,t.stroke(),t.globalAlpha=n}else t.stroke()}function ov(t,e,n){var i=la(e.image,e.__image,n);if(ha(i)){var r=t.createPattern(i,e.repeat||"repeat");if("function"==typeof DOMMatrix&&r&&r.setTransform){var o=new DOMMatrix;o.translateSelf(e.x||0,e.y||0),o.rotateSelf(0,0,(e.rotation||0)*wt),o.scaleSelf(e.scaleX||1,e.scaleY||1),r.setTransform(o)}return r}}var av=["shadowBlur","shadowOffsetX","shadowOffsetY"],sv=[["lineCap","butt"],["lineJoin","miter"],["miterLimit",10]];function lv(t,e,n,i,r){var o=!1;if(!i&&e===(n=n||{}))return!1;if(i||e.opacity!==n.opacity){cv(t,r),o=!0;var a=Math.max(Math.min(e.opacity,1),0);t.globalAlpha=isNaN(a)?Ma.opacity:a}(i||e.blend!==n.blend)&&(o||(cv(t,r),o=!0),t.globalCompositeOperation=e.blend||Ma.blend);for(var s=0;s0&&t.unfinished);t.unfinished||this._zr.flush()}}},e.prototype.getDom=function(){return this._dom},e.prototype.getId=function(){return this.id},e.prototype.getZr=function(){return this._zr},e.prototype.isSSR=function(){return this._ssr},e.prototype.setOption=function(t,e,n){if(!this[Dv])if(this._disposed)om(this.id);else{var i,r,o;if(q(e)&&(n=e.lazyUpdate,i=e.silent,r=e.replaceMerge,o=e.transition,e=e.notMerge),this[Dv]=!0,!this._model||e){var a=new Md(this._api),s=this._theme,l=this._model=new gd;l.scheduler=this._scheduler,l.ssr=this._ssr,l.init(null,null,null,s,this._locale,a)}this._model.setOption(t,{replaceMerge:r},um);var u={seriesTransition:o,optionChanged:!0};if(n)this[Av]={silent:i,updateParams:u},this[Dv]=!1,this.getZr().wakeUp();else{try{Ev(this),Bv.update.call(this,null,u)}catch(t){throw this[Av]=null,this[Dv]=!1,t}this._ssr||this._zr.flush(),this[Av]=null,this[Dv]=!1,Hv.call(this,i),Yv.call(this,i)}}},e.prototype.setTheme=function(){bo()},e.prototype.getModel=function(){return this._model},e.prototype.getOption=function(){return this._model&&this._model.getOption()},e.prototype.getWidth=function(){return this._zr.getWidth()},e.prototype.getHeight=function(){return this._zr.getHeight()},e.prototype.getDevicePixelRatio=function(){return this._zr.painter.dpr||r.hasGlobalWindow&&window.devicePixelRatio||1},e.prototype.getRenderedCanvas=function(t){return this.renderToCanvas(t)},e.prototype.renderToCanvas=function(t){t=t||{};var e=this._zr.painter;return e.getRenderedCanvas({backgroundColor:t.backgroundColor||this._model.get("backgroundColor"),pixelRatio:t.pixelRatio||this.getDevicePixelRatio()})},e.prototype.renderToSVGString=function(t){t=t||{};var e=this._zr.painter;return e.renderToString({useViewBox:t.useViewBox})},e.prototype.getSvgDataURL=function(){if(r.svgSupported){var t=this._zr;return E(t.storage.getDisplayList(),(function(t){t.stopAnimation(null,!0)})),t.painter.toDataURL()}},e.prototype.getDataURL=function(t){if(!this._disposed){var e=(t=t||{}).excludeComponents,n=this._model,i=[],r=this;E(e,(function(t){n.eachComponent({mainType:t},(function(t){var e=r._componentsMap[t.__viewId];e.group.ignore||(i.push(e),e.group.ignore=!0)}))}));var o="svg"===this._zr.painter.getType()?this.getSvgDataURL():this.renderToCanvas(t).toDataURL("image/"+(t&&t.type||"png"));return E(i,(function(t){t.group.ignore=!1})),o}om(this.id)},e.prototype.getConnectedDataURL=function(t){if(!this._disposed){var e="svg"===t.type,n=this.group,i=Math.min,r=Math.max,o=1/0;if(fm[n]){var a=o,s=o,l=-1/0,u=-1/0,c=[],p=t&&t.pixelRatio||this.getDevicePixelRatio();E(dm,(function(o,h){if(o.group===n){var p=e?o.getZr().painter.getSvgDom().innerHTML:o.renderToCanvas(T(t)),d=o.getDom().getBoundingClientRect();a=i(d.left,a),s=i(d.top,s),l=r(d.right,l),u=r(d.bottom,u),c.push({dom:p,left:d.left,top:d.top})}}));var d=(l*=p)-(a*=p),f=(u*=p)-(s*=p),g=h.createCanvas(),y=Yr(g,{renderer:e?"svg":"canvas"});if(y.resize({width:d,height:f}),e){var v="";return E(c,(function(t){var e=t.left-a,n=t.top-s;v+=''+t.dom+""})),y.painter.getSvgRoot().innerHTML=v,t.connectedBackgroundColor&&y.painter.setBackgroundColor(t.connectedBackgroundColor),y.refreshImmediately(),y.painter.toDataURL()}return t.connectedBackgroundColor&&y.add(new Ws({shape:{x:0,y:0,width:d,height:f},style:{fill:t.connectedBackgroundColor}})),E(c,(function(t){var e=new Ns({style:{x:t.left*p-a,y:t.top*p-s,image:t.dom}});y.add(e)})),y.refreshImmediately(),g.toDataURL("image/"+(t&&t.type||"png"))}return this.getDataURL(t)}om(this.id)},e.prototype.convertToPixel=function(t,e){return Fv(this,"convertToPixel",t,e)},e.prototype.convertFromPixel=function(t,e){return Fv(this,"convertFromPixel",t,e)},e.prototype.containPixel=function(t,e){var n;if(!this._disposed)return E(Fo(this._model,t),(function(t,i){i.indexOf("Models")>=0&&E(t,(function(t){var r=t.coordinateSystem;if(r&&r.containPoint)n=n||!!r.containPoint(e);else if("seriesModels"===i){var o=this._chartsMap[t.__viewId];o&&o.containPoint&&(n=n||o.containPoint(e,t))}else 0}),this)}),this),!!n;om(this.id)},e.prototype.getVisual=function(t,e){var n=Fo(this._model,t,{defaultMainType:"series"}),i=n.seriesModel;var r=i.getData(),o=n.hasOwnProperty("dataIndexInside")?n.dataIndexInside:n.hasOwnProperty("dataIndex")?r.indexOfRawIndex(n.dataIndex):null;return null!=o?Dy(r,o,e):Ay(r,e)},e.prototype.getViewOfComponentModel=function(t){return this._componentsMap[t.__viewId]},e.prototype.getViewOfSeriesModel=function(t){return this._chartsMap[t.__viewId]},e.prototype._initEvents=function(){var t,e,n,i=this;E(rm,(function(t){var e=function(e){var n,r=i.getModel(),o=e.target,a="globalout"===t;if(a?n={}:o&&Oy(o,(function(t){var e=rl(t);if(e&&null!=e.dataIndex){var i=e.dataModel||r.getSeriesByIndex(e.seriesIndex);return n=i&&i.getDataParams(e.dataIndex,e.dataType,o)||{},!0}if(e.eventData)return n=A({},e.eventData),!0}),!0),n){var s=n.componentType,l=n.componentIndex;"markLine"!==s&&"markPoint"!==s&&"markArea"!==s||(s="series",l=n.seriesIndex);var u=s&&null!=l&&r.getComponent(s,l),h=u&&i["series"===u.mainType?"_chartsMap":"_componentsMap"][u.__viewId];0,n.event=e,n.type=t,i._$eventProcessor.eventInfo={targetEl:o,packedEvent:n,model:u,view:h},i.trigger(t,n)}};e.zrEventfulCallAtLast=!0,i._zr.on(t,e,i)})),E(sm,(function(t,e){i._messageCenter.on(e,(function(t){this.trigger(e,t)}),i)})),E(["selectchanged"],(function(t){i._messageCenter.on(t,(function(e){this.trigger(t,e)}),i)})),t=this._messageCenter,e=this,n=this._api,t.on("selectchanged",(function(t){var i=n.getModel();t.isFromClick?(Py("map","selectchanged",e,i,t),Py("pie","selectchanged",e,i,t)):"select"===t.fromAction?(Py("map","selected",e,i,t),Py("pie","selected",e,i,t)):"unselect"===t.fromAction&&(Py("map","unselected",e,i,t),Py("pie","unselected",e,i,t))}))},e.prototype.isDisposed=function(){return this._disposed},e.prototype.clear=function(){this._disposed?om(this.id):this.setOption({series:[]},!0)},e.prototype.dispose=function(){if(this._disposed)om(this.id);else{this._disposed=!0,this.getDom()&&Xo(this.getDom(),vm,"");var t=this,e=t._api,n=t._model;E(t._componentsViews,(function(t){t.dispose(n,e)})),E(t._chartsViews,(function(t){t.dispose(n,e)})),t._zr.dispose(),t._dom=t._model=t._chartsMap=t._componentsMap=t._chartsViews=t._componentsViews=t._scheduler=t._api=t._zr=t._throttledZrFlush=t._theme=t._coordSysMgr=t._messageCenter=null,delete dm[t.id]}},e.prototype.resize=function(t){if(!this[Dv])if(this._disposed)om(this.id);else{this._zr.resize(t);var e=this._model;if(this._loadingFX&&this._loadingFX.resize(),e){var n=e.resetOption("media"),i=t&&t.silent;this[Av]&&(null==i&&(i=this[Av].silent),n=!0,this[Av]=null),this[Dv]=!0;try{n&&Ev(this),Bv.update.call(this,{type:"resize",animation:A({duration:0},t&&t.animation)})}catch(t){throw this[Dv]=!1,t}this[Dv]=!1,Hv.call(this,i),Yv.call(this,i)}}},e.prototype.showLoading=function(t,e){if(this._disposed)om(this.id);else if(q(t)&&(e=t,t=""),t=t||"default",this.hideLoading(),pm[t]){var n=pm[t](this._api,e),i=this._zr;this._loadingFX=n,i.add(n)}},e.prototype.hideLoading=function(){this._disposed?om(this.id):(this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null)},e.prototype.makeActionFromEvent=function(t){var e=A({},t);return e.type=sm[t.type],e},e.prototype.dispatchAction=function(t,e){if(this._disposed)om(this.id);else if(q(e)||(e={silent:!!e}),am[t.type]&&this._model)if(this[Dv])this._pendingActions.push(t);else{var n=e.silent;Wv.call(this,t,n);var i=e.flush;i?this._zr.flush():!1!==i&&r.browser.weChat&&this._throttledZrFlush(),Hv.call(this,n),Yv.call(this,n)}},e.prototype.updateLabelLayout=function(){wv.trigger("series:layoutlabels",this._model,this._api,{updatedSeries:[]})},e.prototype.appendData=function(t){if(this._disposed)om(this.id);else{var e=t.seriesIndex,n=this.getModel().getSeriesByIndex(e);0,n.appendData(t),this._scheduler.unfinished=!0,this.getZr().wakeUp()}},e.internalField=function(){function t(t){t.clearColorPalette(),t.eachSeries((function(t){t.clearColorPalette()}))}function e(t){for(var e=[],n=t.currentStates,i=0;i0?{duration:o,delay:i.get("delay"),easing:i.get("easing")}:null;n.eachRendered((function(t){if(t.states&&t.states.emphasis){if(xh(t))return;if(t instanceof ks&&function(t){var e=ll(t);e.normalFill=t.style.fill,e.normalStroke=t.style.stroke;var n=t.states.select||{};e.selectFill=n.style&&n.style.fill||null,e.selectStroke=n.style&&n.style.stroke||null}(t),t.__dirty){var n=t.prevStates;n&&t.useStates(n)}if(r){t.stateTransition=a;var i=t.getTextContent(),o=t.getTextGuideLine();i&&(i.stateTransition=a),o&&(o.stateTransition=a)}t.__dirty&&e(t)}}))}Ev=function(t){var e=t._scheduler;e.restorePipelines(t._model),e.prepareStageTasks(),zv(t,!0),zv(t,!1),e.plan()},zv=function(t,e){for(var n=t._model,i=t._scheduler,r=e?t._componentsViews:t._chartsViews,o=e?t._componentsMap:t._chartsMap,a=t._zr,s=t._api,l=0;le.get("hoverLayerThreshold")&&!r.node&&!r.worker&&e.eachSeries((function(e){if(!e.preventUsingHoverLayer){var n=t._chartsMap[e.__viewId];n.__alive&&n.eachRendered((function(t){t.states.emphasis&&(t.states.emphasis.hoverLayer=!0)}))}}))}(t,e),wv.trigger("series:afterupdate",e,n,l)},Jv=function(t){t[kv]=!0,t.getZr().wakeUp()},Qv=function(t){t[kv]&&(t.getZr().storage.traverse((function(t){xh(t)||e(t)})),t[kv]=!1)},Kv=function(t){return new(function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return n(i,e),i.prototype.getCoordinateSystems=function(){return t._coordSysMgr.getCoordinateSystems()},i.prototype.getComponentByElement=function(e){for(;e;){var n=e.__ecComponentInfo;if(null!=n)return t._model.getComponent(n.mainType,n.index);e=e.parent}},i.prototype.enterEmphasis=function(e,n){Ol(e,n),Jv(t)},i.prototype.leaveEmphasis=function(e,n){Rl(e,n),Jv(t)},i.prototype.enterBlur=function(e){Nl(e),Jv(t)},i.prototype.leaveBlur=function(e){El(e),Jv(t)},i.prototype.enterSelect=function(e){zl(e),Jv(t)},i.prototype.leaveSelect=function(e){Vl(e),Jv(t)},i.prototype.getModel=function(){return t.getModel()},i.prototype.getViewOfComponentModel=function(e){return t.getViewOfComponentModel(e)},i.prototype.getViewOfSeriesModel=function(e){return t.getViewOfSeriesModel(e)},i}(_d))(t)},$v=function(t){function e(t,e){for(var n=0;n=0)){Lm.push(n);var o=ey.wrapStageHandler(n,r);o.__prio=e,o.__raw=n,t.push(o)}}function Om(t,e){pm[t]=e}function Rm(t,e,n){var i=Mv("registerMap");i&&i(t,e,n)}var Nm=function(t){var e=(t=T(t)).type,n="";e||wo(n);var i=e.split(":");2!==i.length&&wo(n);var r=!1;"echarts"===i[0]&&(e=i[1],r=!0),t.__isBuiltIn=r,Vf.set(e,t)};km(Iv,Kg),km(Tv,Jg),km(Tv,Qg),km(Iv,Ty),km(Tv,Cy),km(7e3,(function(t,e){t.eachRawSeries((function(n){if(!t.isSeriesFiltered(n)){var i=n.getData();i.hasItemVisual()&&i.each((function(t){var n=i.getItemVisual(t,"decal");n&&(i.ensureUniqueItemVisual(t,"style").decal=mv(n,e))}));var r=i.getVisual("decal");if(r)i.getVisual("style").decal=mv(r,e)}}))})),wm(Xd),Sm(900,(function(t){var e=yt();t.eachSeries((function(t){var n=t.get("stack");if(n){var i=e.get(n)||e.set(n,[]),r=t.getData(),o={stackResultDimension:r.getCalculationInfo("stackResultDimension"),stackedOverDimension:r.getCalculationInfo("stackedOverDimension"),stackedDimension:r.getCalculationInfo("stackedDimension"),stackedByDimension:r.getCalculationInfo("stackedByDimension"),isStackedByIndex:r.getCalculationInfo("isStackedByIndex"),data:r,seriesModel:t};if(!o.stackedDimension||!o.isStackedByIndex&&!o.stackedByDimension)return;i.length&&r.setCalculationInfo("stackedOnSeries",i[i.length-1].seriesModel),i.push(o)}})),e.each(Ud)})),Om("default",(function(t,e){k(e=e||{},{text:"loading",textColor:"#000",fontSize:12,fontWeight:"normal",fontStyle:"normal",fontFamily:"sans-serif",maskColor:"rgba(255, 255, 255, 0.8)",showSpinner:!0,color:"#5470c6",spinnerRadius:10,lineWidth:5,zlevel:0});var n=new Br,i=new Ws({style:{fill:e.maskColor},zlevel:e.zlevel,z:1e4});n.add(i);var r,o=new Xs({style:{text:e.text,fill:e.textColor,fontSize:e.fontSize,fontWeight:e.fontWeight,fontStyle:e.fontStyle,fontFamily:e.fontFamily},zlevel:e.zlevel,z:10001}),a=new Ws({style:{fill:"none"},textContent:o,textConfig:{position:"right",distance:10},zlevel:e.zlevel,z:10001});return n.add(a),e.showSpinner&&((r=new nh({shape:{startAngle:-ty/2,endAngle:-ty/2+.1,r:e.spinnerRadius},style:{stroke:e.color,lineCap:"round",lineWidth:e.lineWidth},zlevel:e.zlevel,z:10001})).animateShape(!0).when(1e3,{endAngle:3*ty/2}).start("circularInOut"),r.animateShape(!0).when(1e3,{startAngle:3*ty/2}).delay(300).start("circularInOut"),n.add(r)),n.resize=function(){var n=o.getBoundingRect().width,s=e.showSpinner?e.spinnerRadius:0,l=(t.getWidth()-2*s-(e.showSpinner&&n?10:0)-n)/2-(e.showSpinner&&n?0:5+n/2)+(e.showSpinner?0:n/2)+(n?0:s),u=t.getHeight()/2;e.showSpinner&&r.setShape({cx:l,cy:u}),a.setShape({x:l-s,y:u-s,width:2*s,height:2*s}),i.setShape({x:0,y:0,width:t.getWidth(),height:t.getHeight()})},n.resize(),n})),Cm({type:dl,event:dl,update:dl},bt),Cm({type:fl,event:fl,update:fl},bt),Cm({type:gl,event:gl,update:gl},bt),Cm({type:yl,event:yl,update:yl},bt),Cm({type:vl,event:vl,update:vl},bt),bm("light",vy),bm("dark",wy);var Em=[],zm={registerPreprocessor:wm,registerProcessor:Sm,registerPostInit:Mm,registerPostUpdate:Im,registerUpdateLifecycle:Tm,registerAction:Cm,registerCoordinateSystem:Dm,registerLayout:Am,registerVisual:km,registerTransform:Nm,registerLoading:Om,registerMap:Rm,registerImpl:function(t,e){Sv[t]=e},PRIORITY:Cv,ComponentModel:zp,ComponentView:Ag,SeriesModel:bg,ChartView:Og,registerComponentModel:function(t){zp.registerClass(t)},registerComponentView:function(t){Ag.registerClass(t)},registerSeriesModel:function(t){bg.registerClass(t)},registerChartView:function(t){Og.registerClass(t)},registerSubTypeDefaulter:function(t,e){zp.registerSubTypeDefaulter(t,e)},registerPainter:function(t,e){Xr(t,e)}};function Vm(t){Y(t)?E(t,(function(t){Vm(t)})):P(Em,t)>=0||(Em.push(t),X(t)&&(t={install:t}),t.install(zm))}function Bm(t){return null==t?0:t.length||1}function Fm(t){return t}var Gm=function(){function t(t,e,n,i,r,o){this._old=t,this._new=e,this._oldKeyGetter=n||Fm,this._newKeyGetter=i||Fm,this.context=r,this._diffModeMultiple="multiple"===o}return t.prototype.add=function(t){return this._add=t,this},t.prototype.update=function(t){return this._update=t,this},t.prototype.updateManyToOne=function(t){return this._updateManyToOne=t,this},t.prototype.updateOneToMany=function(t){return this._updateOneToMany=t,this},t.prototype.updateManyToMany=function(t){return this._updateManyToMany=t,this},t.prototype.remove=function(t){return this._remove=t,this},t.prototype.execute=function(){this[this._diffModeMultiple?"_executeMultiple":"_executeOneToOne"]()},t.prototype._executeOneToOne=function(){var t=this._old,e=this._new,n={},i=new Array(t.length),r=new Array(e.length);this._initIndexMap(t,null,i,"_oldKeyGetter"),this._initIndexMap(e,n,r,"_newKeyGetter");for(var o=0;o1){var u=s.shift();1===s.length&&(n[a]=s[0]),this._update&&this._update(u,o)}else 1===l?(n[a]=null,this._update&&this._update(s,o)):this._remove&&this._remove(o)}this._performRestAdd(r,n)},t.prototype._executeMultiple=function(){var t=this._old,e=this._new,n={},i={},r=[],o=[];this._initIndexMap(t,n,r,"_oldKeyGetter"),this._initIndexMap(e,i,o,"_newKeyGetter");for(var a=0;a1&&1===c)this._updateManyToOne&&this._updateManyToOne(u,l),i[s]=null;else if(1===h&&c>1)this._updateOneToMany&&this._updateOneToMany(u,l),i[s]=null;else if(1===h&&1===c)this._update&&this._update(u,l),i[s]=null;else if(h>1&&c>1)this._updateManyToMany&&this._updateManyToMany(u,l),i[s]=null;else if(h>1)for(var p=0;p1)for(var a=0;a30}var Qm,tx,ex,nx,ix,rx,ox,ax=q,sx=z,lx="undefined"==typeof Int32Array?Array:Int32Array,ux=["hasItemOption","_nameList","_idList","_invertedIndicesMap","_dimSummary","userOutput","_rawData","_dimValueGetter","_nameDimIdx","_idDimIdx","_nameRepeatCount"],hx=["_approximateExtent"],cx=function(){function t(t,e){var n;this.type="list",this._dimOmitted=!1,this._nameList=[],this._idList=[],this._visual={},this._layout={},this._itemVisuals=[],this._itemLayouts=[],this._graphicEls=[],this._approximateExtent={},this._calculationInfo={},this.hasItemOption=!1,this.TRANSFERABLE_METHODS=["cloneShallow","downSample","minmaxDownSample","lttbDownSample","map"],this.CHANGABLE_METHODS=["filterSelf","selectRange"],this.DOWNSAMPLE_METHODS=["downSample","minmaxDownSample","lttbDownSample"];var i=!1;qm(t)?(n=t.dimensions,this._dimOmitted=t.isDimensionOmitted(),this._schema=t):(i=!0,n=t),n=n||["x","y"];for(var r={},o=[],a={},s=!1,l={},u=0;u=e)){var n=this._store.getProvider();this._updateOrdinalMeta();var i=this._nameList,r=this._idList;if(n.getSource().sourceFormat===Wp&&!n.pure)for(var o=[],a=t;a0},t.prototype.ensureUniqueItemVisual=function(t,e){var n=this._itemVisuals,i=n[t];i||(i=n[t]={});var r=i[e];return null==r&&(Y(r=this.getVisual(e))?r=r.slice():ax(r)&&(r=A({},r)),i[e]=r),r},t.prototype.setItemVisual=function(t,e,n){var i=this._itemVisuals[t]||{};this._itemVisuals[t]=i,ax(e)?A(i,e):i[e]=n},t.prototype.clearAllVisual=function(){this._visual={},this._itemVisuals=[]},t.prototype.setLayout=function(t,e){ax(t)?A(this._layout,t):this._layout[t]=e},t.prototype.getLayout=function(t){return this._layout[t]},t.prototype.getItemLayout=function(t){return this._itemLayouts[t]},t.prototype.setItemLayout=function(t,e,n){this._itemLayouts[t]=n?A(this._itemLayouts[t]||{},e):e},t.prototype.clearItemLayouts=function(){this._itemLayouts.length=0},t.prototype.setItemGraphicEl=function(t,e){var n=this.hostModel&&this.hostModel.seriesIndex;ol(n,this.dataType,t,e),this._graphicEls[t]=e},t.prototype.getItemGraphicEl=function(t){return this._graphicEls[t]},t.prototype.eachItemGraphicEl=function(t,e){E(this._graphicEls,(function(n,i){n&&t&&t.call(e,n,i)}))},t.prototype.cloneShallow=function(e){return e||(e=new t(this._schema?this._schema:sx(this.dimensions,this._getDimInfo,this),this.hostModel)),ix(e,this),e._store=this._store,e},t.prototype.wrapMethod=function(t,e){var n=this[t];X(n)&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var t=n.apply(this,arguments);return e.apply(this,[t].concat(at(arguments)))})},t.internalField=(Qm=function(t){var e=t._invertedIndicesMap;E(e,(function(n,i){var r=t._dimInfos[i],o=r.ordinalMeta,a=t._store;if(o){n=e[i]=new lx(o.categories.length);for(var s=0;s1&&(s+="__ec__"+u),i[e]=s}})),t}();function px(t,e){Qd(t)||(t=ef(t));var n=(e=e||{}).coordDimensions||[],i=e.dimensionsDefine||t.dimensionsDefine||[],r=yt(),o=[],a=function(t,e,n,i){var r=Math.max(t.dimensionsDetectedCount||1,e.length,n.length,i||0);return E(e,(function(t){var e;q(t)&&(e=t.dimsDef)&&(r=Math.max(r,e.length))})),r}(t,n,i,e.dimensionsCount),s=e.canOmitUnusedDimensions&&Jm(a),l=i===t.dimensionsDefine,u=l?$m(t):Km(i),h=e.encodeDefine;!h&&e.encodeDefaulter&&(h=e.encodeDefaulter(t,a));for(var c=yt(h),p=new Xf(a),d=0;d0&&(i.name=r+(o-1)),o++,e.set(r,o)}}(o),new jm({source:t,dimensions:o,fullDimensionCount:a,dimensionOmitted:s})}function dx(t,e,n){if(n||e.hasKey(t)){for(var i=0;e.hasKey(t+i);)i++;t+=i}return e.set(t,!0),t}var fx=function(t){this.coordSysDims=[],this.axisMap=yt(),this.categoryAxisMap=yt(),this.coordSysName=t};var gx={cartesian2d:function(t,e,n,i){var r=t.getReferringComponents("xAxis",Wo).models[0],o=t.getReferringComponents("yAxis",Wo).models[0];e.coordSysDims=["x","y"],n.set("x",r),n.set("y",o),yx(r)&&(i.set("x",r),e.firstCategoryDimIndex=0),yx(o)&&(i.set("y",o),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},singleAxis:function(t,e,n,i){var r=t.getReferringComponents("singleAxis",Wo).models[0];e.coordSysDims=["single"],n.set("single",r),yx(r)&&(i.set("single",r),e.firstCategoryDimIndex=0)},polar:function(t,e,n,i){var r=t.getReferringComponents("polar",Wo).models[0],o=r.findAxisModel("radiusAxis"),a=r.findAxisModel("angleAxis");e.coordSysDims=["radius","angle"],n.set("radius",o),n.set("angle",a),yx(o)&&(i.set("radius",o),e.firstCategoryDimIndex=0),yx(a)&&(i.set("angle",a),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},geo:function(t,e,n,i){e.coordSysDims=["lng","lat"]},parallel:function(t,e,n,i){var r=t.ecModel,o=r.getComponent("parallel",t.get("parallelIndex")),a=e.coordSysDims=o.dimensions.slice();E(o.parallelAxisIndex,(function(t,o){var s=r.getComponent("parallelAxis",t),l=a[o];n.set(l,s),yx(s)&&(i.set(l,s),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=o))}))}};function yx(t){return"category"===t.get("type")}function vx(t,e,n){var i,r,o,a=(n=n||{}).byIndex,s=n.stackedCoordDimension;!function(t){return!qm(t.schema)}(e)?(r=e.schema,i=r.dimensions,o=e.store):i=e;var l,u,h,c,p=!(!t||!t.get("stack"));if(E(i,(function(t,e){U(t)&&(i[e]=t={name:t}),p&&!t.isExtraCoord&&(a||l||!t.ordinalMeta||(l=t),u||"ordinal"===t.type||"time"===t.type||s&&s!==t.coordDim||(u=t))})),!u||a||l||(a=!0),u){h="__\0ecstackresult_"+t.id,c="__\0ecstackedover_"+t.id,l&&(l.createInvertedIndices=!0);var d=u.coordDim,f=u.type,g=0;E(i,(function(t){t.coordDim===d&&g++}));var y={name:h,coordDim:d,coordDimIndex:g,type:f,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:i.length},v={name:c,coordDim:c,coordDimIndex:g+1,type:f,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:i.length+1};r?(o&&(y.storeDimIndex=o.ensureCalculationDimension(c,f),v.storeDimIndex=o.ensureCalculationDimension(h,f)),r.appendCalculationDimension(y),r.appendCalculationDimension(v)):(i.push(y),i.push(v))}return{stackedDimension:u&&u.name,stackedByDimension:l&&l.name,isStackedByIndex:a,stackedOverDimension:c,stackResultDimension:h}}function mx(t,e){return!!e&&e===t.getCalculationInfo("stackedDimension")}function xx(t,e){return mx(t,e)?t.getCalculationInfo("stackResultDimension"):e}function _x(t,e,n){n=n||{};var i,r=e.getSourceManager(),o=!1;t?(o=!0,i=ef(t)):o=(i=r.getSource()).sourceFormat===Wp;var a=function(t){var e=t.get("coordinateSystem"),n=new fx(e),i=gx[e];if(i)return i(t,n,n.axisMap,n.categoryAxisMap),n}(e),s=function(t,e){var n,i=t.get("coordinateSystem"),r=wd.get(i);return e&&e.coordSysDims&&(n=z(e.coordSysDims,(function(t){var n={name:t},i=e.axisMap.get(t);if(i){var r=i.get("type");n.type=Ym(r)}return n}))),n||(n=r&&(r.getDimensionsInfo?r.getDimensionsInfo():r.dimensions.slice())||["x","y"]),n}(e,a),l=n.useEncodeDefaulter,u=X(l)?l:l?H(td,s,e):null,h=px(i,{coordDimensions:s,generateCoord:n.generateCoord,encodeDefine:e.getEncode(),encodeDefaulter:u,canOmitUnusedDimensions:!o}),c=function(t,e,n){var i,r;return n&&E(t,(function(t,o){var a=t.coordDim,s=n.categoryAxisMap.get(a);s&&(null==i&&(i=o),t.ordinalMeta=s.getOrdinalMeta(),e&&(t.createInvertedIndices=!0)),null!=t.otherDims.itemName&&(r=!0)})),r||null==i||(t[i].otherDims.itemName=0),i}(h.dimensions,n.createInvertedIndices,a),p=o?null:r.getSharedDataStore(h),d=vx(e,{schema:h,store:p}),f=new cx(h,e);f.setCalculationInfo(d);var g=null!=c&&function(t){if(t.sourceFormat===Wp){var e=function(t){var e=0;for(;ee[1]&&(e[1]=t[1])},t.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=t),isNaN(e)||(n[1]=e)},t.prototype.isInExtentRange=function(t){return this._extent[0]<=t&&this._extent[1]>=t},t.prototype.isBlank=function(){return this._isBlank},t.prototype.setBlank=function(t){this._isBlank=t},t}();na(bx);var Sx=0,Mx=function(){function t(t){this.categories=t.categories||[],this._needCollect=t.needCollect,this._deduplication=t.deduplication,this.uid=++Sx}return t.createByAxisModel=function(e){var n=e.option,i=n.data,r=i&&z(i,Ix);return new t({categories:r,needCollect:!r,deduplication:!1!==n.dedplication})},t.prototype.getOrdinal=function(t){return this._getOrCreateMap().get(t)},t.prototype.parseAndCollect=function(t){var e,n=this._needCollect;if(!U(t)&&!n)return t;if(n&&!this._deduplication)return e=this.categories.length,this.categories[e]=t,e;var i=this._getOrCreateMap();return null==(e=i.get(t))&&(n?(e=this.categories.length,this.categories[e]=t,i.set(t,e)):e=NaN),e},t.prototype._getOrCreateMap=function(){return this._map||(this._map=yt(this.categories))},t}();function Ix(t){return q(t)&&null!=t.value?t.value:t+""}function Tx(t){return"interval"===t.type||"log"===t.type}function Cx(t,e,n,i){var r={},o=t[1]-t[0],a=r.interval=po(o/e,!0);null!=n&&ai&&(a=r.interval=i);var s=r.intervalPrecision=Ax(a);return function(t,e){!isFinite(t[0])&&(t[0]=e[0]),!isFinite(t[1])&&(t[1]=e[1]),kx(t,0,e),kx(t,1,e),t[0]>t[1]&&(t[0]=t[1])}(r.niceTickExtent=[Jr(Math.ceil(t[0]/a)*a,s),Jr(Math.floor(t[1]/a)*a,s)],t),r}function Dx(t){var e=Math.pow(10,co(t)),n=t/e;return n?2===n?n=3:3===n?n=5:n*=2:n=1,Jr(n*e)}function Ax(t){return to(t)+2}function kx(t,e,n){t[e]=Math.max(Math.min(t[e],n[1]),n[0])}function Lx(t,e){return t>=e[0]&&t<=e[1]}function Px(t,e){return e[1]===e[0]?.5:(t-e[0])/(e[1]-e[0])}function Ox(t,e){return t*(e[1]-e[0])+e[0]}var Rx=function(t){function e(e){var n=t.call(this,e)||this;n.type="ordinal";var i=n.getSetting("ordinalMeta");return i||(i=new Mx({})),Y(i)&&(i=new Mx({categories:z(i,(function(t){return q(t)?t.value:t}))})),n._ordinalMeta=i,n._extent=n.getSetting("extent")||[0,i.categories.length-1],n}return n(e,t),e.prototype.parse=function(t){return null==t?NaN:U(t)?this._ordinalMeta.getOrdinal(t):Math.round(t)},e.prototype.contain=function(t){return Lx(t=this.parse(t),this._extent)&&null!=this._ordinalMeta.categories[t]},e.prototype.normalize=function(t){return Px(t=this._getTickNumber(this.parse(t)),this._extent)},e.prototype.scale=function(t){return t=Math.round(Ox(t,this._extent)),this.getRawOrdinalNumber(t)},e.prototype.getTicks=function(){for(var t=[],e=this._extent,n=e[0];n<=e[1];)t.push({value:n}),n++;return t},e.prototype.getMinorTicks=function(t){},e.prototype.setSortInfo=function(t){if(null!=t){for(var e=t.ordinalNumbers,n=this._ordinalNumbersByTick=[],i=this._ticksByOrdinalNumber=[],r=0,o=this._ordinalMeta.categories.length,a=Math.min(o,e.length);r=0&&t=0&&t=t},e.prototype.getOrdinalMeta=function(){return this._ordinalMeta},e.prototype.calcNiceTicks=function(){},e.prototype.calcNiceExtent=function(){},e.type="ordinal",e}(bx);bx.registerClass(Rx);var Nx=Jr,Ex=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="interval",e._interval=0,e._intervalPrecision=2,e}return n(e,t),e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return Lx(t,this._extent)},e.prototype.normalize=function(t){return Px(t,this._extent)},e.prototype.scale=function(t){return Ox(t,this._extent)},e.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=parseFloat(t)),isNaN(e)||(n[1]=parseFloat(e))},e.prototype.unionExtent=function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1]),this.setExtent(e[0],e[1])},e.prototype.getInterval=function(){return this._interval},e.prototype.setInterval=function(t){this._interval=t,this._niceExtent=this._extent.slice(),this._intervalPrecision=Ax(t)},e.prototype.getTicks=function(t){var e=this._interval,n=this._extent,i=this._niceExtent,r=this._intervalPrecision,o=[];if(!e)return o;n[0]1e4)return[];var s=o.length?o[o.length-1].value:i[1];return n[1]>s&&(t?o.push({value:Nx(s+e,r)}):o.push({value:n[1]})),o},e.prototype.getMinorTicks=function(t){for(var e=this.getTicks(!0),n=[],i=this.getExtent(),r=1;ri[0]&&h0&&(o=null===o?s:Math.min(o,s))}n[i]=o}}return n}(t),n=[];return E(t,(function(t){var i,r=t.coordinateSystem.getBaseAxis(),o=r.getExtent();if("category"===r.type)i=r.getBandWidth();else if("value"===r.type||"time"===r.type){var a=r.dim+"_"+r.index,s=e[a],l=Math.abs(o[1]-o[0]),u=r.scale.getExtent(),h=Math.abs(u[1]-u[0]);i=s?l/h*s:l}else{var c=t.getData();i=Math.abs(o[1]-o[0])/c.count()}var p=$r(t.get("barWidth"),i),d=$r(t.get("barMaxWidth"),i),f=$r(t.get("barMinWidth")||(qx(t)?.5:1),i),g=t.get("barGap"),y=t.get("barCategoryGap");n.push({bandWidth:i,barWidth:p,barMaxWidth:d,barMinWidth:f,barGap:g,barCategoryGap:y,axisKey:Wx(r),stackId:Gx(t)})})),Xx(n)}function Xx(t){var e={};E(t,(function(t,n){var i=t.axisKey,r=t.bandWidth,o=e[i]||{bandWidth:r,remainedWidth:r,autoWidthCount:0,categoryGap:null,gap:"20%",stacks:{}},a=o.stacks;e[i]=o;var s=t.stackId;a[s]||o.autoWidthCount++,a[s]=a[s]||{width:0,maxWidth:0};var l=t.barWidth;l&&!a[s].width&&(a[s].width=l,l=Math.min(o.remainedWidth,l),o.remainedWidth-=l);var u=t.barMaxWidth;u&&(a[s].maxWidth=u);var h=t.barMinWidth;h&&(a[s].minWidth=h);var c=t.barGap;null!=c&&(o.gap=c);var p=t.barCategoryGap;null!=p&&(o.categoryGap=p)}));var n={};return E(e,(function(t,e){n[e]={};var i=t.stacks,r=t.bandWidth,o=t.categoryGap;if(null==o){var a=G(i).length;o=Math.max(35-4*a,15)+"%"}var s=$r(o,r),l=$r(t.gap,1),u=t.remainedWidth,h=t.autoWidthCount,c=(u-s)/(h+(h-1)*l);c=Math.max(c,0),E(i,(function(t){var e=t.maxWidth,n=t.minWidth;if(t.width){i=t.width;e&&(i=Math.min(i,e)),n&&(i=Math.max(i,n)),t.width=i,u-=i+l*i,h--}else{var i=c;e&&ei&&(i=n),i!==c&&(t.width=i,u-=i+l*i,h--)}})),c=(u-s)/(h+(h-1)*l),c=Math.max(c,0);var p,d=0;E(i,(function(t,e){t.width||(t.width=c),p=t,d+=t.width*(1+l)})),p&&(d-=p.width*l);var f=-d/2;E(i,(function(t,i){n[e][i]=n[e][i]||{bandWidth:r,offset:f,width:t.width},f+=t.width*(1+l)}))})),n}function Ux(t,e){var n=Hx(t,e),i=Yx(n);E(n,(function(t){var e=t.getData(),n=t.coordinateSystem.getBaseAxis(),r=Gx(t),o=i[Wx(n)][r],a=o.offset,s=o.width;e.setLayout({bandWidth:o.bandWidth,offset:a,size:s})}))}function Zx(t){return{seriesType:t,plan:kg(),reset:function(t){if(jx(t)){var e=t.getData(),n=t.coordinateSystem,i=n.getBaseAxis(),r=n.getOtherAxis(i),o=e.getDimensionIndex(e.mapDimension(r.dim)),a=e.getDimensionIndex(e.mapDimension(i.dim)),s=t.get("showBackground",!0),l=e.mapDimension(r.dim),u=e.getCalculationInfo("stackResultDimension"),h=mx(e,l)&&!!e.getCalculationInfo("stackedOnSeries"),c=r.isHorizontal(),p=function(t,e){var n=e.model.get("startValue");n||(n=0);return e.toGlobalCoord(e.dataToCoord("log"===e.type?n>0?n:1:n))}(0,r),d=qx(t),f=t.get("barMinHeight")||0,g=u&&e.getDimensionIndex(u),y=e.getLayout("size"),v=e.getLayout("offset");return{progress:function(t,e){for(var i,r=t.count,l=d&&Bx(3*r),u=d&&s&&Bx(3*r),m=d&&Bx(r),x=n.master.getRect(),_=c?x.width:x.height,b=e.getStore(),w=0;null!=(i=t.next());){var S=b.get(h?g:o,i),M=b.get(a,i),I=p,T=void 0;h&&(T=+S-b.get(o,i));var C=void 0,D=void 0,A=void 0,k=void 0;if(c){var L=n.dataToPoint([S,M]);if(h)I=n.dataToPoint([T,M])[0];C=I,D=L[1]+v,A=L[0]-I,k=y,Math.abs(A)0)for(var s=0;s=0;--s)if(l[u]){o=l[u];break}o=o||a.none}if(Y(o)){var h=null==t.level?0:t.level>=0?t.level:o.length+t.level;o=o[h=Math.min(h,o.length-1)]}}return Jc(new Date(t.value),o,r,i)}(t,e,n,this.getSetting("locale"),i)},e.prototype.getTicks=function(){var t=this._interval,e=this._extent,n=[];if(!t)return n;n.push({value:e[0],level:0});var i=this.getSetting("useUTC"),r=function(t,e,n,i){var r=1e4,o=jc,a=0;function s(t,e,n,r,o,a,s){for(var l=new Date(e),u=e,h=l[r]();u1&&0===u&&o.unshift({value:o[0].value-p})}}for(u=0;u=i[0]&&v<=i[1]&&c++)}var m=(i[1]-i[0])/e;if(c>1.5*m&&p>m/1.5)break;if(u.push(g),c>m||t===o[d])break}h=[]}}0;var x=B(z(u,(function(t){return B(t,(function(t){return t.value>=i[0]&&t.value<=i[1]&&!t.notAdd}))})),(function(t){return t.length>0})),_=[],b=x.length-1;for(d=0;dn&&(this._approxInterval=n);var o=$x.length,a=Math.min(function(t,e,n,i){for(;n>>1;t[r][1]16?16:t>7.5?7:t>3.5?4:t>1.5?2:1}function Qx(t){return(t/=2592e6)>6?6:t>3?3:t>2?2:1}function t_(t){return(t/=Gc)>12?12:t>6?6:t>3.5?4:t>2?2:1}function e_(t,e){return(t/=e?Fc:Bc)>30?30:t>20?20:t>15?15:t>10?10:t>5?5:t>2?2:1}function n_(t){return po(t,!0)}function i_(t,e,n){var i=new Date(t);switch(Kc(e)){case"year":case"month":i[up(n)](0);case"day":i[hp(n)](1);case"hour":i[cp(n)](0);case"minute":i[pp(n)](0);case"second":i[dp(n)](0),i[fp(n)](0)}return i.getTime()}bx.registerClass(Kx);var r_=bx.prototype,o_=Ex.prototype,a_=Jr,s_=Math.floor,l_=Math.ceil,u_=Math.pow,h_=Math.log,c_=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="log",e.base=10,e._originalScale=new Ex,e._interval=0,e}return n(e,t),e.prototype.getTicks=function(t){var e=this._originalScale,n=this._extent,i=e.getExtent();return z(o_.getTicks.call(this,t),(function(t){var e=t.value,r=Jr(u_(this.base,e));return r=e===n[0]&&this._fixMin?d_(r,i[0]):r,{value:r=e===n[1]&&this._fixMax?d_(r,i[1]):r}}),this)},e.prototype.setExtent=function(t,e){var n=h_(this.base);t=h_(Math.max(0,t))/n,e=h_(Math.max(0,e))/n,o_.setExtent.call(this,t,e)},e.prototype.getExtent=function(){var t=this.base,e=r_.getExtent.call(this);e[0]=u_(t,e[0]),e[1]=u_(t,e[1]);var n=this._originalScale.getExtent();return this._fixMin&&(e[0]=d_(e[0],n[0])),this._fixMax&&(e[1]=d_(e[1],n[1])),e},e.prototype.unionExtent=function(t){this._originalScale.unionExtent(t);var e=this.base;t[0]=h_(t[0])/h_(e),t[1]=h_(t[1])/h_(e),r_.unionExtent.call(this,t)},e.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},e.prototype.calcNiceTicks=function(t){t=t||10;var e=this._extent,n=e[1]-e[0];if(!(n===1/0||n<=0)){var i=ho(n);for(t/n*i<=.5&&(i*=10);!isNaN(i)&&Math.abs(i)<1&&Math.abs(i)>0;)i*=10;var r=[Jr(l_(e[0]/i)*i),Jr(s_(e[1]/i)*i)];this._interval=i,this._niceExtent=r}},e.prototype.calcNiceExtent=function(t){o_.calcNiceExtent.call(this,t),this._fixMin=t.fixMin,this._fixMax=t.fixMax},e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return Lx(t=h_(t)/h_(this.base),this._extent)},e.prototype.normalize=function(t){return Px(t=h_(t)/h_(this.base),this._extent)},e.prototype.scale=function(t){return t=Ox(t,this._extent),u_(this.base,t)},e.type="log",e}(bx),p_=c_.prototype;function d_(t,e){return a_(t,to(e))}p_.getMinorTicks=o_.getMinorTicks,p_.getLabel=o_.getLabel,bx.registerClass(c_);var f_=function(){function t(t,e,n){this._prepareParams(t,e,n)}return t.prototype._prepareParams=function(t,e,n){n[1]0&&s>0&&!l&&(a=0),a<0&&s<0&&!u&&(s=0));var c=this._determinedMin,p=this._determinedMax;return null!=c&&(a=c,l=!0),null!=p&&(s=p,u=!0),{min:a,max:s,minFixed:l,maxFixed:u,isBlank:h}},t.prototype.modifyDataMinMax=function(t,e){this[y_[t]]=e},t.prototype.setDeterminedMinMax=function(t,e){var n=g_[t];this[n]=e},t.prototype.freeze=function(){this.frozen=!0},t}(),g_={min:"_determinedMin",max:"_determinedMax"},y_={min:"_dataMin",max:"_dataMax"};function v_(t,e,n){var i=t.rawExtentInfo;return i||(i=new f_(t,e,n),t.rawExtentInfo=i,i)}function m_(t,e){return null==e?null:nt(e)?NaN:t.parse(e)}function x_(t,e){var n=t.type,i=v_(t,e,t.getExtent()).calculate();t.setBlank(i.isBlank);var r=i.min,o=i.max,a=e.ecModel;if(a&&"time"===n){var s=Hx("bar",a),l=!1;if(E(s,(function(t){l=l||t.getBaseAxis()===e.axis})),l){var u=Yx(s),h=function(t,e,n,i){var r=n.axis.getExtent(),o=Math.abs(r[1]-r[0]),a=function(t,e,n){if(t&&e){var i=t[Wx(e)];return null!=i&&null!=n?i[Gx(n)]:i}}(i,n.axis);if(void 0===a)return{min:t,max:e};var s=1/0;E(a,(function(t){s=Math.min(t.offset,s)}));var l=-1/0;E(a,(function(t){l=Math.max(t.offset+t.width,l)})),s=Math.abs(s),l=Math.abs(l);var u=s+l,h=e-t,c=h/(1-(s+l)/o)-h;return e+=c*(l/u),t-=c*(s/u),{min:t,max:e}}(r,o,e,u);r=h.min,o=h.max}}return{extent:[r,o],fixMin:i.minFixed,fixMax:i.maxFixed}}function __(t,e){var n=e,i=x_(t,n),r=i.extent,o=n.get("splitNumber");t instanceof c_&&(t.base=n.get("logBase"));var a=t.type,s=n.get("interval"),l="interval"===a||"time"===a;t.setExtent(r[0],r[1]),t.calcNiceExtent({splitNumber:o,fixMin:i.fixMin,fixMax:i.fixMax,minInterval:l?n.get("minInterval"):null,maxInterval:l?n.get("maxInterval"):null}),null!=s&&t.setInterval&&t.setInterval(s)}function b_(t,e){if(e=e||t.get("type"))switch(e){case"category":return new Rx({ordinalMeta:t.getOrdinalMeta?t.getOrdinalMeta():t.getCategories(),extent:[1/0,-1/0]});case"time":return new Kx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get("useUTC")});default:return new(bx.getClass(e)||Ex)}}function w_(t){var e,n,i=t.getLabelModel().get("formatter"),r="category"===t.type?t.scale.getExtent()[0]:null;return"time"===t.scale.type?(n=i,function(e,i){return t.scale.getFormattedLabel(e,i,n)}):U(i)?function(e){return function(n){var i=t.scale.getLabel(n);return e.replace("{value}",null!=i?i:"")}}(i):X(i)?(e=i,function(n,i){return null!=r&&(i=n.value-r),e(S_(t,n),i,null!=n.level?{level:n.level}:null)}):function(e){return t.scale.getLabel(e)}}function S_(t,e){return"category"===t.type?t.scale.getLabel(e):e.value}function M_(t,e){var n=e*Math.PI/180,i=t.width,r=t.height,o=i*Math.abs(Math.cos(n))+Math.abs(r*Math.sin(n)),a=i*Math.abs(Math.sin(n))+Math.abs(r*Math.cos(n));return new ze(t.x,t.y,o,a)}function I_(t){var e=t.get("interval");return null==e?"auto":e}function T_(t){return"category"===t.type&&0===I_(t.getLabelModel())}function C_(t,e){var n={};return E(t.mapDimensionsAll(e),(function(e){n[xx(t,e)]=!0})),G(n)}var D_=function(){function t(){}return t.prototype.getNeedCrossZero=function(){return!this.option.scale},t.prototype.getCoordSysModel=function(){},t}();var A_={isDimensionStacked:mx,enableDataStack:vx,getStackedDimension:xx};var k_=Object.freeze({__proto__:null,createList:function(t){return _x(null,t)},getLayoutRect:kp,dataStack:A_,createScale:function(t,e){var n=e;e instanceof Cc||(n=new Cc(e));var i=b_(n);return i.setExtent(t[0],t[1]),__(i,n),i},mixinAxisModelCommonMethods:function(t){R(t,D_)},getECData:rl,createTextStyle:function(t,e){return oc(t,null,null,"normal"!==(e=e||{}).state)},createDimensions:function(t,e){return px(t,e).dimensions},createSymbol:Xy,enableHoverEmphasis:Ul});function L_(t,e){return Math.abs(t-e)<1e-8}function P_(t,e,n){var i=0,r=t[0];if(!r)return!1;for(var o=1;on&&(t=r,n=a)}if(t)return function(t){for(var e=0,n=0,i=0,r=t.length,o=t[r-1][0],a=t[r-1][1],s=0;s>1^-(1&s),l=l>>1^-(1&l),r=s+=r,o=l+=o,i.push([s/n,l/n])}return i}function H_(t,e){return z(B((t=function(t){if(!t.UTF8Encoding)return t;var e=t,n=e.UTF8Scale;return null==n&&(n=1024),E(e.features,(function(t){var e=t.geometry,i=e.encodeOffsets,r=e.coordinates;if(i)switch(e.type){case"LineString":e.coordinates=W_(r,i,n);break;case"Polygon":case"MultiLineString":G_(r,i,n);break;case"MultiPolygon":E(r,(function(t,e){return G_(t,i[e],n)}))}})),e.UTF8Encoding=!1,e}(t)).features,(function(t){return t.geometry&&t.properties&&t.geometry.coordinates.length>0})),(function(t){var n=t.properties,i=t.geometry,r=[];switch(i.type){case"Polygon":var o=i.coordinates;r.push(new z_(o[0],o.slice(1)));break;case"MultiPolygon":E(i.coordinates,(function(t){t[0]&&r.push(new z_(t[0],t.slice(1)))}));break;case"LineString":r.push(new V_([i.coordinates]));break;case"MultiLineString":r.push(new V_(i.coordinates))}var a=new B_(n[e||"name"],r,n.cp);return a.properties=n,a}))}var Y_=Object.freeze({__proto__:null,linearMap:Kr,round:Jr,asc:Qr,getPrecision:to,getPrecisionSafe:eo,getPixelPrecision:no,getPercentWithPrecision:function(t,e,n){return t[e]&&io(t,n)[e]||0},MAX_SAFE_INTEGER:oo,remRadian:ao,isRadianAroundZero:so,parseDate:uo,quantity:ho,quantityExponent:co,nice:po,quantile:fo,reformIntervals:go,isNumeric:vo,numericToNumber:yo}),X_=Object.freeze({__proto__:null,parse:uo,format:Jc}),U_=Object.freeze({__proto__:null,extendShape:Ch,extendPath:Ah,makePath:Ph,makeImage:Oh,mergePath:Nh,resizePath:Eh,createIcon:Uh,updateProps:vh,initProps:mh,getTransform:Bh,clipPointsByRect:Yh,clipRectByRect:Xh,registerShape:kh,getShapeClass:Lh,Group:Br,Image:Ns,Text:Xs,Circle:Su,Ellipse:Iu,Sector:Fu,Ring:Wu,Polygon:Xu,Polyline:Zu,Rect:Ws,Line:Ku,BezierCurve:th,Arc:nh,IncrementalDisplayable:dh,CompoundPath:ih,LinearGradient:oh,RadialGradient:ah,BoundingRect:ze}),Z_=Object.freeze({__proto__:null,addCommas:gp,toCamelCase:yp,normalizeCssArray:vp,encodeHTML:re,formatTpl:bp,getTooltipMarker:wp,formatTime:function(t,e,n){"week"!==t&&"month"!==t&&"quarter"!==t&&"half-year"!==t&&"year"!==t||(t="MM-dd\nyyyy");var i=uo(e),r=n?"getUTC":"get",o=i[r+"FullYear"](),a=i[r+"Month"]()+1,s=i[r+"Date"](),l=i[r+"Hours"](),u=i[r+"Minutes"](),h=i[r+"Seconds"](),c=i[r+"Milliseconds"]();return t=t.replace("MM",qc(a,2)).replace("M",a).replace("yyyy",o).replace("yy",qc(o%100+"",2)).replace("dd",qc(s,2)).replace("d",s).replace("hh",qc(l,2)).replace("h",l).replace("mm",qc(u,2)).replace("m",u).replace("ss",qc(h,2)).replace("s",h).replace("SSS",qc(c,3))},capitalFirst:function(t){return t?t.charAt(0).toUpperCase()+t.substr(1):t},truncateText:function(t,e,n,i,r){var o={};return pa(o,t,e,n,i,r),o.text},getTextRect:function(t,e,n,i,r,o,a,s){return new Xs({style:{text:t,font:e,align:n,verticalAlign:i,padding:r,rich:o,overflow:a?"truncate":null,lineHeight:s}}).getBoundingRect()}}),j_=Object.freeze({__proto__:null,map:z,each:E,indexOf:P,inherits:O,reduce:V,filter:B,bind:W,curry:H,isArray:Y,isString:U,isObject:q,isFunction:X,extend:A,defaults:k,clone:T,merge:C}),q_=Vo();function K_(t,e){var n=z(e,(function(e){return t.scale.parse(e)}));return"time"===t.type&&n.length>0&&(n.sort(),n.unshift(n[0]),n.push(n[n.length-1])),n}function $_(t){var e=t.getLabelModel().get("customValues");if(e){var n=w_(t),i=t.scale.getExtent();return{labels:z(B(K_(t,e),(function(t){return t>=i[0]&&t<=i[1]})),(function(e){var i={value:e};return{formattedLabel:n(i),rawLabel:t.scale.getLabel(i),tickValue:e}}))}}return"category"===t.type?function(t){var e=t.getLabelModel(),n=Q_(t,e);return!e.get("show")||t.scale.isBlank()?{labels:[],labelCategoryInterval:n.labelCategoryInterval}:n}(t):function(t){var e=t.scale.getTicks(),n=w_(t);return{labels:z(e,(function(e,i){return{level:e.level,formattedLabel:n(e,i),rawLabel:t.scale.getLabel(e),tickValue:e.value}}))}}(t)}function J_(t,e){var n=t.getTickModel().get("customValues");if(n){var i=t.scale.getExtent();return{ticks:B(K_(t,n),(function(t){return t>=i[0]&&t<=i[1]}))}}return"category"===t.type?function(t,e){var n,i,r=tb(t,"ticks"),o=I_(e),a=eb(r,o);if(a)return a;e.get("show")&&!t.scale.isBlank()||(n=[]);if(X(o))n=rb(t,o,!0);else if("auto"===o){var s=Q_(t,t.getLabelModel());i=s.labelCategoryInterval,n=z(s.labels,(function(t){return t.tickValue}))}else n=ib(t,i=o,!0);return nb(r,o,{ticks:n,tickCategoryInterval:i})}(t,e):{ticks:z(t.scale.getTicks(),(function(t){return t.value}))}}function Q_(t,e){var n,i,r=tb(t,"labels"),o=I_(e),a=eb(r,o);return a||(X(o)?n=rb(t,o):(i="auto"===o?function(t){var e=q_(t).autoInterval;return null!=e?e:q_(t).autoInterval=t.calculateCategoryInterval()}(t):o,n=ib(t,i)),nb(r,o,{labels:n,labelCategoryInterval:i}))}function tb(t,e){return q_(t)[e]||(q_(t)[e]=[])}function eb(t,e){for(var n=0;n1&&h/l>2&&(u=Math.round(Math.ceil(u/l)*l));var c=T_(t),p=a.get("showMinLabel")||c,d=a.get("showMaxLabel")||c;p&&u!==o[0]&&g(o[0]);for(var f=u;f<=o[1];f+=l)g(f);function g(t){var e={value:t};s.push(n?t:{formattedLabel:i(e),rawLabel:r.getLabel(e),tickValue:t})}return d&&f-l!==o[1]&&g(o[1]),s}function rb(t,e,n){var i=t.scale,r=w_(t),o=[];return E(i.getTicks(),(function(t){var a=i.getLabel(t),s=t.value;e(t.value,a)&&o.push(n?s:{formattedLabel:r(t),rawLabel:a,tickValue:s})})),o}var ob=[0,1],ab=function(){function t(t,e,n){this.onBand=!1,this.inverse=!1,this.dim=t,this.scale=e,this._extent=n||[0,0]}return t.prototype.contain=function(t){var e=this._extent,n=Math.min(e[0],e[1]),i=Math.max(e[0],e[1]);return t>=n&&t<=i},t.prototype.containData=function(t){return this.scale.contain(t)},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.getPixelPrecision=function(t){return no(t||this.scale.getExtent(),this._extent)},t.prototype.setExtent=function(t,e){var n=this._extent;n[0]=t,n[1]=e},t.prototype.dataToCoord=function(t,e){var n=this._extent,i=this.scale;return t=i.normalize(t),this.onBand&&"ordinal"===i.type&&sb(n=n.slice(),i.count()),Kr(t,ob,n,e)},t.prototype.coordToData=function(t,e){var n=this._extent,i=this.scale;this.onBand&&"ordinal"===i.type&&sb(n=n.slice(),i.count());var r=Kr(t,n,ob,e);return this.scale.scale(r)},t.prototype.pointToData=function(t,e){},t.prototype.getTicksCoords=function(t){var e=(t=t||{}).tickModel||this.getTickModel(),n=z(J_(this,e).ticks,(function(t){return{coord:this.dataToCoord("ordinal"===this.scale.type?this.scale.getRawOrdinalNumber(t):t),tickValue:t}}),this);return function(t,e,n,i){var r=e.length;if(!t.onBand||n||!r)return;var o,a,s=t.getExtent();if(1===r)e[0].coord=s[0],o=e[1]={coord:s[1],tickValue:e[0].tickValue};else{var l=e[r-1].tickValue-e[0].tickValue,u=(e[r-1].coord-e[0].coord)/l;E(e,(function(t){t.coord-=u/2}));var h=t.scale.getExtent();a=1+h[1]-e[r-1].tickValue,o={coord:e[r-1].coord+u*a,tickValue:h[1]+1},e.push(o)}var c=s[0]>s[1];p(e[0].coord,s[0])&&(i?e[0].coord=s[0]:e.shift());i&&p(s[0],e[0].coord)&&e.unshift({coord:s[0]});p(s[1],o.coord)&&(i?o.coord=s[1]:e.pop());i&&p(o.coord,s[1])&&e.push({coord:s[1]});function p(t,e){return t=Jr(t),e=Jr(e),c?t>e:t0&&t<100||(t=5),z(this.scale.getMinorTicks(t),(function(t){return z(t,(function(t){return{coord:this.dataToCoord(t),tickValue:t}}),this)}),this)},t.prototype.getViewLabels=function(){return $_(this).labels},t.prototype.getLabelModel=function(){return this.model.getModel("axisLabel")},t.prototype.getTickModel=function(){return this.model.getModel("axisTick")},t.prototype.getBandWidth=function(){var t=this._extent,e=this.scale.getExtent(),n=e[1]-e[0]+(this.onBand?1:0);0===n&&(n=1);var i=Math.abs(t[1]-t[0]);return Math.abs(i)/n},t.prototype.calculateCategoryInterval=function(){return function(t){var e=function(t){var e=t.getLabelModel();return{axisRotate:t.getRotate?t.getRotate():t.isHorizontal&&!t.isHorizontal()?90:0,labelRotate:e.get("rotate")||0,font:e.getFont()}}(t),n=w_(t),i=(e.axisRotate-e.labelRotate)/180*Math.PI,r=t.scale,o=r.getExtent(),a=r.count();if(o[1]-o[0]<1)return 0;var s=1;a>40&&(s=Math.max(1,Math.floor(a/40)));for(var l=o[0],u=t.dataToCoord(l+1)-t.dataToCoord(l),h=Math.abs(u*Math.cos(i)),c=Math.abs(u*Math.sin(i)),p=0,d=0;l<=o[1];l+=s){var f,g,y=Sr(n({value:l}),e.font,"center","top");f=1.3*y.width,g=1.3*y.height,p=Math.max(p,f,7),d=Math.max(d,g,7)}var v=p/h,m=d/c;isNaN(v)&&(v=1/0),isNaN(m)&&(m=1/0);var x=Math.max(0,Math.floor(Math.min(v,m))),_=q_(t.model),b=t.getExtent(),w=_.lastAutoInterval,S=_.lastTickCount;return null!=w&&null!=S&&Math.abs(w-x)<=1&&Math.abs(S-a)<=1&&w>x&&_.axisExtent0===b[0]&&_.axisExtent1===b[1]?x=w:(_.lastTickCount=a,_.lastAutoInterval=x,_.axisExtent0=b[0],_.axisExtent1=b[1]),x}(this)},t}();function sb(t,e){var n=(t[1]-t[0])/e/2;t[0]+=n,t[1]-=n}var lb=2*Math.PI,ub=hs.CMD,hb=["top","right","bottom","left"];function cb(t,e,n,i,r){var o=n.width,a=n.height;switch(t){case"top":i.set(n.x+o/2,n.y-e),r.set(0,-1);break;case"bottom":i.set(n.x+o/2,n.y+a+e),r.set(0,1);break;case"left":i.set(n.x-e,n.y+a/2),r.set(-1,0);break;case"right":i.set(n.x+o+e,n.y+a/2),r.set(1,0)}}function pb(t,e,n,i,r,o,a,s,l){a-=t,s-=e;var u=Math.sqrt(a*a+s*s),h=(a/=u)*n+t,c=(s/=u)*n+e;if(Math.abs(i-r)%lb<1e-4)return l[0]=h,l[1]=c,u-n;if(o){var p=i;i=gs(r),r=gs(p)}else i=gs(i),r=gs(r);i>r&&(r+=lb);var d=Math.atan2(s,a);if(d<0&&(d+=lb),d>=i&&d<=r||d+lb>=i&&d+lb<=r)return l[0]=h,l[1]=c,u-n;var f=n*Math.cos(i)+t,g=n*Math.sin(i)+e,y=n*Math.cos(r)+t,v=n*Math.sin(r)+e,m=(f-a)*(f-a)+(g-s)*(g-s),x=(y-a)*(y-a)+(v-s)*(v-s);return m0){e=e/180*Math.PI,mb.fromArray(t[0]),xb.fromArray(t[1]),_b.fromArray(t[2]),De.sub(bb,mb,xb),De.sub(wb,_b,xb);var n=bb.len(),i=wb.len();if(!(n<.001||i<.001)){bb.scale(1/n),wb.scale(1/i);var r=bb.dot(wb);if(Math.cos(e)1&&De.copy(Ib,_b),Ib.toArray(t[1])}}}}function Cb(t,e,n){if(n<=180&&n>0){n=n/180*Math.PI,mb.fromArray(t[0]),xb.fromArray(t[1]),_b.fromArray(t[2]),De.sub(bb,xb,mb),De.sub(wb,_b,xb);var i=bb.len(),r=wb.len();if(!(i<.001||r<.001))if(bb.scale(1/i),wb.scale(1/r),bb.dot(e)=a)De.copy(Ib,_b);else{Ib.scaleAndAdd(wb,o/Math.tan(Math.PI/2-s));var l=_b.x!==xb.x?(Ib.x-xb.x)/(_b.x-xb.x):(Ib.y-xb.y)/(_b.y-xb.y);if(isNaN(l))return;l<0?De.copy(Ib,xb):l>1&&De.copy(Ib,_b)}Ib.toArray(t[1])}}}function Db(t,e,n,i){var r="normal"===n,o=r?t:t.ensureState(n);o.ignore=e;var a=i.get("smooth");a&&!0===a&&(a=.3),o.shape=o.shape||{},a>0&&(o.shape.smooth=a);var s=i.getModel("lineStyle").getLineStyle();r?t.useStyle(s):o.style=s}function Ab(t,e){var n=e.smooth,i=e.points;if(i)if(t.moveTo(i[0][0],i[0][1]),n>0&&i.length>=3){var r=Vt(i[0],i[1]),o=Vt(i[1],i[2]);if(!r||!o)return t.lineTo(i[1][0],i[1][1]),void t.lineTo(i[2][0],i[2][1]);var a=Math.min(r,o)*n,s=Gt([],i[1],i[0],a/r),l=Gt([],i[1],i[2],a/o),u=Gt([],s,l,.5);t.bezierCurveTo(s[0],s[1],s[0],s[1],u[0],u[1]),t.bezierCurveTo(l[0],l[1],l[0],l[1],i[2][0],i[2][1])}else for(var h=1;h0&&o&&_(-h/a,0,a);var f,g,y=t[0],v=t[a-1];return m(),f<0&&b(-f,.8),g<0&&b(g,.8),m(),x(f,g,1),x(g,f,-1),m(),f<0&&w(-f),g<0&&w(g),u}function m(){f=y.rect[e]-i,g=r-v.rect[e]-v.rect[n]}function x(t,e,n){if(t<0){var i=Math.min(e,-t);if(i>0){_(i*n,0,a);var r=i+t;r<0&&b(-r*n,1)}else b(-t*n,1)}}function _(n,i,r){0!==n&&(u=!0);for(var o=i;o0)for(l=0;l0;l--){_(-(o[l-1]*c),l,a)}}}function w(t){var e=t<0?-1:1;t=Math.abs(t);for(var n=Math.ceil(t/(a-1)),i=0;i0?_(n,0,i+1):_(-n,a-i-1,a),(t-=n)<=0)return}}function Rb(t,e,n,i){return Ob(t,"y","height",e,n,i)}function Nb(t){var e=[];t.sort((function(t,e){return e.priority-t.priority}));var n=new ze(0,0,0,0);function i(t){if(!t.ignore){var e=t.ensureState("emphasis");null==e.ignore&&(e.ignore=!1)}t.ignore=!0}for(var r=0;r=0&&n.attr(d.oldLayoutSelect),P(u,"emphasis")>=0&&n.attr(d.oldLayoutEmphasis)),vh(n,s,e,a)}else if(n.attr(s),!pc(n).valueAnimation){var h=rt(n.style.opacity,1);n.style.opacity=0,mh(n,{style:{opacity:h}},e,a)}if(d.oldLayout=s,n.states.select){var c=d.oldLayoutSelect={};Wb(c,s,Hb),Wb(c,n.states.select,Hb)}if(n.states.emphasis){var p=d.oldLayoutEmphasis={};Wb(p,s,Hb),Wb(p,n.states.emphasis,Hb)}fc(n,a,l,e,e)}if(i&&!i.ignore&&!i.invisible){r=(d=Gb(i)).oldLayout;var d,f={points:i.shape.points};r?(i.attr({shape:r}),vh(i,{shape:f},e)):(i.setShape(f),i.style.strokePercent=0,mh(i,{style:{strokePercent:1}},e)),d.oldLayout=f}},t}(),Xb=Vo();var Ub=Math.sin,Zb=Math.cos,jb=Math.PI,qb=2*Math.PI,Kb=180/jb,$b=function(){function t(){}return t.prototype.reset=function(t){this._start=!0,this._d=[],this._str="",this._p=Math.pow(10,t||4)},t.prototype.moveTo=function(t,e){this._add("M",t,e)},t.prototype.lineTo=function(t,e){this._add("L",t,e)},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){this._add("C",t,e,n,i,r,o)},t.prototype.quadraticCurveTo=function(t,e,n,i){this._add("Q",t,e,n,i)},t.prototype.arc=function(t,e,n,i,r,o){this.ellipse(t,e,n,n,0,i,r,o)},t.prototype.ellipse=function(t,e,n,i,r,o,a,s){var l=a-o,u=!s,h=Math.abs(l),c=pi(h-qb)||(u?l>=qb:-l>=qb),p=l>0?l%qb:l%qb+qb,d=!1;d=!!c||!pi(h)&&p>=jb==!!u;var f=t+n*Zb(o),g=e+i*Ub(o);this._start&&this._add("M",f,g);var y=Math.round(r*Kb);if(c){var v=1/this._p,m=(u?1:-1)*(qb-v);this._add("A",n,i,y,1,+u,t+n*Zb(o+m),e+i*Ub(o+m)),v>.01&&this._add("A",n,i,y,0,+u,f,g)}else{var x=t+n*Zb(a),_=e+i*Ub(a);this._add("A",n,i,y,+d,+u,x,_)}},t.prototype.rect=function(t,e,n,i){this._add("M",t,e),this._add("l",n,0),this._add("l",0,i),this._add("l",-n,0),this._add("Z")},t.prototype.closePath=function(){this._d.length>0&&this._add("Z")},t.prototype._add=function(t,e,n,i,r,o,a,s,l){for(var u=[],h=this._p,c=1;c"}(r,o)+("style"!==r?re(a):a||"")+(i?""+n+z(i,(function(e){return t(e)})).join(n)+n:"")+("")}(t)}function uw(t){return{zrId:t,shadowCache:{},patternCache:{},gradientCache:{},clipPathCache:{},defs:{},cssNodes:{},cssAnims:{},cssStyleCache:{},cssAnimIdx:0,shadowIdx:0,gradientIdx:0,patternIdx:0,clipPathIdx:0}}function hw(t,e,n,i){return sw("svg","root",{width:t,height:e,xmlns:iw,"xmlns:xlink":rw,version:"1.1",baseProfile:"full",viewBox:!!i&&"0 0 "+t+" "+e},n)}var cw=0;function pw(){return cw++}var dw={cubicIn:"0.32,0,0.67,0",cubicOut:"0.33,1,0.68,1",cubicInOut:"0.65,0,0.35,1",quadraticIn:"0.11,0,0.5,0",quadraticOut:"0.5,1,0.89,1",quadraticInOut:"0.45,0,0.55,1",quarticIn:"0.5,0,0.75,0",quarticOut:"0.25,1,0.5,1",quarticInOut:"0.76,0,0.24,1",quinticIn:"0.64,0,0.78,0",quinticOut:"0.22,1,0.36,1",quinticInOut:"0.83,0,0.17,1",sinusoidalIn:"0.12,0,0.39,0",sinusoidalOut:"0.61,1,0.88,1",sinusoidalInOut:"0.37,0,0.63,1",exponentialIn:"0.7,0,0.84,0",exponentialOut:"0.16,1,0.3,1",exponentialInOut:"0.87,0,0.13,1",circularIn:"0.55,0,1,0.45",circularOut:"0,0.55,0.45,1",circularInOut:"0.85,0,0.15,1"},fw="transform-origin";function gw(t,e,n){var i=A({},t.shape);A(i,e),t.buildPath(n,i);var r=new $b;return r.reset(wi(t)),n.rebuildPath(r,1),r.generateStr(),r.getStr()}function yw(t,e){var n=e.originX,i=e.originY;(n||i)&&(t[fw]=n+"px "+i+"px")}var vw={fill:"fill",opacity:"opacity",lineWidth:"stroke-width",lineDashOffset:"stroke-dashoffset"};function mw(t,e){var n=e.zrId+"-ani-"+e.cssAnimIdx++;return e.cssAnims[n]=t,n}function xw(t){return U(t)?dw[t]?"cubic-bezier("+dw[t]+")":Pn(t)?t:"":""}function _w(t,e,n,i){var r=t.animators,o=r.length,a=[];if(t instanceof ih){var s=function(t,e,n){var i,r,o=t.shape.paths,a={};if(E(o,(function(t){var e=uw(n.zrId);e.animation=!0,_w(t,{},e,!0);var o=e.cssAnims,s=e.cssNodes,l=G(o),u=l.length;if(u){var h=o[r=l[u-1]];for(var c in h){var p=h[c];a[c]=a[c]||{d:""},a[c].d+=p.d||""}for(var d in s){var f=s[d].animation;f.indexOf(r)>=0&&(i=f)}}})),i){e.d=!1;var s=mw(a,n);return i.replace(r,s)}}(t,e,n);if(s)a.push(s);else if(!o)return}else if(!o)return;for(var l={},u=0;u0})).length)return mw(h,n)+" "+r[0]+" both"}for(var y in l){(s=g(l[y]))&&a.push(s)}if(a.length){var v=n.zrId+"-cls-"+pw();n.cssNodes["."+v]={animation:a.join(",")},e.class=v}}function bw(t,e,n,i){var r=JSON.stringify(t),o=n.cssStyleCache[r];o||(o=n.zrId+"-cls-"+pw(),n.cssStyleCache[r]=o,n.cssNodes["."+o+(i?":hover":"")]=t),e.class=e.class?e.class+" "+o:o}var ww=Math.round;function Sw(t){return t&&U(t.src)}function Mw(t){return t&&X(t.toDataURL)}function Iw(t,e,n,i){nw((function(r,o){var a="fill"===r||"stroke"===r;a&&_i(o)?Ew(e,t,r,i):a&&vi(o)?zw(n,t,r,i):t[r]=o,a&&i.ssr&&"none"===o&&(t["pointer-events"]="visible")}),e,n,!1),function(t,e,n){var i=t.style;if(function(t){return t&&(t.shadowBlur||t.shadowOffsetX||t.shadowOffsetY)}(i)){var r=function(t){var e=t.style,n=t.getGlobalScale();return[e.shadowColor,(e.shadowBlur||0).toFixed(2),(e.shadowOffsetX||0).toFixed(2),(e.shadowOffsetY||0).toFixed(2),n[0],n[1]].join(",")}(t),o=n.shadowCache,a=o[r];if(!a){var s=t.getGlobalScale(),l=s[0],u=s[1];if(!l||!u)return;var h=i.shadowOffsetX||0,c=i.shadowOffsetY||0,p=i.shadowBlur,d=hi(i.shadowColor),f=d.opacity,g=d.color,y=p/2/l+" "+p/2/u;a=n.zrId+"-s"+n.shadowIdx++,n.defs[a]=sw("filter",a,{id:a,x:"-100%",y:"-100%",width:"300%",height:"300%"},[sw("feDropShadow","",{dx:h/l,dy:c/u,stdDeviation:y,"flood-color":g,"flood-opacity":f})]),o[r]=a}e.filter=bi(a)}}(n,t,i)}function Tw(t,e){var n=Ur(e);n&&(n.each((function(e,n){null!=e&&(t[(ow+n).toLowerCase()]=e+"")})),e.isSilent()&&(t[ow+"silent"]="true"))}function Cw(t){return pi(t[0]-1)&&pi(t[1])&&pi(t[2])&&pi(t[3]-1)}function Dw(t,e,n){if(e&&(!function(t){return pi(t[4])&&pi(t[5])}(e)||!Cw(e))){var i=n?10:1e4;t.transform=Cw(e)?"translate("+ww(e[4]*i)/i+" "+ww(e[5]*i)/i+")":function(t){return"matrix("+di(t[0])+","+di(t[1])+","+di(t[2])+","+di(t[3])+","+fi(t[4])+","+fi(t[5])+")"}(e)}}function Aw(t,e,n){for(var i=t.points,r=[],o=0;o=0&&a||o;s&&(r=si(s))}var l=i.lineWidth;l&&(l/=!i.strokeNoScale&&t.transform?t.transform[0]:1);var u={cursor:"pointer"};r&&(u.fill=r),i.stroke&&(u.stroke=i.stroke),l&&(u["stroke-width"]=l),bw(u,e,n,!0)}}(t,o,e),sw(s,t.id+"",o)}function Nw(t,e){return t instanceof ks?Rw(t,e):t instanceof Ns?function(t,e){var n=t.style,i=n.image;if(i&&!U(i)&&(Sw(i)?i=i.src:Mw(i)&&(i=i.toDataURL())),i){var r=n.x||0,o=n.y||0,a={href:i,width:n.width,height:n.height};return r&&(a.x=r),o&&(a.y=o),Dw(a,t.transform),Iw(a,n,t,e),Tw(a,t),e.animation&&_w(t,a,e),sw("image",t.id+"",a)}}(t,e):t instanceof Ps?function(t,e){var n=t.style,i=n.text;if(null!=i&&(i+=""),i&&!isNaN(n.x)&&!isNaN(n.y)){var r=n.font||a,s=n.x||0,l=function(t,e,n){return"top"===n?t+=e/2:"bottom"===n&&(t-=e/2),t}(n.y||0,Tr(r),n.textBaseline),u={"dominant-baseline":"central","text-anchor":gi[n.textAlign]||n.textAlign};if($s(n)){var h="",c=n.fontStyle,p=qs(n.fontSize);if(!parseFloat(p))return;var d=n.fontFamily||o,f=n.fontWeight;h+="font-size:"+p+";font-family:"+d+";",c&&"normal"!==c&&(h+="font-style:"+c+";"),f&&"normal"!==f&&(h+="font-weight:"+f+";"),u.style=h}else u.style="font: "+r;return i.match(/\s/)&&(u["xml:space"]="preserve"),s&&(u.x=s),l&&(u.y=l),Dw(u,t.transform),Iw(u,n,t,e),Tw(u,t),e.animation&&_w(t,u,e),sw("text",t.id+"",u,void 0,i)}}(t,e):void 0}function Ew(t,e,n,i){var r,o=t[n],a={gradientUnits:o.global?"userSpaceOnUse":"objectBoundingBox"};if(mi(o))r="linearGradient",a.x1=o.x,a.y1=o.y,a.x2=o.x2,a.y2=o.y2;else{if(!xi(o))return void 0;r="radialGradient",a.cx=rt(o.x,.5),a.cy=rt(o.y,.5),a.r=rt(o.r,.5)}for(var s=o.colorStops,l=[],u=0,h=s.length;ul?Jw(t,null==n[c+1]?null:n[c+1].elm,n,s,c):Qw(t,e,a,l))}(n,i,r):jw(r)?(jw(t.text)&&Xw(n,""),Jw(n,null,r,0,r.length-1)):jw(i)?Qw(n,i,0,i.length-1):jw(t.text)&&Xw(n,""):t.text!==e.text&&(jw(i)&&Qw(n,i,0,i.length-1),Xw(n,e.text)))}var nS=0,iS=function(){function t(t,e,n){if(this.type="svg",this.refreshHover=rS("refreshHover"),this.configLayer=rS("configLayer"),this.storage=e,this._opts=n=A({},n),this.root=t,this._id="zr"+nS++,this._oldVNode=hw(n.width,n.height),t&&!n.ssr){var i=this._viewport=document.createElement("div");i.style.cssText="position:relative;overflow:hidden";var r=this._svgDom=this._oldVNode.elm=aw("svg");tS(null,this._oldVNode),i.appendChild(r),t.appendChild(i)}this.resize(n.width,n.height)}return t.prototype.getType=function(){return this.type},t.prototype.getViewportRoot=function(){return this._viewport},t.prototype.getViewportRootOffset=function(){var t=this.getViewportRoot();if(t)return{offsetLeft:t.offsetLeft||0,offsetTop:t.offsetTop||0}},t.prototype.getSvgDom=function(){return this._svgDom},t.prototype.refresh=function(){if(this.root){var t=this.renderToVNode({willUpdate:!0});t.attrs.style="position:absolute;left:0;top:0;user-select:none",function(t,e){if(Kw(t,e))eS(t,e);else{var n=t.elm,i=Hw(n);$w(e),null!==i&&(Fw(i,e.elm,Yw(n)),Qw(i,[t],0,0))}}(this._oldVNode,t),this._oldVNode=t}},t.prototype.renderOneToVNode=function(t){return Nw(t,uw(this._id))},t.prototype.renderToVNode=function(t){t=t||{};var e=this.storage.getDisplayList(!0),n=this._width,i=this._height,r=uw(this._id);r.animation=t.animation,r.willUpdate=t.willUpdate,r.compress=t.compress,r.emphasis=t.emphasis,r.ssr=this._opts.ssr;var o=[],a=this._bgVNode=function(t,e,n,i){var r;if(n&&"none"!==n)if(r=sw("rect","bg",{width:t,height:e,x:"0",y:"0"}),_i(n))Ew({fill:n},r.attrs,"fill",i);else if(vi(n))zw({style:{fill:n},dirty:bt,getBoundingRect:function(){return{width:t,height:e}}},r.attrs,"fill",i);else{var o=hi(n),a=o.color,s=o.opacity;r.attrs.fill=a,s<1&&(r.attrs["fill-opacity"]=s)}return r}(n,i,this._backgroundColor,r);a&&o.push(a);var s=t.compress?null:this._mainVNode=sw("g","main",{},[]);this._paintList(e,r,s?s.children:o),s&&o.push(s);var l=z(G(r.defs),(function(t){return r.defs[t]}));if(l.length&&o.push(sw("defs","defs",{},l)),t.animation){var u=function(t,e,n){var i=(n=n||{}).newline?"\n":"",r=" {"+i,o=i+"}",a=z(G(t),(function(e){return e+r+z(G(t[e]),(function(n){return n+":"+t[e][n]+";"})).join(i)+o})).join(i),s=z(G(e),(function(t){return"@keyframes "+t+r+z(G(e[t]),(function(n){return n+r+z(G(e[t][n]),(function(i){var r=e[t][n][i];return"d"===i&&(r='path("'+r+'")'),i+":"+r+";"})).join(i)+o})).join(i)+o})).join(i);return a||s?[""].join(i):""}(r.cssNodes,r.cssAnims,{newline:!0});if(u){var h=sw("style","stl",{},[],u);o.push(h)}}return hw(n,i,o,t.useViewBox)},t.prototype.renderToString=function(t){return t=t||{},lw(this.renderToVNode({animation:rt(t.cssAnimation,!0),emphasis:rt(t.cssEmphasis,!0),willUpdate:!1,compress:!0,useViewBox:rt(t.useViewBox,!0)}),{newline:!0})},t.prototype.setBackgroundColor=function(t){this._backgroundColor=t},t.prototype.getSvgRoot=function(){return this._mainVNode&&this._mainVNode.elm},t.prototype._paintList=function(t,e,n){for(var i,r,o=t.length,a=[],s=0,l=0,u=0;u=0&&(!c||!r||c[f]!==r[f]);f--);for(var g=d-1;g>f;g--)i=a[--s-1];for(var y=f+1;y=a)}}for(var h=this.__startIndex;h15)break}n.prevElClipPaths&&u.restore()};if(p)if(0===p.length)s=l.__endIndex;else for(var _=d.dpr,b=0;b0&&t>i[0]){for(s=0;st);s++);a=n[i[s]]}if(i.splice(s+1,0,t),n[t]=e,!e.virtual)if(a){var l=a.dom;l.nextSibling?o.insertBefore(e.dom,l.nextSibling):o.appendChild(e.dom)}else o.firstChild?o.insertBefore(e.dom,o.firstChild):o.appendChild(e.dom);e.painter||(e.painter=this)}},t.prototype.eachLayer=function(t,e){for(var n=this._zlevelList,i=0;i0?uS:0),this._needsManuallyCompositing),u.__builtin__||I("ZLevel "+l+" has been used by unkown layer "+u.id),u!==o&&(u.__used=!0,u.__startIndex!==r&&(u.__dirty=!0),u.__startIndex=r,u.incremental?u.__drawIndex=-1:u.__drawIndex=r,e(r),o=u),1&s.__dirty&&!s.__inHover&&(u.__dirty=!0,u.incremental&&u.__drawIndex<0&&(u.__drawIndex=r))}e(r),this.eachBuiltinLayer((function(t,e){!t.__used&&t.getElementCount()>0&&(t.__dirty=!0,t.__startIndex=t.__endIndex=t.__drawIndex=0),t.__dirty&&t.__drawIndex<0&&(t.__drawIndex=t.__startIndex)}))},t.prototype.clear=function(){return this.eachBuiltinLayer(this._clearLayer),this},t.prototype._clearLayer=function(t){t.clear()},t.prototype.setBackgroundColor=function(t){this._backgroundColor=t,E(this._layers,(function(t){t.setUnpainted()}))},t.prototype.configLayer=function(t,e){if(e){var n=this._layerConfig;n[t]?C(n[t],e,!0):n[t]=e;for(var i=0;i-1&&(s.style.stroke=s.style.fill,s.style.fill="#fff",s.style.lineWidth=2),e},e.type="series.line",e.dependencies=["grid","polar"],e.defaultOption={z:3,coordinateSystem:"cartesian2d",legendHoverLink:!0,clip:!0,label:{position:"top"},endLabel:{show:!1,valueAnimation:!0,distance:8},lineStyle:{width:2,type:"solid"},emphasis:{scale:!0},step:!1,smooth:!1,smoothMonotone:null,symbol:"emptyCircle",symbolSize:4,symbolRotate:null,showSymbol:!0,showAllSymbol:"auto",connectNulls:!1,sampling:"none",animationEasing:"linear",progressive:0,hoverLayerThreshold:1/0,universalTransition:{divideShape:"clone"},triggerLineEvent:!1},e}(bg);function pS(t,e){var n=t.mapDimensionsAll("defaultedLabel"),i=n.length;if(1===i){var r=mf(t,e,n[0]);return null!=r?r+"":null}if(i){for(var o=[],a=0;a=0&&i.push(e[o])}return i.join(" ")}var fS=function(t){function e(e,n,i,r){var o=t.call(this)||this;return o.updateData(e,n,i,r),o}return n(e,t),e.prototype._createSymbol=function(t,e,n,i,r){this.removeAll();var o=Xy(t,-1,-1,2,2,null,r);o.attr({z2:100,culling:!0,scaleX:i[0]/2,scaleY:i[1]/2}),o.drift=gS,this._symbolType=t,this.add(o)},e.prototype.stopSymbolAnimation=function(t){this.childAt(0).stopAnimation(null,t)},e.prototype.getSymbolType=function(){return this._symbolType},e.prototype.getSymbolPath=function(){return this.childAt(0)},e.prototype.highlight=function(){Ol(this.childAt(0))},e.prototype.downplay=function(){Rl(this.childAt(0))},e.prototype.setZ=function(t,e){var n=this.childAt(0);n.zlevel=t,n.z=e},e.prototype.setDraggable=function(t,e){var n=this.childAt(0);n.draggable=t,n.cursor=!e&&t?"move":n.cursor},e.prototype.updateData=function(t,n,i,r){this.silent=!1;var o=t.getItemVisual(n,"symbol")||"circle",a=t.hostModel,s=e.getSymbolSize(t,n),l=o!==this._symbolType,u=r&&r.disableAnimation;if(l){var h=t.getItemVisual(n,"symbolKeepAspect");this._createSymbol(o,t,n,s,h)}else{(p=this.childAt(0)).silent=!1;var c={scaleX:s[0]/2,scaleY:s[1]/2};u?p.attr(c):vh(p,c,a,n),Sh(p)}if(this._updateCommon(t,n,s,i,r),l){var p=this.childAt(0);if(!u){c={scaleX:this._sizeX,scaleY:this._sizeY,style:{opacity:p.style.opacity}};p.scaleX=p.scaleY=0,p.style.opacity=0,mh(p,c,a,n)}}u&&this.childAt(0).stopAnimation("leave")},e.prototype._updateCommon=function(t,e,n,i,r){var o,a,s,l,u,h,c,p,d,f=this.childAt(0),g=t.hostModel;if(i&&(o=i.emphasisItemStyle,a=i.blurItemStyle,s=i.selectItemStyle,l=i.focus,u=i.blurScope,c=i.labelStatesModels,p=i.hoverScale,d=i.cursorStyle,h=i.emphasisDisabled),!i||t.hasItemOption){var y=i&&i.itemModel?i.itemModel:t.getItemModel(e),v=y.getModel("emphasis");o=v.getModel("itemStyle").getItemStyle(),s=y.getModel(["select","itemStyle"]).getItemStyle(),a=y.getModel(["blur","itemStyle"]).getItemStyle(),l=v.get("focus"),u=v.get("blurScope"),h=v.get("disabled"),c=rc(y),p=v.getShallow("scale"),d=y.getShallow("cursor")}var m=t.getItemVisual(e,"symbolRotate");f.attr("rotation",(m||0)*Math.PI/180||0);var x=Zy(t.getItemVisual(e,"symbolOffset"),n);x&&(f.x=x[0],f.y=x[1]),d&&f.attr("cursor",d);var _=t.getItemVisual(e,"style"),b=_.fill;if(f instanceof Ns){var w=f.style;f.useStyle(A({image:w.image,x:w.x,y:w.y,width:w.width,height:w.height},_))}else f.__isEmptyBrush?f.useStyle(A({},_)):f.useStyle(_),f.style.decal=null,f.setColor(b,r&&r.symbolInnerColor),f.style.strokeNoScale=!0;var S=t.getItemVisual(e,"liftZ"),M=this._z2;null!=S?null==M&&(this._z2=f.z2,f.z2+=S):null!=M&&(f.z2=M,this._z2=null);var I=r&&r.useNameLabel;ic(f,c,{labelFetcher:g,labelDataIndex:e,defaultText:function(e){return I?t.getName(e):pS(t,e)},inheritColor:b,defaultOpacity:_.opacity}),this._sizeX=n[0]/2,this._sizeY=n[1]/2;var T=f.ensureState("emphasis");T.style=o,f.ensureState("select").style=s,f.ensureState("blur").style=a;var C=null==p||!0===p?Math.max(1.1,3/this._sizeY):isFinite(p)&&p>0?+p:1;T.scaleX=this._sizeX*C,T.scaleY=this._sizeY*C,this.setSymbolScale(1),Zl(this,l,u,h)},e.prototype.setSymbolScale=function(t){this.scaleX=this.scaleY=t},e.prototype.fadeOut=function(t,e,n){var i=this.childAt(0),r=rl(this).dataIndex,o=n&&n.animation;if(this.silent=i.silent=!0,n&&n.fadeLabel){var a=i.getTextContent();a&&_h(a,{style:{opacity:0}},e,{dataIndex:r,removeOpt:o,cb:function(){i.removeTextContent()}})}else i.removeTextContent();_h(i,{style:{opacity:0},scaleX:0,scaleY:0},e,{dataIndex:r,cb:t,removeOpt:o})},e.getSymbolSize=function(t,e){return Uy(t.getItemVisual(e,"symbolSize"))},e}(Br);function gS(t,e){this.parent.drift(t,e)}function yS(t,e,n,i){return e&&!isNaN(e[0])&&!isNaN(e[1])&&!(i.isIgnore&&i.isIgnore(n))&&!(i.clipShape&&!i.clipShape.contain(e[0],e[1]))&&"none"!==t.getItemVisual(n,"symbol")}function vS(t){return null==t||q(t)||(t={isIgnore:t}),t||{}}function mS(t){var e=t.hostModel,n=e.getModel("emphasis");return{emphasisItemStyle:n.getModel("itemStyle").getItemStyle(),blurItemStyle:e.getModel(["blur","itemStyle"]).getItemStyle(),selectItemStyle:e.getModel(["select","itemStyle"]).getItemStyle(),focus:n.get("focus"),blurScope:n.get("blurScope"),emphasisDisabled:n.get("disabled"),hoverScale:n.get("scale"),labelStatesModels:rc(e),cursorStyle:e.get("cursor")}}var xS=function(){function t(t){this.group=new Br,this._SymbolCtor=t||fS}return t.prototype.updateData=function(t,e){this._progressiveEls=null,e=vS(e);var n=this.group,i=t.hostModel,r=this._data,o=this._SymbolCtor,a=e.disableAnimation,s=mS(t),l={disableAnimation:a},u=e.getSymbolPoint||function(e){return t.getItemLayout(e)};r||n.removeAll(),t.diff(r).add((function(i){var r=u(i);if(yS(t,r,i,e)){var a=new o(t,i,s,l);a.setPosition(r),t.setItemGraphicEl(i,a),n.add(a)}})).update((function(h,c){var p=r.getItemGraphicEl(c),d=u(h);if(yS(t,d,h,e)){var f=t.getItemVisual(h,"symbol")||"circle",g=p&&p.getSymbolType&&p.getSymbolType();if(!p||g&&g!==f)n.remove(p),(p=new o(t,h,s,l)).setPosition(d);else{p.updateData(t,h,s,l);var y={x:d[0],y:d[1]};a?p.attr(y):vh(p,y,i)}n.add(p),t.setItemGraphicEl(h,p)}else n.remove(p)})).remove((function(t){var e=r.getItemGraphicEl(t);e&&e.fadeOut((function(){n.remove(e)}),i)})).execute(),this._getSymbolPoint=u,this._data=t},t.prototype.updateLayout=function(){var t=this,e=this._data;e&&e.eachItemGraphicEl((function(e,n){var i=t._getSymbolPoint(n);e.setPosition(i),e.markRedraw()}))},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=mS(t),this._data=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e,n){function i(t){t.isGroup||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}this._progressiveEls=[],n=vS(n);for(var r=t.start;r0?n=i[0]:i[1]<0&&(n=i[1]);return n}(r,n),a=i.dim,s=r.dim,l=e.mapDimension(s),u=e.mapDimension(a),h="x"===s||"radius"===s?1:0,c=z(t.dimensions,(function(t){return e.mapDimension(t)})),p=!1,d=e.getCalculationInfo("stackResultDimension");return mx(e,c[0])&&(p=!0,c[0]=d),mx(e,c[1])&&(p=!0,c[1]=d),{dataDimsForPoint:c,valueStart:o,valueAxisDim:s,baseAxisDim:a,stacked:!!p,valueDim:l,baseDim:u,baseDataOffset:h,stackedOverDimension:e.getCalculationInfo("stackedOverDimension")}}function bS(t,e,n,i){var r=NaN;t.stacked&&(r=n.get(n.getCalculationInfo("stackedOverDimension"),i)),isNaN(r)&&(r=t.valueStart);var o=t.baseDataOffset,a=[];return a[o]=n.get(t.baseDim,i),a[1-o]=r,e.dataToPoint(a)}var wS=Math.min,SS=Math.max;function MS(t,e){return isNaN(t)||isNaN(e)}function IS(t,e,n,i,r,o,a,s,l){for(var u,h,c,p,d,f,g=n,y=0;y=r||g<0)break;if(MS(v,m)){if(l){g+=o;continue}break}if(g===n)t[o>0?"moveTo":"lineTo"](v,m),c=v,p=m;else{var x=v-u,_=m-h;if(x*x+_*_<.5){g+=o;continue}if(a>0){for(var b=g+o,w=e[2*b],S=e[2*b+1];w===v&&S===m&&y=i||MS(w,S))d=v,f=m;else{T=w-u,C=S-h;var k=v-u,L=w-v,P=m-h,O=S-m,R=void 0,N=void 0;if("x"===s){var E=T>0?1:-1;d=v-E*(R=Math.abs(k))*a,f=m,D=v+E*(N=Math.abs(L))*a,A=m}else if("y"===s){var z=C>0?1:-1;d=v,f=m-z*(R=Math.abs(P))*a,D=v,A=m+z*(N=Math.abs(O))*a}else R=Math.sqrt(k*k+P*P),d=v-T*a*(1-(I=(N=Math.sqrt(L*L+O*O))/(N+R))),f=m-C*a*(1-I),A=m+C*a*I,D=wS(D=v+T*a*I,SS(w,v)),A=wS(A,SS(S,m)),D=SS(D,wS(w,v)),f=m-(C=(A=SS(A,wS(S,m)))-m)*R/N,d=wS(d=v-(T=D-v)*R/N,SS(u,v)),f=wS(f,SS(h,m)),D=v+(T=v-(d=SS(d,wS(u,v))))*N/R,A=m+(C=m-(f=SS(f,wS(h,m))))*N/R}t.bezierCurveTo(c,p,d,f,v,m),c=D,p=A}else t.lineTo(v,m)}u=v,h=m,g+=o}return y}var TS=function(){this.smooth=0,this.smoothConstraint=!0},CS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="ec-polyline",n}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new TS},e.prototype.buildPath=function(t,e){var n=e.points,i=0,r=n.length/2;if(e.connectNulls){for(;r>0&&MS(n[2*r-2],n[2*r-1]);r--);for(;i=0){var y=a?(h-i)*g+i:(u-n)*g+n;return a?[t,y]:[y,t]}n=u,i=h;break;case o.C:u=r[l++],h=r[l++],c=r[l++],p=r[l++],d=r[l++],f=r[l++];var v=a?_n(n,u,c,d,t,s):_n(i,h,p,f,t,s);if(v>0)for(var m=0;m=0){y=a?mn(i,h,p,f,x):mn(n,u,c,d,x);return a?[t,y]:[y,t]}}n=d,i=f}}},e}(ks),DS=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(TS),AS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="ec-polygon",n}return n(e,t),e.prototype.getDefaultShape=function(){return new DS},e.prototype.buildPath=function(t,e){var n=e.points,i=e.stackedOnPoints,r=0,o=n.length/2,a=e.smoothMonotone;if(e.connectNulls){for(;o>0&&MS(n[2*o-2],n[2*o-1]);o--);for(;r=0;a--){var s=t.getDimensionInfo(i[a].dimension);if("x"===(r=s&&s.coordDim)||"y"===r){o=i[a];break}}if(o){var l=e.getAxis(r),u=z(o.stops,(function(t){return{coord:l.toGlobalCoord(l.dataToCoord(t.value)),color:t.color}})),h=u.length,c=o.outerColors.slice();h&&u[0].coord>u[h-1].coord&&(u.reverse(),c.reverse());var p=function(t,e){var n,i,r=[],o=t.length;function a(t,e,n){var i=t.coord;return{coord:n,color:ti((n-i)/(e.coord-i),[t.color,e.color])}}for(var s=0;se){i?r.push(a(i,l,e)):n&&r.push(a(n,l,0),a(n,l,e));break}n&&(r.push(a(n,l,0)),n=null),r.push(l),i=l}}return r}(u,"x"===r?n.getWidth():n.getHeight()),d=p.length;if(!d&&h)return u[0].coord<0?c[1]?c[1]:u[h-1].color:c[0]?c[0]:u[0].color;var f=p[0].coord-10,g=p[d-1].coord+10,y=g-f;if(y<.001)return"transparent";E(p,(function(t){t.offset=(t.coord-f)/y})),p.push({offset:d?p[d-1].offset:.5,color:c[1]||"transparent"}),p.unshift({offset:d?p[0].offset:.5,color:c[0]||"transparent"});var v=new oh(0,0,0,0,p,!0);return v[r]=f,v[r+"2"]=g,v}}}function FS(t,e,n){var i=t.get("showAllSymbol"),r="auto"===i;if(!i||r){var o=n.getAxesByScale("ordinal")[0];if(o&&(!r||!function(t,e){var n=t.getExtent(),i=Math.abs(n[1]-n[0])/t.scale.count();isNaN(i)&&(i=0);for(var r=e.count(),o=Math.max(1,Math.round(r/5)),a=0;ai)return!1;return!0}(o,e))){var a=e.mapDimension(o.dim),s={};return E(o.getViewLabels(),(function(t){var e=o.scale.getRawOrdinalNumber(t.tickValue);s[e]=1})),function(t){return!s.hasOwnProperty(e.get(a,t))}}}}function GS(t,e){return[t[2*e],t[2*e+1]]}function WS(t){if(t.get(["endLabel","show"]))return!0;for(var e=0;e0&&"bolder"===t.get(["emphasis","lineStyle","width"]))&&(p.getState("emphasis").style.lineWidth=+p.style.lineWidth+1);rl(p).seriesIndex=t.seriesIndex,Zl(p,A,L,P);var O=zS(t.get("smooth")),R=t.get("smoothMonotone");if(p.setShape({smooth:O,smoothMonotone:R,connectNulls:b}),d){var N=o.getCalculationInfo("stackedOnSeries"),E=0;d.useStyle(k(s.getAreaStyle(),{fill:T,opacity:.7,lineJoin:"bevel",decal:o.getVisual("style").decal})),N&&(E=zS(N.get("smooth"))),d.setShape({smooth:O,stackedOnSmooth:E,smoothMonotone:R,connectNulls:b}),$l(d,t,"areaStyle"),rl(d).seriesIndex=t.seriesIndex,Zl(d,A,L,P)}var z=this._changePolyState;o.eachItemGraphicEl((function(t){t&&(t.onHoverStateChange=z)})),this._polyline.onHoverStateChange=z,this._data=o,this._coordSys=i,this._stackedOnPoints=x,this._points=l,this._step=I,this._valueOrigin=v,t.get("triggerLineEvent")&&(this.packEventData(t,p),d&&this.packEventData(t,d))},e.prototype.packEventData=function(t,e){rl(e).eventData={componentType:"series",componentSubType:"line",componentIndex:t.componentIndex,seriesIndex:t.seriesIndex,seriesName:t.name,seriesType:"line"}},e.prototype.highlight=function(t,e,n,i){var r=t.getData(),o=zo(r,i);if(this._changePolyState("emphasis"),!(o instanceof Array)&&null!=o&&o>=0){var a=r.getLayout("points"),s=r.getItemGraphicEl(o);if(!s){var l=a[2*o],u=a[2*o+1];if(isNaN(l)||isNaN(u))return;if(this._clipShapeForSymbol&&!this._clipShapeForSymbol.contain(l,u))return;var h=t.get("zlevel")||0,c=t.get("z")||0;(s=new fS(r,o)).x=l,s.y=u,s.setZ(h,c);var p=s.getSymbolPath().getTextContent();p&&(p.zlevel=h,p.z=c,p.z2=this._polyline.z2+1),s.__temp=!0,r.setItemGraphicEl(o,s),s.stopSymbolAnimation(!0),this.group.add(s)}s.highlight()}else Og.prototype.highlight.call(this,t,e,n,i)},e.prototype.downplay=function(t,e,n,i){var r=t.getData(),o=zo(r,i);if(this._changePolyState("normal"),null!=o&&o>=0){var a=r.getItemGraphicEl(o);a&&(a.__temp?(r.setItemGraphicEl(o,null),this.group.remove(a)):a.downplay())}else Og.prototype.downplay.call(this,t,e,n,i)},e.prototype._changePolyState=function(t){var e=this._polygon;Dl(this._polyline,t),e&&Dl(e,t)},e.prototype._newPolyline=function(t){var e=this._polyline;return e&&this._lineGroup.remove(e),e=new CS({shape:{points:t},segmentIgnoreThreshold:2,z2:10}),this._lineGroup.add(e),this._polyline=e,e},e.prototype._newPolygon=function(t,e){var n=this._polygon;return n&&this._lineGroup.remove(n),n=new AS({shape:{points:t,stackedOnPoints:e},segmentIgnoreThreshold:2}),this._lineGroup.add(n),this._polygon=n,n},e.prototype._initSymbolLabelAnimation=function(t,e,n){var i,r,o=e.getBaseAxis(),a=o.inverse;"cartesian2d"===e.type?(i=o.isHorizontal(),r=!1):"polar"===e.type&&(i="angle"===o.dim,r=!0);var s=t.hostModel,l=s.get("animationDuration");X(l)&&(l=l(null));var u=s.get("animationDelay")||0,h=X(u)?u(null):u;t.eachItemGraphicEl((function(t,o){var s=t;if(s){var c=[t.x,t.y],p=void 0,d=void 0,f=void 0;if(n)if(r){var g=n,y=e.pointToCoord(c);i?(p=g.startAngle,d=g.endAngle,f=-y[1]/180*Math.PI):(p=g.r0,d=g.r,f=y[0])}else{var v=n;i?(p=v.x,d=v.x+v.width,f=t.x):(p=v.y+v.height,d=v.y,f=t.y)}var m=d===p?0:(f-p)/(d-p);a&&(m=1-m);var x=X(u)?u(o):l*m+h,_=s.getSymbolPath(),b=_.getTextContent();s.attr({scaleX:0,scaleY:0}),s.animateTo({scaleX:1,scaleY:1},{duration:200,setToFinal:!0,delay:x}),b&&b.animateFrom({style:{opacity:0}},{duration:300,delay:x}),_.disableLabelAnimation=!0}}))},e.prototype._initOrUpdateEndLabel=function(t,e,n){var i=t.getModel("endLabel");if(WS(t)){var r=t.getData(),o=this._polyline,a=r.getLayout("points");if(!a)return o.removeTextContent(),void(this._endLabel=null);var s=this._endLabel;s||((s=this._endLabel=new Xs({z2:200})).ignoreClip=!0,o.setTextContent(this._endLabel),o.disableLabelAnimation=!0);var l=function(t){for(var e,n,i=t.length/2;i>0&&(e=t[2*i-2],n=t[2*i-1],isNaN(e)||isNaN(n));i--);return i-1}(a);l>=0&&(ic(o,rc(t,"endLabel"),{inheritColor:n,labelFetcher:t,labelDataIndex:l,defaultText:function(t,e,n){return null!=n?dS(r,n):pS(r,t)},enableTextSetter:!0},function(t,e){var n=e.getBaseAxis(),i=n.isHorizontal(),r=n.inverse,o=i?r?"right":"left":"center",a=i?"middle":r?"top":"bottom";return{normal:{align:t.get("align")||o,verticalAlign:t.get("verticalAlign")||a}}}(i,e)),o.textConfig.position=null)}else this._endLabel&&(this._polyline.removeTextContent(),this._endLabel=null)},e.prototype._endLabelOnDuring=function(t,e,n,i,r,o,a){var s=this._endLabel,l=this._polyline;if(s){t<1&&null==i.originalX&&(i.originalX=s.x,i.originalY=s.y);var u=n.getLayout("points"),h=n.hostModel,c=h.get("connectNulls"),p=o.get("precision"),d=o.get("distance")||0,f=a.getBaseAxis(),g=f.isHorizontal(),y=f.inverse,v=e.shape,m=y?g?v.x:v.y+v.height:g?v.x+v.width:v.y,x=(g?d:0)*(y?-1:1),_=(g?0:-d)*(y?-1:1),b=g?"x":"y",w=function(t,e,n){for(var i,r,o=t.length/2,a="x"===n?0:1,s=0,l=-1,u=0;u=e||i>=e&&r<=e){l=u;break}s=u,i=r}else i=r;return{range:[s,l],t:(e-i)/(r-i)}}(u,m,b),S=w.range,M=S[1]-S[0],I=void 0;if(M>=1){if(M>1&&!c){var T=GS(u,S[0]);s.attr({x:T[0]+x,y:T[1]+_}),r&&(I=h.getRawValue(S[0]))}else{(T=l.getPointOn(m,b))&&s.attr({x:T[0]+x,y:T[1]+_});var C=h.getRawValue(S[0]),D=h.getRawValue(S[1]);r&&(I=Zo(n,p,C,D,w.t))}i.lastFrameIndex=S[0]}else{var A=1===t||i.lastFrameIndex>0?S[0]:0;T=GS(u,A);r&&(I=h.getRawValue(A)),s.attr({x:T[0]+x,y:T[1]+_})}if(r){var k=pc(s);"function"==typeof k.setLabelText&&k.setLabelText(I)}}},e.prototype._doUpdateAnimation=function(t,e,n,i,r,o,a){var s=this._polyline,l=this._polygon,u=t.hostModel,h=function(t,e,n,i,r,o,a,s){for(var l=function(t,e){var n=[];return e.diff(t).add((function(t){n.push({cmd:"+",idx:t})})).update((function(t,e){n.push({cmd:"=",idx:e,idx1:t})})).remove((function(t){n.push({cmd:"-",idx:t})})).execute(),n}(t,e),u=[],h=[],c=[],p=[],d=[],f=[],g=[],y=_S(r,e,a),v=t.getLayout("points")||[],m=e.getLayout("points")||[],x=0;x3e3||l&&ES(p,f)>3e3)return s.stopAnimation(),s.setShape({points:d}),void(l&&(l.stopAnimation(),l.setShape({points:d,stackedOnPoints:f})));s.shape.__points=h.current,s.shape.points=c;var g={shape:{points:d}};h.current!==c&&(g.shape.__points=h.next),s.stopAnimation(),vh(s,g,u),l&&(l.setShape({points:c,stackedOnPoints:p}),l.stopAnimation(),vh(l,{shape:{stackedOnPoints:f}},u),s.shape.points!==l.shape.points&&(l.shape.points=s.shape.points));for(var y=[],v=h.status,m=0;me&&(e=t[n]);return isFinite(e)?e:NaN},min:function(t){for(var e=1/0,n=0;n10&&"cartesian2d"===o.type&&r){var s=o.getBaseAxis(),l=o.getOtherAxis(s),u=s.getExtent(),h=n.getDevicePixelRatio(),c=Math.abs(u[1]-u[0])*(h||1),p=Math.round(a/c);if(isFinite(p)&&p>1){"lttb"===r?t.setData(i.lttbDownSample(i.mapDimension(l.dim),1/p)):"minmax"===r&&t.setData(i.minmaxDownSample(i.mapDimension(l.dim),1/p));var d=void 0;U(r)?d=US[r]:X(r)&&(d=r),d&&t.setData(i.downSample(i.mapDimension(l.dim),1/p,d,ZS))}}}}}var qS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(t,e){return _x(null,this,{useEncodeDefaulter:!0})},e.prototype.getMarkerPosition=function(t,e,n){var i=this.coordinateSystem;if(i&&i.clampData){var r=i.clampData(t),o=i.dataToPoint(r);if(n)E(i.getAxes(),(function(t,n){if("category"===t.type&&null!=e){var i=t.getTicksCoords(),a=t.getTickModel().get("alignWithLabel"),s=r[n],l="x1"===e[n]||"y1"===e[n];if(l&&!a&&(s+=1),i.length<2)return;if(2===i.length)return void(o[n]=t.toGlobalCoord(t.getExtent()[l?1:0]));for(var u=void 0,h=void 0,c=1,p=0;ps){h=(d+u)/2;break}1===p&&(c=f-i[0].tickValue)}null==h&&(u?u&&(h=i[i.length-1].coord):h=i[0].coord),o[n]=t.toGlobalCoord(h)}}));else{var a=this.getData(),s=a.getLayout("offset"),l=a.getLayout("size"),u=i.getBaseAxis().isHorizontal()?0:1;o[u]+=s+l/2}return o}return[NaN,NaN]},e.type="series.__base_bar__",e.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,barMinHeight:0,barMinAngle:0,large:!1,largeThreshold:400,progressive:3e3,progressiveChunkMode:"mod"},e}(bg);bg.registerClass(qS);var KS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(){return _x(null,this,{useEncodeDefaulter:!0,createInvertedIndices:!!this.get("realtimeSort",!0)||null})},e.prototype.getProgressive=function(){return!!this.get("large")&&this.get("progressive")},e.prototype.getProgressiveThreshold=function(){var t=this.get("progressiveThreshold"),e=this.get("largeThreshold");return e>t&&(t=e),t},e.prototype.brushSelector=function(t,e,n){return n.rect(e.getItemLayout(t))},e.type="series.bar",e.dependencies=["grid","polar"],e.defaultOption=kc(qS.defaultOption,{clip:!0,roundCap:!1,showBackground:!1,backgroundStyle:{color:"rgba(180, 180, 180, 0.2)",borderColor:null,borderWidth:0,borderType:"solid",borderRadius:0,shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,opacity:1},select:{itemStyle:{borderColor:"#212121"}},realtimeSort:!1}),e}(qS),$S=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0},JS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="sausage",n}return n(e,t),e.prototype.getDefaultShape=function(){return new $S},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=Math.max(e.r0||0,0),o=Math.max(e.r,0),a=.5*(o-r),s=r+a,l=e.startAngle,u=e.endAngle,h=e.clockwise,c=2*Math.PI,p=h?u-lo)return!0;o=u}return!1},e.prototype._isOrderDifferentInView=function(t,e){for(var n=e.scale,i=n.getExtent(),r=Math.max(0,i[0]),o=Math.min(i[1],n.getOrdinalMeta().categories.length-1);r<=o;++r)if(t.ordinalNumbers[r]!==n.getRawOrdinalNumber(r))return!0},e.prototype._updateSortWithinSameData=function(t,e,n,i){if(this._isOrderChangedWithinSameData(t,e,n)){var r=this._dataSort(t,n,e);this._isOrderDifferentInView(r,n)&&(this._removeOnRenderedListener(i),i.dispatchAction({type:"changeAxisOrder",componentType:n.dim+"Axis",axisId:n.index,sortInfo:r}))}},e.prototype._dispatchInitSort=function(t,e,n){var i=e.baseAxis,r=this._dataSort(t,i,(function(n){return t.get(t.mapDimension(e.otherAxis.dim),n)}));n.dispatchAction({type:"changeAxisOrder",componentType:i.dim+"Axis",isInitSort:!0,axisId:i.index,sortInfo:r})},e.prototype.remove=function(t,e){this._clear(this._model),this._removeOnRenderedListener(e)},e.prototype.dispose=function(t,e){this._removeOnRenderedListener(e)},e.prototype._removeOnRenderedListener=function(t){this._onRendered&&(t.getZr().off("rendered",this._onRendered),this._onRendered=null)},e.prototype._clear=function(t){var e=this.group,n=this._data;t&&t.isAnimationEnabled()&&n&&!this._isLargeDraw?(this._removeBackground(),this._backgroundEls=[],n.eachItemGraphicEl((function(e){wh(e,t,rl(e).dataIndex)}))):e.removeAll(),this._data=null,this._isFirstFrame=!0},e.prototype._removeBackground=function(){this.group.remove(this._backgroundGroup),this._backgroundGroup=null},e.type="bar",e}(Og),oM={cartesian2d:function(t,e){var n=e.width<0?-1:1,i=e.height<0?-1:1;n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height);var r=t.x+t.width,o=t.y+t.height,a=nM(e.x,t.x),s=iM(e.x+e.width,r),l=nM(e.y,t.y),u=iM(e.y+e.height,o),h=sr?s:a,e.y=c&&l>o?u:l,e.width=h?0:s-a,e.height=c?0:u-l,n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height),h||c},polar:function(t,e){var n=e.r0<=e.r?1:-1;if(n<0){var i=e.r;e.r=e.r0,e.r0=i}var r=iM(e.r,t.r),o=nM(e.r0,t.r0);e.r=r,e.r0=o;var a=r-o<0;if(n<0){i=e.r;e.r=e.r0,e.r0=i}return a}},aM={cartesian2d:function(t,e,n,i,r,o,a,s,l){var u=new Ws({shape:A({},i),z2:1});(u.__dataIndex=n,u.name="item",o)&&(u.shape[r?"height":"width"]=0);return u},polar:function(t,e,n,i,r,o,a,s,l){var u=!r&&l?JS:Fu,h=new u({shape:i,z2:1});h.name="item";var c,p,d=dM(r);if(h.calculateTextPosition=(c=d,p=({isRoundCap:u===JS}||{}).isRoundCap,function(t,e,n){var i=e.position;if(!i||i instanceof Array)return Dr(t,e,n);var r=c(i),o=null!=e.distance?e.distance:5,a=this.shape,s=a.cx,l=a.cy,u=a.r,h=a.r0,d=(u+h)/2,f=a.startAngle,g=a.endAngle,y=(f+g)/2,v=p?Math.abs(u-h)/2:0,m=Math.cos,x=Math.sin,_=s+u*m(f),b=l+u*x(f),w="left",S="top";switch(r){case"startArc":_=s+(h-o)*m(y),b=l+(h-o)*x(y),w="center",S="top";break;case"insideStartArc":_=s+(h+o)*m(y),b=l+(h+o)*x(y),w="center",S="bottom";break;case"startAngle":_=s+d*m(f)+QS(f,o+v,!1),b=l+d*x(f)+tM(f,o+v,!1),w="right",S="middle";break;case"insideStartAngle":_=s+d*m(f)+QS(f,-o+v,!1),b=l+d*x(f)+tM(f,-o+v,!1),w="left",S="middle";break;case"middle":_=s+d*m(y),b=l+d*x(y),w="center",S="middle";break;case"endArc":_=s+(u+o)*m(y),b=l+(u+o)*x(y),w="center",S="bottom";break;case"insideEndArc":_=s+(u-o)*m(y),b=l+(u-o)*x(y),w="center",S="top";break;case"endAngle":_=s+d*m(g)+QS(g,o+v,!0),b=l+d*x(g)+tM(g,o+v,!0),w="left",S="middle";break;case"insideEndAngle":_=s+d*m(g)+QS(g,-o+v,!0),b=l+d*x(g)+tM(g,-o+v,!0),w="right",S="middle";break;default:return Dr(t,e,n)}return(t=t||{}).x=_,t.y=b,t.align=w,t.verticalAlign=S,t}),o){var f=r?"r":"endAngle",g={};h.shape[f]=r?i.r0:i.startAngle,g[f]=i[f],(s?vh:mh)(h,{shape:g},o)}return h}};function sM(t,e,n,i,r,o,a,s){var l,u;o?(u={x:i.x,width:i.width},l={y:i.y,height:i.height}):(u={y:i.y,height:i.height},l={x:i.x,width:i.width}),s||(a?vh:mh)(n,{shape:l},e,r,null),(a?vh:mh)(n,{shape:u},e?t.baseAxis.model:null,r)}function lM(t,e){for(var n=0;n0?1:-1,a=i.height>0?1:-1;return{x:i.x+o*r/2,y:i.y+a*r/2,width:i.width-o*r,height:i.height-a*r}},polar:function(t,e,n){var i=t.getItemLayout(e);return{cx:i.cx,cy:i.cy,r0:i.r0,r:i.r,startAngle:i.startAngle,endAngle:i.endAngle,clockwise:i.clockwise}}};function dM(t){return function(t){var e=t?"Arc":"Angle";return function(t){switch(t){case"start":case"insideStart":case"end":case"insideEnd":return t+e;default:return t}}}(t)}function fM(t,e,n,i,r,o,a,s){var l=e.getItemVisual(n,"style");if(s){if(!o.get("roundCap")){var u=t.shape;A(u,eM(i.getModel("itemStyle"),u,!0)),t.setShape(u)}}else{var h=i.get(["itemStyle","borderRadius"])||0;t.setShape("r",h)}t.useStyle(l);var c=i.getShallow("cursor");c&&t.attr("cursor",c);var p=s?a?r.r>=r.r0?"endArc":"startArc":r.endAngle>=r.startAngle?"endAngle":"startAngle":a?r.height>=0?"bottom":"top":r.width>=0?"right":"left",d=rc(i);ic(t,d,{labelFetcher:o,labelDataIndex:n,defaultText:pS(o.getData(),n),inheritColor:l.fill,defaultOpacity:l.opacity,defaultOutsidePosition:p});var f=t.getTextContent();if(s&&f){var g=i.get(["label","position"]);t.textConfig.inside="middle"===g||null,function(t,e,n,i){if(j(i))t.setTextConfig({rotation:i});else if(Y(e))t.setTextConfig({rotation:0});else{var r,o=t.shape,a=o.clockwise?o.startAngle:o.endAngle,s=o.clockwise?o.endAngle:o.startAngle,l=(a+s)/2,u=n(e);switch(u){case"startArc":case"insideStartArc":case"middle":case"insideEndArc":case"endArc":r=l;break;case"startAngle":case"insideStartAngle":r=a;break;case"endAngle":case"insideEndAngle":r=s;break;default:return void t.setTextConfig({rotation:0})}var h=1.5*Math.PI-r;"middle"===u&&h>Math.PI/2&&h<1.5*Math.PI&&(h-=Math.PI),t.setTextConfig({rotation:h})}}(t,"outside"===g?p:g,dM(a),i.get(["label","rotate"]))}dc(f,d,o.getRawValue(n),(function(t){return dS(e,t)}));var y=i.getModel(["emphasis"]);Zl(t,y.get("focus"),y.get("blurScope"),y.get("disabled")),$l(t,i),function(t){return null!=t.startAngle&&null!=t.endAngle&&t.startAngle===t.endAngle}(r)&&(t.style.fill="none",t.style.stroke="none",E(t.states,(function(t){t.style&&(t.style.fill=t.style.stroke="none")})))}var gM=function(){},yM=function(t){function e(e){var n=t.call(this,e)||this;return n.type="largeBar",n}return n(e,t),e.prototype.getDefaultShape=function(){return new gM},e.prototype.buildPath=function(t,e){for(var n=e.points,i=this.baseDimIdx,r=1-this.baseDimIdx,o=[],a=[],s=this.barWidth,l=0;l=s[0]&&e<=s[0]+l[0]&&n>=s[1]&&n<=s[1]+l[1])return a[h]}return-1}(this,t.offsetX,t.offsetY);rl(this).dataIndex=e>=0?e:null}),30,!1);function xM(t,e,n){if(OS(n,"cartesian2d")){var i=e,r=n.getArea();return{x:t?i.x:r.x,y:t?r.y:i.y,width:t?i.width:r.width,height:t?r.height:i.height}}var o=e;return{cx:(r=n.getArea()).cx,cy:r.cy,r0:t?r.r0:o.r0,r:t?r.r:o.r,startAngle:t?o.startAngle:0,endAngle:t?o.endAngle:2*Math.PI}}var _M=2*Math.PI,bM=Math.PI/180;function wM(t,e){return kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}function SM(t,e){var n=wM(t,e),i=t.get("center"),r=t.get("radius");Y(r)||(r=[0,r]);var o,a,s=$r(n.width,e.getWidth()),l=$r(n.height,e.getHeight()),u=Math.min(s,l),h=$r(r[0],u/2),c=$r(r[1],u/2),p=t.coordinateSystem;if(p){var d=p.dataToPoint(i);o=d[0]||0,a=d[1]||0}else Y(i)||(i=[i,i]),o=$r(i[0],s)+n.x,a=$r(i[1],l)+n.y;return{cx:o,cy:a,r0:h,r:c}}function MM(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.getData(),i=e.mapDimension("value"),r=wM(t,n),o=SM(t,n),a=o.cx,s=o.cy,l=o.r,u=o.r0,h=-t.get("startAngle")*bM,c=t.get("endAngle"),p=t.get("padAngle")*bM;c="auto"===c?h-_M:-c*bM;var d=t.get("minAngle")*bM+p,f=0;e.each(i,(function(t){!isNaN(t)&&f++}));var g=e.getSum(i),y=Math.PI/(g||f)*2,v=t.get("clockwise"),m=t.get("roseType"),x=t.get("stillShowZeroSum"),_=e.getDataExtent(i);_[0]=0;var b=v?1:-1,w=[h,c],S=b*p/2;us(w,!v),h=w[0],c=w[1];var M=IM(t);M.startAngle=h,M.endAngle=c,M.clockwise=v;var I=Math.abs(c-h),T=I,C=0,D=h;if(e.setLayout({viewRect:r,r:l}),e.each(i,(function(t,n){var i;if(isNaN(t))e.setItemLayout(n,{angle:NaN,startAngle:NaN,endAngle:NaN,clockwise:v,cx:a,cy:s,r0:u,r:m?NaN:l});else{(i="area"!==m?0===g&&x?y:t*y:I/f)i?h=o=D+b*i/2:(o=D+S,h=r-S),e.setItemLayout(n,{angle:i,startAngle:o,endAngle:h,clockwise:v,cx:a,cy:s,r0:u,r:m?Kr(t,_,[u,l]):l}),D=r}})),T<_M&&f)if(T<=.001){var A=I/f;e.each(i,(function(t,n){if(!isNaN(t)){var i=e.getItemLayout(n);i.angle=A;var r=0,o=0;An?a:o,h=Math.abs(l.label.y-n);if(h>=u.maxY){var c=l.label.x-e-l.len2*r,p=i+l.len,f=Math.abs(c)t.unconstrainedWidth?null:d:null;i.setStyle("width",f)}var g=i.getBoundingRect();o.width=g.width;var y=(i.style.margin||0)+2.1;o.height=g.height+y,o.y-=(o.height-c)/2}}}function kM(t){return"center"===t.position}function LM(t){var e,n,i=t.getData(),r=[],o=!1,a=(t.get("minShowLabelAngle")||0)*CM,s=i.getLayout("viewRect"),l=i.getLayout("r"),u=s.width,h=s.x,c=s.y,p=s.height;function d(t){t.ignore=!0}i.each((function(t){var s=i.getItemGraphicEl(t),c=s.shape,p=s.getTextContent(),f=s.getTextGuideLine(),g=i.getItemModel(t),y=g.getModel("label"),v=y.get("position")||g.get(["emphasis","label","position"]),m=y.get("distanceToLabelLine"),x=y.get("alignTo"),_=$r(y.get("edgeDistance"),u),b=y.get("bleedMargin"),w=g.getModel("labelLine"),S=w.get("length");S=$r(S,u);var M=w.get("length2");if(M=$r(M,u),Math.abs(c.endAngle-c.startAngle)0?"right":"left":k>0?"left":"right"}var B=Math.PI,F=0,G=y.get("rotate");if(j(G))F=G*(B/180);else if("center"===v)F=0;else if("radial"===G||!0===G){F=k<0?-A+B:-A}else if("tangential"===G&&"outside"!==v&&"outer"!==v){var W=Math.atan2(k,L);W<0&&(W=2*B+W),L>0&&(W=B+W),F=W-B}if(o=!!F,p.x=I,p.y=T,p.rotation=F,p.setStyle({verticalAlign:"middle"}),P){p.setStyle({align:D});var H=p.states.select;H&&(H.x+=p.x,H.y+=p.y)}else{var Y=p.getBoundingRect().clone();Y.applyTransform(p.getComputedTransform());var X=(p.style.margin||0)+2.1;Y.y-=X/2,Y.height+=X,r.push({label:p,labelLine:f,position:v,len:S,len2:M,minTurnAngle:w.get("minTurnAngle"),maxSurfaceAngle:w.get("maxSurfaceAngle"),surfaceNormal:new De(k,L),linePoints:C,textAlign:D,labelDistance:m,labelAlignTo:x,edgeDistance:_,bleedMargin:b,rect:Y,unconstrainedWidth:Y.width,labelStyleWidth:p.style.width})}s.setTextConfig({inside:P})}})),!o&&t.get("avoidLabelOverlap")&&function(t,e,n,i,r,o,a,s){for(var l=[],u=[],h=Number.MAX_VALUE,c=-Number.MAX_VALUE,p=0;p0){for(var l=o.getItemLayout(0),u=1;isNaN(l&&l.startAngle)&&u=n.r0}},e.type="pie",e}(Og);function RM(t,e,n){e=Y(e)&&{coordDimensions:e}||A({encodeDefine:t.getEncode()},e);var i=t.getSource(),r=px(i,e).dimensions,o=new cx(r,t);return o.initData(i,n),o}var NM=function(){function t(t,e){this._getDataWithEncodedVisual=t,this._getRawData=e}return t.prototype.getAllNames=function(){var t=this._getRawData();return t.mapArray(t.getName)},t.prototype.containName=function(t){return this._getRawData().indexOfName(t)>=0},t.prototype.indexOfName=function(t){return this._getDataWithEncodedVisual().indexOfName(t)},t.prototype.getItemVisual=function(t,e){return this._getDataWithEncodedVisual().getItemVisual(t,e)},t}(),EM=Vo(),zM=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new NM(W(this.getData,this),W(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.mergeOption=function(){t.prototype.mergeOption.apply(this,arguments)},e.prototype.getInitialData=function(){return RM(this,{coordDimensions:["value"],encodeDefaulter:H(ed,this)})},e.prototype.getDataParams=function(e){var n=this.getData(),i=EM(n),r=i.seats;if(!r){var o=[];n.each(n.mapDimension("value"),(function(t){o.push(t)})),r=i.seats=io(o,n.hostModel.get("percentPrecision"))}var a=t.prototype.getDataParams.call(this,e);return a.percent=r[e]||0,a.$vars.push("percent"),a},e.prototype._defaultLabelLine=function(t){Co(t,"labelLine",["show"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.type="series.pie",e.defaultOption={z:2,legendHoverLink:!0,colorBy:"data",center:["50%","50%"],radius:[0,"75%"],clockwise:!0,startAngle:90,endAngle:"auto",padAngle:0,minAngle:0,minShowLabelAngle:0,selectedOffset:10,percentPrecision:2,stillShowZeroSum:!0,left:0,top:0,right:0,bottom:0,width:null,height:null,label:{rotate:0,show:!0,overflow:"truncate",position:"outer",alignTo:"none",edgeDistance:"25%",bleedMargin:10,distanceToLabelLine:5},labelLine:{show:!0,length:15,length2:15,smooth:!1,minTurnAngle:90,maxSurfaceAngle:90,lineStyle:{width:1,type:"solid"}},itemStyle:{borderWidth:1,borderJoin:"round"},showEmptyCircle:!0,emptyCircleStyle:{color:"lightgray",opacity:1},labelLayout:{hideOverlap:!0},emphasis:{scale:!0,scaleSize:5},avoidLabelOverlap:!0,animationType:"expansion",animationDuration:1e3,animationTypeUpdate:"transition",animationEasingUpdate:"cubicInOut",animationDurationUpdate:500,animationEasing:"cubicInOut"},e}(bg);var VM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){return _x(null,this,{useEncodeDefaulter:!0})},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?5e3:this.get("progressive"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?1e4:this.get("progressiveThreshold"):t},e.prototype.brushSelector=function(t,e,n){return n.point(e.getItemLayout(t))},e.prototype.getZLevelKey=function(){return this.getData().count()>this.getProgressiveThreshold()?this.id:""},e.type="series.scatter",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={coordinateSystem:"cartesian2d",z:2,legendHoverLink:!0,symbolSize:10,large:!1,largeThreshold:2e3,itemStyle:{opacity:.8},emphasis:{scale:!0},clip:!0,select:{itemStyle:{borderColor:"#212121"}},universalTransition:{divideShape:"clone"}},e}(bg),BM=function(){},FM=function(t){function e(e){var n=t.call(this,e)||this;return n._off=0,n.hoverDataIdx=-1,n}return n(e,t),e.prototype.getDefaultShape=function(){return new BM},e.prototype.reset=function(){this.notClear=!1,this._off=0},e.prototype.buildPath=function(t,e){var n,i=e.points,r=e.size,o=this.symbolProxy,a=o.shape,s=t.getContext?t.getContext():t,l=s&&r[0]<4,u=this.softClipShape;if(l)this._ctx=s;else{for(this._ctx=null,n=this._off;n=0;s--){var l=2*s,u=i[l]-o/2,h=i[l+1]-a/2;if(t>=u&&e>=h&&t<=u+o&&e<=h+a)return s}return-1},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect();return t=n[0],e=n[1],i.contain(t,e)?(this.hoverDataIdx=this.findDataIndex(t,e))>=0:(this.hoverDataIdx=-1,!1)},e.prototype.getBoundingRect=function(){var t=this._rect;if(!t){for(var e=this.shape,n=e.points,i=e.size,r=i[0],o=i[1],a=1/0,s=1/0,l=-1/0,u=-1/0,h=0;h=0&&(l.dataIndex=n+(t.startIndex||0))}))},t.prototype.remove=function(){this._clear()},t.prototype._clear=function(){this._newAdded=[],this.group.removeAll()},t}(),WM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).updateData(i,{clipShape:this._getClipShape(t)}),this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).incrementalPrepareUpdate(i),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._symbolDraw.incrementalUpdate(t,e.getData(),{clipShape:this._getClipShape(e)}),this._finished=t.end===e.getData().count()},e.prototype.updateTransform=function(t,e,n){var i=t.getData();if(this.group.dirty(),!this._finished||i.count()>1e4)return{update:!0};var r=XS("").reset(t,e,n);r.progress&&r.progress({start:0,end:i.count(),count:i.count()},i),this._symbolDraw.updateLayout(i)},e.prototype.eachRendered=function(t){this._symbolDraw&&this._symbolDraw.eachRendered(t)},e.prototype._getClipShape=function(t){if(t.get("clip",!0)){var e=t.coordinateSystem;return e&&e.getArea&&e.getArea(.1)}},e.prototype._updateSymbolDraw=function(t,e){var n=this._symbolDraw,i=e.pipelineContext.large;return n&&i===this._isLargeDraw||(n&&n.remove(),n=this._symbolDraw=i?new GM:new xS,this._isLargeDraw=i,this.group.removeAll()),this.group.add(n.group),n},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(!0),this._symbolDraw=null},e.prototype.dispose=function(){},e.type="scatter",e}(Og),HM=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.type="grid",e.dependencies=["xAxis","yAxis"],e.layoutMode="box",e.defaultOption={show:!1,z:0,left:"10%",top:60,right:"10%",bottom:70,containLabel:!1,backgroundColor:"rgba(0,0,0,0)",borderWidth:1,borderColor:"#ccc"},e}(zp),YM=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents("grid",Wo).models[0]},e.type="cartesian2dAxis",e}(zp);R(YM,D_);var XM={show:!0,z:0,inverse:!1,name:"",nameLocation:"end",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:"...",placeholder:"."},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:!0,onZeroAxisIndex:null,lineStyle:{color:"#6E7079",width:1,type:"solid"},symbol:["none","none"],symbolSize:[10,15]},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12},splitLine:{show:!0,showMinLine:!0,showMaxLine:!0,lineStyle:{color:["#E0E6F1"],width:1,type:"solid"}},splitArea:{show:!1,areaStyle:{color:["rgba(250,250,250,0.2)","rgba(210,219,238,0.2)"]}}},UM=C({boundaryGap:!0,deduplication:null,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:"auto"},axisLabel:{interval:"auto"}},XM),ZM=C({boundaryGap:[0,0],axisLine:{show:"auto"},axisTick:{show:"auto"},splitNumber:5,minorTick:{show:!1,splitNumber:5,length:3,lineStyle:{}},minorSplitLine:{show:!1,lineStyle:{color:"#F4F7FD",width:1}}},XM),jM={category:UM,value:ZM,time:C({splitNumber:6,axisLabel:{showMinLabel:!1,showMaxLabel:!1,rich:{primary:{fontWeight:"bold"}}},splitLine:{show:!1}},ZM),log:k({logBase:10},ZM)},qM={value:1,category:1,time:1,log:1};function KM(t,e,i,r){E(qM,(function(o,a){var s=C(C({},jM[a],!0),r,!0),l=function(t){function i(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e+"Axis."+a,n}return n(i,t),i.prototype.mergeDefaultAndTheme=function(t,e){var n=Pp(this),i=n?Rp(t):{};C(t,e.getTheme().get(a+"Axis")),C(t,this.getDefaultOption()),t.type=$M(t),n&&Op(t,i,n)},i.prototype.optionUpdated=function(){"category"===this.option.type&&(this.__ordinalMeta=Mx.createByAxisModel(this))},i.prototype.getCategories=function(t){var e=this.option;if("category"===e.type)return t?e.data:this.__ordinalMeta.categories},i.prototype.getOrdinalMeta=function(){return this.__ordinalMeta},i.type=e+"Axis."+a,i.defaultOption=s,i}(i);t.registerComponentModel(l)})),t.registerSubTypeDefaulter(e+"Axis",$M)}function $M(t){return t.type||(t.data?"category":"value")}var JM=function(){function t(t){this.type="cartesian",this._dimList=[],this._axes={},this.name=t||""}return t.prototype.getAxis=function(t){return this._axes[t]},t.prototype.getAxes=function(){return z(this._dimList,(function(t){return this._axes[t]}),this)},t.prototype.getAxesByScale=function(t){return t=t.toLowerCase(),B(this.getAxes(),(function(e){return e.scale.type===t}))},t.prototype.addAxis=function(t){var e=t.dim;this._axes[e]=t,this._dimList.push(e)},t}(),QM=["x","y"];function tI(t){return"interval"===t.type||"time"===t.type}var eI=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="cartesian2d",e.dimensions=QM,e}return n(e,t),e.prototype.calcAffineTransform=function(){this._transform=this._invTransform=null;var t=this.getAxis("x").scale,e=this.getAxis("y").scale;if(tI(t)&&tI(e)){var n=t.getExtent(),i=e.getExtent(),r=this.dataToPoint([n[0],i[0]]),o=this.dataToPoint([n[1],i[1]]),a=n[1]-n[0],s=i[1]-i[0];if(a&&s){var l=(o[0]-r[0])/a,u=(o[1]-r[1])/s,h=r[0]-n[0]*l,c=r[1]-i[0]*u,p=this._transform=[l,0,0,u,h,c];this._invTransform=Ie([],p)}}},e.prototype.getBaseAxis=function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAxis("x")},e.prototype.containPoint=function(t){var e=this.getAxis("x"),n=this.getAxis("y");return e.contain(e.toLocalCoord(t[0]))&&n.contain(n.toLocalCoord(t[1]))},e.prototype.containData=function(t){return this.getAxis("x").containData(t[0])&&this.getAxis("y").containData(t[1])},e.prototype.containZone=function(t,e){var n=this.dataToPoint(t),i=this.dataToPoint(e),r=this.getArea(),o=new ze(n[0],n[1],i[0]-n[0],i[1]-n[1]);return r.intersect(o)},e.prototype.dataToPoint=function(t,e,n){n=n||[];var i=t[0],r=t[1];if(this._transform&&null!=i&&isFinite(i)&&null!=r&&isFinite(r))return Wt(n,t,this._transform);var o=this.getAxis("x"),a=this.getAxis("y");return n[0]=o.toGlobalCoord(o.dataToCoord(i,e)),n[1]=a.toGlobalCoord(a.dataToCoord(r,e)),n},e.prototype.clampData=function(t,e){var n=this.getAxis("x").scale,i=this.getAxis("y").scale,r=n.getExtent(),o=i.getExtent(),a=n.parse(t[0]),s=i.parse(t[1]);return(e=e||[])[0]=Math.min(Math.max(Math.min(r[0],r[1]),a),Math.max(r[0],r[1])),e[1]=Math.min(Math.max(Math.min(o[0],o[1]),s),Math.max(o[0],o[1])),e},e.prototype.pointToData=function(t,e){var n=[];if(this._invTransform)return Wt(n,t,this._invTransform);var i=this.getAxis("x"),r=this.getAxis("y");return n[0]=i.coordToData(i.toLocalCoord(t[0]),e),n[1]=r.coordToData(r.toLocalCoord(t[1]),e),n},e.prototype.getOtherAxis=function(t){return this.getAxis("x"===t.dim?"y":"x")},e.prototype.getArea=function(t){t=t||0;var e=this.getAxis("x").getGlobalExtent(),n=this.getAxis("y").getGlobalExtent(),i=Math.min(e[0],e[1])-t,r=Math.min(n[0],n[1])-t,o=Math.max(e[0],e[1])-i+t,a=Math.max(n[0],n[1])-r+t;return new ze(i,r,o,a)},e}(JM),nI=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.index=0,a.type=r||"value",a.position=o||"bottom",a}return n(e,t),e.prototype.isHorizontal=function(){var t=this.position;return"top"===t||"bottom"===t},e.prototype.getGlobalExtent=function(t){var e=this.getExtent();return e[0]=this.toGlobalCoord(e[0]),e[1]=this.toGlobalCoord(e[1]),t&&e[0]>e[1]&&e.reverse(),e},e.prototype.pointToData=function(t,e){return this.coordToData(this.toLocalCoord(t["x"===this.dim?0:1]),e)},e.prototype.setCategorySortInfo=function(t){if("category"!==this.type)return!1;this.model.option.categorySortInfo=t,this.scale.setSortInfo(t)},e}(ab);function iI(t,e,n){n=n||{};var i=t.coordinateSystem,r=e.axis,o={},a=r.getAxesOnZeroOf()[0],s=r.position,l=a?"onZero":s,u=r.dim,h=i.getRect(),c=[h.x,h.x+h.width,h.y,h.y+h.height],p={left:0,right:1,top:0,bottom:1,onZero:2},d=e.get("offset")||0,f="x"===u?[c[2]-d,c[3]+d]:[c[0]-d,c[1]+d];if(a){var g=a.toGlobalCoord(a.dataToCoord(0));f[p.onZero]=Math.max(Math.min(g,f[1]),f[0])}o.position=["y"===u?f[p[l]]:c[0],"x"===u?f[p[l]]:c[3]],o.rotation=Math.PI/2*("x"===u?0:1);o.labelDirection=o.tickDirection=o.nameDirection={top:-1,bottom:1,left:-1,right:1}[s],o.labelOffset=a?f[p[s]]-f[p.onZero]:0,e.get(["axisTick","inside"])&&(o.tickDirection=-o.tickDirection),it(n.labelInside,e.get(["axisLabel","inside"]))&&(o.labelDirection=-o.labelDirection);var y=e.get(["axisLabel","rotate"]);return o.labelRotate="top"===l?-y:y,o.z2=1,o}function rI(t){return"cartesian2d"===t.get("coordinateSystem")}function oI(t){var e={xAxisModel:null,yAxisModel:null};return E(e,(function(n,i){var r=i.replace(/Model$/,""),o=t.getReferringComponents(r,Wo).models[0];e[i]=o})),e}var aI=Math.log;function sI(t,e,n){var i=Ex.prototype,r=i.getTicks.call(n),o=i.getTicks.call(n,!0),a=r.length-1,s=i.getInterval.call(n),l=x_(t,e),u=l.extent,h=l.fixMin,c=l.fixMax;if("log"===t.type){var p=aI(t.base);u=[aI(u[0])/p,aI(u[1])/p]}t.setExtent(u[0],u[1]),t.calcNiceExtent({splitNumber:a,fixMin:h,fixMax:c});var d=i.getExtent.call(t);h&&(u[0]=d[0]),c&&(u[1]=d[1]);var f=i.getInterval.call(t),g=u[0],y=u[1];if(h&&c)f=(y-g)/a;else if(h)for(y=u[0]+f*a;yu[0]&&isFinite(g)&&isFinite(u[0]);)f=Dx(f),g=u[1]-f*a;else{t.getTicks().length-1>a&&(f=Dx(f));var v=f*a;(g=Jr((y=Math.ceil(u[1]/f)*f)-v))<0&&u[0]>=0?(g=0,y=Jr(v)):y>0&&u[1]<=0&&(y=0,g=-Jr(v))}var m=(r[0].value-o[0].value)/s,x=(r[a].value-o[a].value)/s;i.setExtent.call(t,g+f*m,y+f*x),i.setInterval.call(t,f),(m||x)&&i.setNiceExtent.call(t,g+f,y-f)}var lI=function(){function t(t,e,n){this.type="grid",this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this.axisPointerEnabled=!0,this.dimensions=QM,this._initCartesian(t,e,n),this.model=t}return t.prototype.getRect=function(){return this._rect},t.prototype.update=function(t,e){var n=this._axesMap;function i(t){var e,n=G(t),i=n.length;if(i){for(var r=[],o=i-1;o>=0;o--){var a=t[+n[o]],s=a.model,l=a.scale;Tx(l)&&s.get("alignTicks")&&null==s.get("interval")?r.push(a):(__(l,s),Tx(l)&&(e=a))}r.length&&(e||__((e=r.pop()).scale,e.model),E(r,(function(t){sI(t.scale,t.model,e.scale)})))}}this._updateScale(t,this.model),i(n.x),i(n.y);var r={};E(n.x,(function(t){hI(n,"y",t,r)})),E(n.y,(function(t){hI(n,"x",t,r)})),this.resize(this.model,e)},t.prototype.resize=function(t,e,n){var i=t.getBoxLayoutParams(),r=!n&&t.get("containLabel"),o=kp(i,{width:e.getWidth(),height:e.getHeight()});this._rect=o;var a=this._axesList;function s(){E(a,(function(t){var e=t.isHorizontal(),n=e?[0,o.width]:[0,o.height],i=t.inverse?1:0;t.setExtent(n[i],n[1-i]),function(t,e){var n=t.getExtent(),i=n[0]+n[1];t.toGlobalCoord="x"===t.dim?function(t){return t+e}:function(t){return i-t+e},t.toLocalCoord="x"===t.dim?function(t){return t-e}:function(t){return i-t+e}}(t,e?o.x:o.y)}))}s(),r&&(E(a,(function(t){if(!t.model.get(["axisLabel","inside"])){var e=function(t){var e=t.model,n=t.scale;if(e.get(["axisLabel","show"])&&!n.isBlank()){var i,r,o=n.getExtent();r=n instanceof Rx?n.count():(i=n.getTicks()).length;var a,s=t.getLabelModel(),l=w_(t),u=1;r>40&&(u=Math.ceil(r/40));for(var h=0;h0&&i>0||n<0&&i<0)}(t)}var pI=Math.PI,dI=function(){function t(t,e){this.group=new Br,this.opt=e,this.axisModel=t,k(e,{labelOffset:0,nameDirection:1,tickDirection:1,labelDirection:1,silent:!0,handleAutoShown:function(){return!0}});var n=new Br({x:e.position[0],y:e.position[1],rotation:e.rotation});n.updateTransform(),this._transformGroup=n}return t.prototype.hasBuilder=function(t){return!!fI[t]},t.prototype.add=function(t){fI[t](this.opt,this.axisModel,this.group,this._transformGroup)},t.prototype.getGroup=function(){return this.group},t.innerTextLayout=function(t,e,n){var i,r,o=ao(e-t);return so(o)?(r=n>0?"top":"bottom",i="center"):so(o-pI)?(r=n>0?"bottom":"top",i="center"):(r="middle",i=o>0&&o0?"right":"left":n>0?"left":"right"),{rotation:o,textAlign:i,textVerticalAlign:r}},t.makeAxisEventDataBase=function(t){var e={componentType:t.mainType,componentIndex:t.componentIndex};return e[t.mainType+"Index"]=t.componentIndex,e},t.isLabelSilent=function(t){var e=t.get("tooltip");return t.get("silent")||!(t.get("triggerEvent")||e&&e.show)},t}(),fI={axisLine:function(t,e,n,i){var r=e.get(["axisLine","show"]);if("auto"===r&&t.handleAutoShown&&(r=t.handleAutoShown("axisLine")),r){var o=e.axis.getExtent(),a=i.transform,s=[o[0],0],l=[o[1],0],u=s[0]>l[0];a&&(Wt(s,s,a),Wt(l,l,a));var h=A({lineCap:"round"},e.getModel(["axisLine","lineStyle"]).getLineStyle()),c=new Ku({shape:{x1:s[0],y1:s[1],x2:l[0],y2:l[1]},style:h,strokeContainThreshold:t.strokeContainThreshold||5,silent:!0,z2:1});zh(c.shape,c.style.lineWidth),c.anid="line",n.add(c);var p=e.get(["axisLine","symbol"]);if(null!=p){var d=e.get(["axisLine","symbolSize"]);U(p)&&(p=[p,p]),(U(d)||j(d))&&(d=[d,d]);var f=Zy(e.get(["axisLine","symbolOffset"])||0,d),g=d[0],y=d[1];E([{rotate:t.rotation+Math.PI/2,offset:f[0],r:0},{rotate:t.rotation-Math.PI/2,offset:f[1],r:Math.sqrt((s[0]-l[0])*(s[0]-l[0])+(s[1]-l[1])*(s[1]-l[1]))}],(function(e,i){if("none"!==p[i]&&null!=p[i]){var r=Xy(p[i],-g/2,-y/2,g,y,h.stroke,!0),o=e.r+e.offset,a=u?l:s;r.attr({rotation:e.rotate,x:a[0]+o*Math.cos(t.rotation),y:a[1]-o*Math.sin(t.rotation),silent:!0,z2:11}),n.add(r)}}))}}},axisTickLabel:function(t,e,n,i){var r=function(t,e,n,i){var r=n.axis,o=n.getModel("axisTick"),a=o.get("show");"auto"===a&&i.handleAutoShown&&(a=i.handleAutoShown("axisTick"));if(!a||r.scale.isBlank())return;for(var s=o.getModel("lineStyle"),l=i.tickDirection*o.get("length"),u=mI(r.getTicksCoords(),e.transform,l,k(s.getLineStyle(),{stroke:n.get(["axisLine","lineStyle","color"])}),"ticks"),h=0;hc[1]?-1:1,d=["start"===s?c[0]-p*h:"end"===s?c[1]+p*h:(c[0]+c[1])/2,vI(s)?t.labelOffset+l*h:0],f=e.get("nameRotate");null!=f&&(f=f*pI/180),vI(s)?o=dI.innerTextLayout(t.rotation,null!=f?f:t.rotation,l):(o=function(t,e,n,i){var r,o,a=ao(n-t),s=i[0]>i[1],l="start"===e&&!s||"start"!==e&&s;so(a-pI/2)?(o=l?"bottom":"top",r="center"):so(a-1.5*pI)?(o=l?"top":"bottom",r="center"):(o="middle",r=a<1.5*pI&&a>pI/2?l?"left":"right":l?"right":"left");return{rotation:a,textAlign:r,textVerticalAlign:o}}(t.rotation,s,f||0,c),null!=(a=t.axisNameAvailableWidth)&&(a=Math.abs(a/Math.sin(o.rotation)),!isFinite(a)&&(a=null)));var g=u.getFont(),y=e.get("nameTruncate",!0)||{},v=y.ellipsis,m=it(t.nameTruncateMaxWidth,y.maxWidth,a),x=new Xs({x:d[0],y:d[1],rotation:o.rotation,silent:dI.isLabelSilent(e),style:oc(u,{text:r,font:g,overflow:"truncate",width:m,ellipsis:v,fill:u.getTextColor()||e.get(["axisLine","lineStyle","color"]),align:u.get("align")||o.textAlign,verticalAlign:u.get("verticalAlign")||o.textVerticalAlign}),z2:1});if(Kh({el:x,componentModel:e,itemName:r}),x.__fullText=r,x.anid="name",e.get("triggerEvent")){var _=dI.makeAxisEventDataBase(e);_.targetType="axisName",_.name=r,rl(x).eventData=_}i.add(x),x.updateTransform(),n.add(x),x.decomposeTransform()}}};function gI(t){t&&(t.ignore=!0)}function yI(t,e){var n=t&&t.getBoundingRect().clone(),i=e&&e.getBoundingRect().clone();if(n&&i){var r=xe([]);return Se(r,r,-t.rotation),n.applyTransform(be([],r,t.getLocalTransform())),i.applyTransform(be([],r,e.getLocalTransform())),n.intersect(i)}}function vI(t){return"middle"===t||"center"===t}function mI(t,e,n,i,r){for(var o=[],a=[],s=[],l=0;l=0||t===e}function bI(t){var e=wI(t);if(e){var n=e.axisPointerModel,i=e.axis.scale,r=n.option,o=n.get("status"),a=n.get("value");null!=a&&(a=i.parse(a));var s=SI(n);null==o&&(r.status=s?"show":"hide");var l=i.getExtent().slice();l[0]>l[1]&&l.reverse(),(null==a||a>l[1])&&(a=l[1]),a0&&!c.min?c.min=0:null!=c.min&&c.min<0&&!c.max&&(c.max=0);var p=a;null!=c.color&&(p=k({color:c.color},a));var d=C(T(c),{boundaryGap:t,splitNumber:e,scale:n,axisLine:i,axisTick:r,axisLabel:o,name:c.text,showName:s,nameLocation:"end",nameGap:u,nameTextStyle:p,triggerEvent:h},!1);if(U(l)){var f=d.name;d.name=l.replace("{value}",null!=f?f:"")}else X(l)&&(d.name=l(d.name,d));var g=new Cc(d,null,this.ecModel);return R(g,D_.prototype),g.mainType="radar",g.componentIndex=this.componentIndex,g}),this);this._indicatorModels=c},e.prototype.getIndicatorModels=function(){return this._indicatorModels},e.type="radar",e.defaultOption={z:0,center:["50%","50%"],radius:"75%",startAngle:90,axisName:{show:!0},boundaryGap:[0,0],splitNumber:5,axisNameGap:15,scale:!1,shape:"polygon",axisLine:C({lineStyle:{color:"#bbb"}},XI.axisLine),axisLabel:UI(XI.axisLabel,!1),axisTick:UI(XI.axisTick,!1),splitLine:UI(XI.splitLine,!0),splitArea:UI(XI.splitArea,!0),indicator:[]},e}(zp),jI=["axisLine","axisTickLabel","axisName"],qI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll(),this._buildAxes(t),this._buildSplitLineAndArea(t)},e.prototype._buildAxes=function(t){var e=t.coordinateSystem;E(z(e.getIndicatorAxes(),(function(t){var n=t.model.get("showName")?t.name:"";return new dI(t.model,{axisName:n,position:[e.cx,e.cy],rotation:t.angle,labelDirection:-1,tickDirection:-1,nameDirection:1})})),(function(t){E(jI,t.add,t),this.group.add(t.getGroup())}),this)},e.prototype._buildSplitLineAndArea=function(t){var e=t.coordinateSystem,n=e.getIndicatorAxes();if(n.length){var i=t.get("shape"),r=t.getModel("splitLine"),o=t.getModel("splitArea"),a=r.getModel("lineStyle"),s=o.getModel("areaStyle"),l=r.get("show"),u=o.get("show"),h=a.get("color"),c=s.get("color"),p=Y(h)?h:[h],d=Y(c)?c:[c],f=[],g=[];if("circle"===i)for(var y=n[0].getTicksCoords(),v=e.cx,m=e.cy,x=0;x3?1.4:r>1?1.2:1.1;iT(this,"zoom","zoomOnMouseWheel",t,{scale:i>0?s:1/s,originX:o,originY:a,isAvailableBehavior:null})}if(n){var l=Math.abs(i);iT(this,"scrollMove","moveOnMouseWheel",t,{scrollDelta:(i>0?1:-1)*(l>3?.4:l>1?.15:.05),originX:o,originY:a,isAvailableBehavior:null})}}},e.prototype._pinchHandler=function(t){tT(this._zr,"globalPan")||iT(this,"zoom",null,t,{scale:t.pinchScale>1?1.1:1/1.1,originX:t.pinchX,originY:t.pinchY,isAvailableBehavior:null})},e}(jt);function iT(t,e,n,i,r){t.pointerChecker&&t.pointerChecker(i,r.originX,r.originY)&&(de(i.event),rT(t,e,n,i,r))}function rT(t,e,n,i,r){r.isAvailableBehavior=W(oT,null,n,i),t.trigger(e,r)}function oT(t,e,n){var i=n[t];return!t||i&&(!U(i)||e.event[i+"Key"])}function aT(t,e,n){var i=t.target;i.x+=e,i.y+=n,i.dirty()}function sT(t,e,n,i){var r=t.target,o=t.zoomLimit,a=t.zoom=t.zoom||1;if(a*=e,o){var s=o.min||0,l=o.max||1/0;a=Math.max(Math.min(l,a),s)}var u=a/t.zoom;t.zoom=a,r.x-=(n-r.x)*(u-1),r.y-=(i-r.y)*(u-1),r.scaleX*=u,r.scaleY*=u,r.dirty()}var lT,uT={axisPointer:1,tooltip:1,brush:1};function hT(t,e,n){var i=e.getComponentByElement(t.topTarget),r=i&&i.coordinateSystem;return i&&i!==n&&!uT.hasOwnProperty(i.mainType)&&r&&r.model!==n}function cT(t){U(t)&&(t=(new DOMParser).parseFromString(t,"text/xml"));var e=t;for(9===e.nodeType&&(e=e.firstChild);"svg"!==e.nodeName.toLowerCase()||1!==e.nodeType;)e=e.nextSibling;return e}var pT={fill:"fill",stroke:"stroke","stroke-width":"lineWidth",opacity:"opacity","fill-opacity":"fillOpacity","stroke-opacity":"strokeOpacity","stroke-dasharray":"lineDash","stroke-dashoffset":"lineDashOffset","stroke-linecap":"lineCap","stroke-linejoin":"lineJoin","stroke-miterlimit":"miterLimit","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","text-anchor":"textAlign",visibility:"visibility",display:"display"},dT=G(pT),fT={"alignment-baseline":"textBaseline","stop-color":"stopColor"},gT=G(fT),yT=function(){function t(){this._defs={},this._root=null}return t.prototype.parse=function(t,e){e=e||{};var n=cT(t);this._defsUsePending=[];var i=new Br;this._root=i;var r=[],o=n.getAttribute("viewBox")||"",a=parseFloat(n.getAttribute("width")||e.width),s=parseFloat(n.getAttribute("height")||e.height);isNaN(a)&&(a=null),isNaN(s)&&(s=null),wT(n,i,null,!0,!1);for(var l,u,h=n.firstChild;h;)this._parseNode(h,i,r,null,!1,!1),h=h.nextSibling;if(function(t,e){for(var n=0;n=4&&(l={x:parseFloat(c[0]||0),y:parseFloat(c[1]||0),width:parseFloat(c[2]),height:parseFloat(c[3])})}if(l&&null!=a&&null!=s&&(u=LT(l,{x:0,y:0,width:a,height:s}),!e.ignoreViewBox)){var p=i;(i=new Br).add(p),p.scaleX=p.scaleY=u.scale,p.x=u.x,p.y=u.y}return e.ignoreRootClip||null==a||null==s||i.setClipPath(new Ws({shape:{x:0,y:0,width:a,height:s}})),{root:i,width:a,height:s,viewBoxRect:l,viewBoxTransform:u,named:r}},t.prototype._parseNode=function(t,e,n,i,r,o){var a,s=t.nodeName.toLowerCase(),l=i;if("defs"===s&&(r=!0),"text"===s&&(o=!0),"defs"===s||"switch"===s)a=e;else{if(!r){var u=lT[s];if(u&&_t(lT,s)){a=u.call(this,t,e);var h=t.getAttribute("name");if(h){var c={name:h,namedFrom:null,svgNodeTagLower:s,el:a};n.push(c),"g"===s&&(l=c)}else i&&n.push({name:i.name,namedFrom:i,svgNodeTagLower:s,el:a});e.add(a)}}var p=vT[s];if(p&&_t(vT,s)){var d=p.call(this,t),f=t.getAttribute("id");f&&(this._defs[f]=d)}}if(a&&a.isGroup)for(var g=t.firstChild;g;)1===g.nodeType?this._parseNode(g,a,n,l,r,o):3===g.nodeType&&o&&this._parseText(g,a),g=g.nextSibling},t.prototype._parseText=function(t,e){var n=new Ps({style:{text:t.textContent},silent:!0,x:this._textX||0,y:this._textY||0});_T(e,n),wT(t,n,this._defsUsePending,!1,!1),function(t,e){var n=e.__selfStyle;if(n){var i=n.textBaseline,r=i;i&&"auto"!==i?"baseline"===i?r="alphabetic":"before-edge"===i||"text-before-edge"===i?r="top":"after-edge"===i||"text-after-edge"===i?r="bottom":"central"!==i&&"mathematical"!==i||(r="middle"):r="alphabetic",t.style.textBaseline=r}var o=e.__inheritedStyle;if(o){var a=o.textAlign,s=a;a&&("middle"===a&&(s="center"),t.style.textAlign=s)}}(n,e);var i=n.style,r=i.fontSize;r&&r<9&&(i.fontSize=9,n.scaleX*=r/9,n.scaleY*=r/9);var o=(i.fontSize||i.fontFamily)&&[i.fontStyle,i.fontWeight,(i.fontSize||12)+"px",i.fontFamily||"sans-serif"].join(" ");i.font=o;var a=n.getBoundingRect();return this._textX+=a.width,e.add(n),n},t.internalField=void(lT={g:function(t,e){var n=new Br;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n},rect:function(t,e){var n=new Ws;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.setShape({x:parseFloat(t.getAttribute("x")||"0"),y:parseFloat(t.getAttribute("y")||"0"),width:parseFloat(t.getAttribute("width")||"0"),height:parseFloat(t.getAttribute("height")||"0")}),n.silent=!0,n},circle:function(t,e){var n=new Su;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute("cx")||"0"),cy:parseFloat(t.getAttribute("cy")||"0"),r:parseFloat(t.getAttribute("r")||"0")}),n.silent=!0,n},line:function(t,e){var n=new Ku;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.setShape({x1:parseFloat(t.getAttribute("x1")||"0"),y1:parseFloat(t.getAttribute("y1")||"0"),x2:parseFloat(t.getAttribute("x2")||"0"),y2:parseFloat(t.getAttribute("y2")||"0")}),n.silent=!0,n},ellipse:function(t,e){var n=new Iu;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute("cx")||"0"),cy:parseFloat(t.getAttribute("cy")||"0"),rx:parseFloat(t.getAttribute("rx")||"0"),ry:parseFloat(t.getAttribute("ry")||"0")}),n.silent=!0,n},polygon:function(t,e){var n,i=t.getAttribute("points");i&&(n=bT(i));var r=new Xu({shape:{points:n||[]},silent:!0});return _T(e,r),wT(t,r,this._defsUsePending,!1,!1),r},polyline:function(t,e){var n,i=t.getAttribute("points");i&&(n=bT(i));var r=new Zu({shape:{points:n||[]},silent:!0});return _T(e,r),wT(t,r,this._defsUsePending,!1,!1),r},image:function(t,e){var n=new Ns;return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.setStyle({image:t.getAttribute("xlink:href")||t.getAttribute("href"),x:+t.getAttribute("x"),y:+t.getAttribute("y"),width:+t.getAttribute("width"),height:+t.getAttribute("height")}),n.silent=!0,n},text:function(t,e){var n=t.getAttribute("x")||"0",i=t.getAttribute("y")||"0",r=t.getAttribute("dx")||"0",o=t.getAttribute("dy")||"0";this._textX=parseFloat(n)+parseFloat(r),this._textY=parseFloat(i)+parseFloat(o);var a=new Br;return _T(e,a),wT(t,a,this._defsUsePending,!1,!0),a},tspan:function(t,e){var n=t.getAttribute("x"),i=t.getAttribute("y");null!=n&&(this._textX=parseFloat(n)),null!=i&&(this._textY=parseFloat(i));var r=t.getAttribute("dx")||"0",o=t.getAttribute("dy")||"0",a=new Br;return _T(e,a),wT(t,a,this._defsUsePending,!1,!0),this._textX+=parseFloat(r),this._textY+=parseFloat(o),a},path:function(t,e){var n=_u(t.getAttribute("d")||"");return _T(e,n),wT(t,n,this._defsUsePending,!1,!1),n.silent=!0,n}}),t}(),vT={lineargradient:function(t){var e=parseInt(t.getAttribute("x1")||"0",10),n=parseInt(t.getAttribute("y1")||"0",10),i=parseInt(t.getAttribute("x2")||"10",10),r=parseInt(t.getAttribute("y2")||"0",10),o=new oh(e,n,i,r);return mT(t,o),xT(t,o),o},radialgradient:function(t){var e=parseInt(t.getAttribute("cx")||"0",10),n=parseInt(t.getAttribute("cy")||"0",10),i=parseInt(t.getAttribute("r")||"0",10),r=new ah(e,n,i);return mT(t,r),xT(t,r),r}};function mT(t,e){"userSpaceOnUse"===t.getAttribute("gradientUnits")&&(e.global=!0)}function xT(t,e){for(var n=t.firstChild;n;){if(1===n.nodeType&&"stop"===n.nodeName.toLocaleLowerCase()){var i=n.getAttribute("offset"),r=void 0;r=i&&i.indexOf("%")>0?parseInt(i,10)/100:i?parseFloat(i):0;var o={};kT(n,o,o);var a=o.stopColor||n.getAttribute("stop-color")||"#000000";e.colorStops.push({offset:r,color:a})}n=n.nextSibling}}function _T(t,e){t&&t.__inheritedStyle&&(e.__inheritedStyle||(e.__inheritedStyle={}),k(e.__inheritedStyle,t.__inheritedStyle))}function bT(t){for(var e=TT(t),n=[],i=0;i0;o-=2){var a=i[o],s=i[o-1],l=TT(a);switch(r=r||[1,0,0,1,0,0],s){case"translate":we(r,r,[parseFloat(l[0]),parseFloat(l[1]||"0")]);break;case"scale":Me(r,r,[parseFloat(l[0]),parseFloat(l[1]||l[0])]);break;case"rotate":Se(r,r,-parseFloat(l[0])*DT,[parseFloat(l[1]||"0"),parseFloat(l[2]||"0")]);break;case"skewX":be(r,[1,0,Math.tan(parseFloat(l[0])*DT),1,0,0],r);break;case"skewY":be(r,[1,Math.tan(parseFloat(l[0])*DT),0,1,0,0],r);break;case"matrix":r[0]=parseFloat(l[0]),r[1]=parseFloat(l[1]),r[2]=parseFloat(l[2]),r[3]=parseFloat(l[3]),r[4]=parseFloat(l[4]),r[5]=parseFloat(l[5])}}e.setLocalTransform(r)}}(t,e),kT(t,a,s),i||function(t,e,n){for(var i=0;i0,f={api:n,geo:s,mapOrGeoModel:t,data:a,isVisualEncodedByVisualMap:d,isGeo:o,transformInfoRaw:c};"geoJSON"===s.resourceType?this._buildGeoJSON(f):"geoSVG"===s.resourceType&&this._buildSVG(f),this._updateController(t,e,n),this._updateMapSelectHandler(t,l,n,i)},t.prototype._buildGeoJSON=function(t){var e=this._regionsGroupByName=yt(),n=yt(),i=this._regionsGroup,r=t.transformInfoRaw,o=t.mapOrGeoModel,a=t.data,s=t.geo.projection,l=s&&s.stream;function u(t,e){return e&&(t=e(t)),t&&[t[0]*r.scaleX+r.x,t[1]*r.scaleY+r.y]}function h(t){for(var e=[],n=!l&&s&&s.project,i=0;i=0)&&(p=r);var d=a?{normal:{align:"center",verticalAlign:"middle"}}:null;ic(e,rc(i),{labelFetcher:p,labelDataIndex:c,defaultText:n},d);var f=e.getTextContent();if(f&&(JT(f).ignore=f.ignore,e.textConfig&&a)){var g=e.getBoundingRect().clone();e.textConfig.layoutRect=g,e.textConfig.position=[(a[0]-g.x)/g.width*100+"%",(a[1]-g.y)/g.height*100+"%"]}e.disableLabelAnimation=!0}else e.removeTextContent(),e.removeTextConfig(),e.disableLabelAnimation=null}function rC(t,e,n,i,r,o){t.data?t.data.setItemGraphicEl(o,e):rl(e).eventData={componentType:"geo",componentIndex:r.componentIndex,geoIndex:r.componentIndex,name:n,region:i&&i.option||{}}}function oC(t,e,n,i,r){t.data||Kh({el:e,componentModel:r,itemName:n,itemTooltipOption:i.get("tooltip")})}function aC(t,e,n,i,r){e.highDownSilentOnTouch=!!r.get("selectedMode");var o=i.getModel("emphasis"),a=o.get("focus");return Zl(e,a,o.get("blurScope"),o.get("disabled")),t.isGeo&&function(t,e,n){var i=rl(t);i.componentMainType=e.mainType,i.componentIndex=e.componentIndex,i.componentHighDownName=n}(e,r,n),a}function sC(t,e,n){var i,r=[];function o(){i=[]}function a(){i.length&&(r.push(i),i=[])}var s=e({polygonStart:o,polygonEnd:a,lineStart:o,lineEnd:a,point:function(t,e){isFinite(t)&&isFinite(e)&&i.push([t,e])},sphere:function(){}});return!n&&s.polygonStart(),E(t,(function(t){s.lineStart();for(var e=0;e-1&&(n.style.stroke=n.style.fill,n.style.fill="#fff",n.style.lineWidth=2),n},e.type="series.map",e.dependencies=["geo"],e.layoutMode="box",e.defaultOption={z:2,coordinateSystem:"geo",map:"",left:"center",top:"center",aspectScale:null,showLegendSymbol:!0,boundingCoords:null,center:null,zoom:1,scaleLimit:null,selectedMode:!0,label:{show:!1,color:"#000"},itemStyle:{borderWidth:.5,borderColor:"#444",areaColor:"#eee"},emphasis:{label:{show:!0,color:"rgb(100,0,0)"},itemStyle:{areaColor:"rgba(255,215,0,0.8)"}},select:{label:{show:!0,color:"rgb(100,0,0)"},itemStyle:{color:"rgba(255,215,0,0.8)"}},nameProperty:"name"},e}(bg);function hC(t){var e={};t.eachSeriesByType("map",(function(t){var n=t.getHostGeoModel(),i=n?"o"+n.id:"i"+t.getMapType();(e[i]=e[i]||[]).push(t)})),E(e,(function(t,e){for(var n,i,r,o=(n=z(t,(function(t){return t.getData()})),i=t[0].get("mapValueCalculation"),r={},E(n,(function(t){t.each(t.mapDimension("value"),(function(e,n){var i="ec-"+t.getName(n);r[i]=r[i]||[],isNaN(e)||r[i].push(e)}))})),n[0].map(n[0].mapDimension("value"),(function(t,e){for(var o="ec-"+n[0].getName(e),a=0,s=1/0,l=-1/0,u=r[o].length,h=0;h1?(d.width=p,d.height=p/x):(d.height=p,d.width=p*x),d.y=c[1]-d.height/2,d.x=c[0]-d.width/2;else{var b=t.getBoxLayoutParams();b.aspect=x,d=kp(b,{width:v,height:m})}this.setViewRect(d.x,d.y,d.width,d.height),this.setCenter(t.get("center"),e),this.setZoom(t.get("zoom"))}R(vC,dC);var _C=function(){function t(){this.dimensions=yC}return t.prototype.create=function(t,e){var n=[];function i(t){return{nameProperty:t.get("nameProperty"),aspectScale:t.get("aspectScale"),projection:t.get("projection")}}t.eachComponent("geo",(function(t,r){var o=t.get("map"),a=new vC(o+r,o,A({nameMap:t.get("nameMap")},i(t)));a.zoomLimit=t.get("scaleLimit"),n.push(a),t.coordinateSystem=a,a.model=t,a.resize=xC,a.resize(t,e)})),t.eachSeries((function(t){if("geo"===t.get("coordinateSystem")){var e=t.get("geoIndex")||0;t.coordinateSystem=n[e]}}));var r={};return t.eachSeriesByType("map",(function(t){if(!t.getHostGeoModel()){var e=t.getMapType();r[e]=r[e]||[],r[e].push(t)}})),E(r,(function(t,r){var o=z(t,(function(t){return t.get("nameMap")})),a=new vC(r,r,A({nameMap:D(o)},i(t[0])));a.zoomLimit=it.apply(null,z(t,(function(t){return t.get("scaleLimit")}))),n.push(a),a.resize=xC,a.resize(t[0],e),E(t,(function(t){t.coordinateSystem=a,function(t,e){E(e.get("geoCoord"),(function(e,n){t.addGeoCoord(n,e)}))}(a,t)}))})),n},t.prototype.getFilledRegions=function(t,e,n,i){for(var r=(t||[]).slice(),o=yt(),a=0;a=0;){var o=e[n];o.hierNode.prelim+=i,o.hierNode.modifier+=i,r+=o.hierNode.change,i+=o.hierNode.shift+r}}(t);var o=(n[0].hierNode.prelim+n[n.length-1].hierNode.prelim)/2;r?(t.hierNode.prelim=r.hierNode.prelim+e(t,r),t.hierNode.modifier=t.hierNode.prelim-o):t.hierNode.prelim=o}else r&&(t.hierNode.prelim=r.hierNode.prelim+e(t,r));t.parentNode.hierNode.defaultAncestor=function(t,e,n,i){if(e){for(var r=t,o=t,a=o.parentNode.children[0],s=e,l=r.hierNode.modifier,u=o.hierNode.modifier,h=a.hierNode.modifier,c=s.hierNode.modifier;s=PC(s),o=OC(o),s&&o;){r=PC(r),a=OC(a),r.hierNode.ancestor=t;var p=s.hierNode.prelim+c-o.hierNode.prelim-u+i(s,o);p>0&&(NC(RC(s,t,n),t,p),u+=p,l+=p),c+=s.hierNode.modifier,u+=o.hierNode.modifier,l+=r.hierNode.modifier,h+=a.hierNode.modifier}s&&!PC(r)&&(r.hierNode.thread=s,r.hierNode.modifier+=c-l),o&&!OC(a)&&(a.hierNode.thread=o,a.hierNode.modifier+=u-h,n=t)}return n}(t,r,t.parentNode.hierNode.defaultAncestor||i[0],e)}function AC(t){var e=t.hierNode.prelim+t.parentNode.hierNode.modifier;t.setLayout({x:e},!0),t.hierNode.modifier+=t.parentNode.hierNode.modifier}function kC(t){return arguments.length?t:EC}function LC(t,e){return t-=Math.PI/2,{x:e*Math.cos(t),y:e*Math.sin(t)}}function PC(t){var e=t.children;return e.length&&t.isExpand?e[e.length-1]:t.hierNode.thread}function OC(t){var e=t.children;return e.length&&t.isExpand?e[0]:t.hierNode.thread}function RC(t,e,n){return t.hierNode.ancestor.parentNode===e.parentNode?t.hierNode.ancestor:n}function NC(t,e,n){var i=n/(e.hierNode.i-t.hierNode.i);e.hierNode.change-=i,e.hierNode.shift+=n,e.hierNode.modifier+=n,e.hierNode.prelim+=n,t.hierNode.change+=i}function EC(t,e){return t.parentNode===e.parentNode?1:2}var zC=function(){this.parentPoint=[],this.childPoints=[]},VC=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new zC},e.prototype.buildPath=function(t,e){var n=e.childPoints,i=n.length,r=e.parentPoint,o=n[0],a=n[i-1];if(1===i)return t.moveTo(r[0],r[1]),void t.lineTo(o[0],o[1]);var s=e.orient,l="TB"===s||"BT"===s?0:1,u=1-l,h=$r(e.forkPosition,1),c=[];c[l]=r[l],c[u]=r[u]+(a[u]-r[u])*h,t.moveTo(r[0],r[1]),t.lineTo(c[0],c[1]),t.moveTo(o[0],o[1]),c[l]=o[l],t.lineTo(c[0],c[1]),c[l]=a[l],t.lineTo(c[0],c[1]),t.lineTo(a[0],a[1]);for(var p=1;pm.x)||(_-=Math.PI);var S=b?"left":"right",M=s.getModel("label"),I=M.get("rotate"),T=I*(Math.PI/180),C=y.getTextContent();C&&(y.setTextConfig({position:M.get("position")||S,rotation:null==I?-_:T,origin:"center"}),C.setStyle("verticalAlign","middle"))}var D=s.get(["emphasis","focus"]),A="relative"===D?vt(a.getAncestorsIndices(),a.getDescendantIndices()):"ancestor"===D?a.getAncestorsIndices():"descendant"===D?a.getDescendantIndices():null;A&&(rl(n).focus=A),function(t,e,n,i,r,o,a,s){var l=e.getModel(),u=t.get("edgeShape"),h=t.get("layout"),c=t.getOrient(),p=t.get(["lineStyle","curveness"]),d=t.get("edgeForkPosition"),f=l.getModel("lineStyle").getLineStyle(),g=i.__edge;if("curve"===u)e.parentNode&&e.parentNode!==n&&(g||(g=i.__edge=new th({shape:XC(h,c,p,r,r)})),vh(g,{shape:XC(h,c,p,o,a)},t));else if("polyline"===u)if("orthogonal"===h){if(e!==n&&e.children&&0!==e.children.length&&!0===e.isExpand){for(var y=e.children,v=[],m=0;me&&(e=i.height)}this.height=e+1},t.prototype.getNodeById=function(t){if(this.getId()===t)return this;for(var e=0,n=this.children,i=n.length;e=0&&this.hostTree.data.setItemLayout(this.dataIndex,t,e)},t.prototype.getLayout=function(){return this.hostTree.data.getItemLayout(this.dataIndex)},t.prototype.getModel=function(t){if(!(this.dataIndex<0))return this.hostTree.data.getItemModel(this.dataIndex).getModel(t)},t.prototype.getLevelModel=function(){return(this.hostTree.levelModels||[])[this.depth]},t.prototype.setVisual=function(t,e){this.dataIndex>=0&&this.hostTree.data.setItemVisual(this.dataIndex,t,e)},t.prototype.getVisual=function(t){return this.hostTree.data.getItemVisual(this.dataIndex,t)},t.prototype.getRawIndex=function(){return this.hostTree.data.getRawIndex(this.dataIndex)},t.prototype.getId=function(){return this.hostTree.data.getId(this.dataIndex)},t.prototype.getChildIndex=function(){if(this.parentNode){for(var t=this.parentNode.children,e=0;e=0){var i=n.getData().tree.root,r=t.targetNode;if(U(r)&&(r=i.getNodeById(r)),r&&i.contains(r))return{node:r};var o=t.targetNodeId;if(null!=o&&(r=i.getNodeById(o)))return{node:r}}}function rD(t){for(var e=[];t;)(t=t.parentNode)&&e.push(t);return e.reverse()}function oD(t,e){return P(rD(t),e)>=0}function aD(t,e){for(var n=[];t;){var i=t.dataIndex;n.push({name:t.name,dataIndex:i,value:e.getRawValue(i)}),t=t.parentNode}return n.reverse(),n}var sD=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.hasSymbolVisual=!0,e.ignoreStyleOnData=!0,e}return n(e,t),e.prototype.getInitialData=function(t){var e={name:t.name,children:t.data},n=t.leaves||{},i=new Cc(n,this,this.ecModel),r=nD.createTree(e,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=r.getNodeByDataIndex(e);return n&&n.children.length&&n.isExpand||(t.parentModel=i),t}))}));var o=0;r.eachNode("preorder",(function(t){t.depth>o&&(o=t.depth)}));var a=t.expandAndCollapse&&t.initialTreeDepth>=0?t.initialTreeDepth:o;return r.root.eachNode("preorder",(function(t){var e=t.hostTree.data.getRawDataItem(t.dataIndex);t.isExpand=e&&null!=e.collapsed?!e.collapsed:t.depth<=a})),r.data},e.prototype.getOrient=function(){var t=this.get("orient");return"horizontal"===t?t="LR":"vertical"===t&&(t="TB"),t},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.formatTooltip=function(t,e,n){for(var i=this.getData().tree,r=i.root.children[0],o=i.getNodeByDataIndex(t),a=o.getValue(),s=o.name;o&&o!==r;)s=o.parentNode.name+"."+s,o=o.parentNode;return og("nameValue",{name:s,value:a,noValue:isNaN(a)||null==a})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treeAncestors=aD(i,this),n.collapsed=!i.isExpand,n},e.type="series.tree",e.layoutMode="box",e.defaultOption={z:2,coordinateSystem:"view",left:"12%",top:"12%",right:"12%",bottom:"12%",layout:"orthogonal",edgeShape:"curve",edgeForkPosition:"50%",roam:!1,nodeScaleRatio:.4,center:null,zoom:1,orient:"LR",symbol:"emptyCircle",symbolSize:7,expandAndCollapse:!0,initialTreeDepth:2,lineStyle:{color:"#ccc",width:1.5,curveness:.5},itemStyle:{color:"lightsteelblue",borderWidth:1.5},label:{show:!0},animationEasing:"linear",animationDuration:700,animationDurationUpdate:500},e}(bg);function lD(t,e){for(var n,i=[t];n=i.pop();)if(e(n),n.isExpand){var r=n.children;if(r.length)for(var o=r.length-1;o>=0;o--)i.push(r[o])}}function uD(t,e){t.eachSeriesByType("tree",(function(t){!function(t,e){var n=function(t,e){return kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=n;var i=t.get("layout"),r=0,o=0,a=null;"radial"===i?(r=2*Math.PI,o=Math.min(n.height,n.width)/2,a=kC((function(t,e){return(t.parentNode===e.parentNode?1:2)/t.depth}))):(r=n.width,o=n.height,a=kC());var s=t.getData().tree.root,l=s.children[0];if(l){!function(t){var e=t;e.hierNode={defaultAncestor:null,ancestor:e,prelim:0,modifier:0,change:0,shift:0,i:0,thread:null};for(var n,i,r=[e];n=r.pop();)if(i=n.children,n.isExpand&&i.length)for(var o=i.length-1;o>=0;o--){var a=i[o];a.hierNode={defaultAncestor:null,ancestor:a,prelim:0,modifier:0,change:0,shift:0,i:o,thread:null},r.push(a)}}(s),function(t,e,n){for(var i,r=[t],o=[];i=r.pop();)if(o.push(i),i.isExpand){var a=i.children;if(a.length)for(var s=0;sh.getLayout().x&&(h=t),t.depth>c.depth&&(c=t)}));var p=u===h?1:a(u,h)/2,d=p-u.getLayout().x,f=0,g=0,y=0,v=0;if("radial"===i)f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),lD(l,(function(t){y=(t.getLayout().x+d)*f,v=(t.depth-1)*g;var e=LC(y,v);t.setLayout({x:e.x,y:e.y,rawX:y,rawY:v},!0)}));else{var m=t.getOrient();"RL"===m||"LR"===m?(g=o/(h.getLayout().x+p+d),f=r/(c.depth-1||1),lD(l,(function(t){v=(t.getLayout().x+d)*g,y="LR"===m?(t.depth-1)*f:r-(t.depth-1)*f,t.setLayout({x:y,y:v},!0)}))):"TB"!==m&&"BT"!==m||(f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),lD(l,(function(t){y=(t.getLayout().x+d)*f,v="TB"===m?(t.depth-1)*g:o-(t.depth-1)*g,t.setLayout({x:y,y:v},!0)})))}}}(t,e)}))}function hD(t){t.eachSeriesByType("tree",(function(t){var e=t.getData();e.tree.eachNode((function(t){var n=t.getModel().getModel("itemStyle").getItemStyle();A(e.ensureUniqueItemVisual(t.dataIndex,"style"),n)}))}))}var cD=["treemapZoomToNode","treemapRender","treemapMove"];function pD(t){var e=t.getData().tree,n={};e.eachNode((function(e){for(var i=e;i&&i.depth>1;)i=i.parentNode;var r=pd(t.ecModel,i.name||i.dataIndex+"",n);e.setVisual("decal",r)}))}var dD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.preventUsingHoverLayer=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};fD(n);var i=t.levels||[],r=this.designatedVisualItemStyle={},o=new Cc({itemStyle:r},this,e);i=t.levels=function(t,e){var n,i,r=To(e.get("color")),o=To(e.get(["aria","decal","decals"]));if(!r)return;t=t||[],E(t,(function(t){var e=new Cc(t),r=e.get("color"),o=e.get("decal");(e.get(["itemStyle","color"])||r&&"none"!==r)&&(n=!0),(e.get(["itemStyle","decal"])||o&&"none"!==o)&&(i=!0)}));var a=t[0]||(t[0]={});n||(a.color=r.slice());!i&&o&&(a.decal=o.slice());return t}(i,e);var a=z(i||[],(function(t){return new Cc(t,o,e)}),this),s=nD.createTree(n,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=s.getNodeByDataIndex(e),i=n?a[n.depth]:null;return t.parentModel=i||o,t}))}));return s.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.formatTooltip=function(t,e,n){var i=this.getData(),r=this.getRawValue(t);return og("nameValue",{name:i.getName(t),value:r})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treeAncestors=aD(i,this),n.treePathInfo=n.treeAncestors,n},e.prototype.setLayoutInfo=function(t){this.layoutInfo=this.layoutInfo||{},A(this.layoutInfo,t)},e.prototype.mapIdToIndex=function(t){var e=this._idIndexMap;e||(e=this._idIndexMap=yt(),this._idIndexMapCount=0);var n=e.get(t);return null==n&&e.set(t,n=this._idIndexMapCount++),n},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){pD(this)},e.type="series.treemap",e.layoutMode="box",e.defaultOption={progressive:0,left:"center",top:"middle",width:"80%",height:"80%",sort:!0,clipWindow:"origin",squareRatio:.5*(1+Math.sqrt(5)),leafDepth:null,drillDownIcon:"▶",zoomToNodeRatio:.1024,scaleLimit:null,roam:!0,nodeClick:"zoomToNode",animation:!0,animationDurationUpdate:900,animationEasing:"quinticInOut",breadcrumb:{show:!0,height:22,left:"center",top:"bottom",emptyItemWidth:25,itemStyle:{color:"rgba(0,0,0,0.7)",textStyle:{color:"#fff"}},emphasis:{itemStyle:{color:"rgba(0,0,0,0.9)"}}},label:{show:!0,distance:0,padding:5,position:"inside",color:"#fff",overflow:"truncate"},upperLabel:{show:!1,position:[0,"50%"],height:20,overflow:"truncate",verticalAlign:"middle"},itemStyle:{color:null,colorAlpha:null,colorSaturation:null,borderWidth:0,gapWidth:0,borderColor:"#fff",borderColorSaturation:null},emphasis:{upperLabel:{show:!0,position:[0,"50%"],overflow:"truncate",verticalAlign:"middle"}},visualDimension:0,visualMin:null,visualMax:null,color:[],colorAlpha:null,colorSaturation:null,colorMappingBy:"index",visibleMin:10,childrenVisibleMin:null,levels:[]},e}(bg);function fD(t){var e=0;E(t.children,(function(t){fD(t);var n=t.value;Y(n)&&(n=n[0]),e+=n}));var n=t.value;Y(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),Y(t.value)?t.value[0]=n:t.value=n}var gD=function(){function t(t){this.group=new Br,t.add(this.group)}return t.prototype.render=function(t,e,n,i){var r=t.getModel("breadcrumb"),o=this.group;if(o.removeAll(),r.get("show")&&n){var a=r.getModel("itemStyle"),s=r.getModel("emphasis"),l=a.getModel("textStyle"),u=s.getModel(["itemStyle","textStyle"]),h={pos:{left:r.get("left"),right:r.get("right"),top:r.get("top"),bottom:r.get("bottom")},box:{width:e.getWidth(),height:e.getHeight()},emptyItemWidth:r.get("emptyItemWidth"),totalWidth:0,renderList:[]};this._prepare(n,h,l),this._renderContent(t,h,a,s,l,u,i),Lp(o,h.pos,h.box)}},t.prototype._prepare=function(t,e,n){for(var i=t;i;i=i.parentNode){var r=Ro(i.getModel().get("name"),""),o=n.getTextRect(r),a=Math.max(o.width+16,e.emptyItemWidth);e.totalWidth+=a+8,e.renderList.push({node:i,text:r,width:a})}},t.prototype._renderContent=function(t,e,n,i,r,o,a){for(var s,l,u,h,c,p,d,f,g,y=0,v=e.emptyItemWidth,m=t.get(["breadcrumb","height"]),x=(s=e.pos,l=e.box,h=l.width,c=l.height,p=$r(s.left,h),d=$r(s.top,c),f=$r(s.right,h),g=$r(s.bottom,c),(isNaN(p)||isNaN(parseFloat(s.left)))&&(p=0),(isNaN(f)||isNaN(parseFloat(s.right)))&&(f=h),(isNaN(d)||isNaN(parseFloat(s.top)))&&(d=0),(isNaN(g)||isNaN(parseFloat(s.bottom)))&&(g=c),u=vp(u||0),{width:Math.max(f-p-u[1]-u[3],0),height:Math.max(g-d-u[0]-u[2],0)}),_=e.totalWidth,b=e.renderList,w=i.getModel("itemStyle").getItemStyle(),S=b.length-1;S>=0;S--){var M=b[S],I=M.node,T=M.width,C=M.text;_>x.width&&(_-=T-v,T=v,C=null);var D=new Xu({shape:{points:yD(y,0,T,m,S===b.length-1,0===S)},style:k(n.getItemStyle(),{lineJoin:"bevel"}),textContent:new Xs({style:oc(r,{text:C})}),textConfig:{position:"inside"},z2:1e5,onclick:H(a,I)});D.disableLabelAnimation=!0,D.getTextContent().ensureState("emphasis").style=oc(o,{text:C}),D.ensureState("emphasis").style=w,Zl(D,i.get("focus"),i.get("blurScope"),i.get("disabled")),this.group.add(D),vD(D,t,I),y+=T+8}},t.prototype.remove=function(){this.group.removeAll()},t}();function yD(t,e,n,i,r,o){var a=[[r?t:t-5,e],[t+n,e],[t+n,e+i],[r?t:t-5,e+i]];return!o&&a.splice(2,0,[t+n+5,e+i/2]),!r&&a.push([t,e+i/2]),a}function vD(t,e,n){rl(t).eventData={componentType:"series",componentSubType:"treemap",componentIndex:e.componentIndex,seriesIndex:e.seriesIndex,seriesName:e.name,seriesType:"treemap",selfType:"breadcrumb",nodeData:{dataIndex:n&&n.dataIndex,name:n&&n.name},treePathInfo:n&&aD(n,e)}}var mD=function(){function t(){this._storage=[],this._elExistsMap={}}return t.prototype.add=function(t,e,n,i,r){return!this._elExistsMap[t.id]&&(this._elExistsMap[t.id]=!0,this._storage.push({el:t,target:e,duration:n,delay:i,easing:r}),!0)},t.prototype.finished=function(t){return this._finishedCallback=t,this},t.prototype.start=function(){for(var t=this,e=this._storage.length,n=function(){--e<=0&&(t._storage.length=0,t._elExistsMap={},t._finishedCallback&&t._finishedCallback())},i=0,r=this._storage.length;i3||Math.abs(t.dy)>3)){var e=this.seriesModel.getData().tree.root;if(!e)return;var n=e.getLayout();if(!n)return;this.api.dispatchAction({type:"treemapMove",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:n.x+t.dx,y:n.y+t.dy,width:n.width,height:n.height}})}},e.prototype._onZoom=function(t){var e=t.originX,n=t.originY,i=t.scale;if("animating"!==this._state){var r=this.seriesModel.getData().tree.root;if(!r)return;var o=r.getLayout();if(!o)return;var a,s=new ze(o.x,o.y,o.width,o.height),l=this._controllerHost;a=l.zoomLimit;var u=l.zoom=l.zoom||1;if(u*=i,a){var h=a.min||0,c=a.max||1/0;u=Math.max(Math.min(c,u),h)}var p=u/l.zoom;l.zoom=u;var d=this.seriesModel.layoutInfo,f=[1,0,0,1,0,0];we(f,f,[-(e-=d.x),-(n-=d.y)]),Me(f,f,[p,p]),we(f,f,[e,n]),s.applyTransform(f),this.api.dispatchAction({type:"treemapRender",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:s.x,y:s.y,width:s.width,height:s.height}})}},e.prototype._initEvents=function(t){var e=this;t.on("click",(function(t){if("ready"===e._state){var n=e.seriesModel.get("nodeClick",!0);if(n){var i=e.findTarget(t.offsetX,t.offsetY);if(i){var r=i.node;if(r.getLayout().isLeafRoot)e._rootToNode(i);else if("zoomToNode"===n)e._zoomToNode(i);else if("link"===n){var o=r.hostTree.data.getItemModel(r.dataIndex),a=o.get("link",!0),s=o.get("target",!0)||"blank";a&&Mp(a,s)}}}}}),this)},e.prototype._renderBreadcrumb=function(t,e,n){var i=this;n||(n=null!=t.get("leafDepth",!0)?{node:t.getViewRoot()}:this.findTarget(e.getWidth()/2,e.getHeight()/2))||(n={node:t.getData().tree.root}),(this._breadcrumb||(this._breadcrumb=new gD(this.group))).render(t,e,n.node,(function(e){"animating"!==i._state&&(oD(t.getViewRoot(),e)?i._rootToNode({node:e}):i._zoomToNode({node:e}))}))},e.prototype.remove=function(){this._clearController(),this._containerGroup&&this._containerGroup.removeAll(),this._storage={nodeGroup:[],background:[],content:[]},this._state="ready",this._breadcrumb&&this._breadcrumb.remove()},e.prototype.dispose=function(){this._clearController()},e.prototype._zoomToNode=function(t){this.api.dispatchAction({type:"treemapZoomToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype._rootToNode=function(t){this.api.dispatchAction({type:"treemapRootToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype.findTarget=function(t,e){var n;return this.seriesModel.getViewRoot().eachNode({attr:"viewChildren",order:"preorder"},(function(i){var r=this._storage.background[i.getRawIndex()];if(r){var o=r.transformCoordToLocal(t,e),a=r.shape;if(!(a.x<=o[0]&&o[0]<=a.x+a.width&&a.y<=o[1]&&o[1]<=a.y+a.height))return!1;n={node:i,offsetX:o[0],offsetY:o[1]}}}),this),n},e.type="treemap",e}(Og);var CD=E,DD=q,AD=-1,kD=function(){function t(e){var n=e.mappingMethod,i=e.type,r=this.option=T(e);this.type=i,this.mappingMethod=n,this._normalizeData=FD[n];var o=t.visualHandlers[i];this.applyVisual=o.applyVisual,this.getColorMapper=o.getColorMapper,this._normalizedToVisual=o._normalizedToVisual[n],"piecewise"===n?(LD(r),function(t){var e=t.pieceList;t.hasSpecialVisual=!1,E(e,(function(e,n){e.originIndex=n,null!=e.visual&&(t.hasSpecialVisual=!0)}))}(r)):"category"===n?r.categories?function(t){var e=t.categories,n=t.categoryMap={},i=t.visual;if(CD(e,(function(t,e){n[t]=e})),!Y(i)){var r=[];q(i)?CD(i,(function(t,e){var i=n[e];r[null!=i?i:AD]=t})):r[-1]=i,i=BD(t,r)}for(var o=e.length-1;o>=0;o--)null==i[o]&&(delete n[e[o]],e.pop())}(r):LD(r,!0):(lt("linear"!==n||r.dataExtent),LD(r))}return t.prototype.mapValueToVisual=function(t){var e=this._normalizeData(t);return this._normalizedToVisual(e,t)},t.prototype.getNormalizer=function(){return W(this._normalizeData,this)},t.listVisualTypes=function(){return G(t.visualHandlers)},t.isValidType=function(e){return t.visualHandlers.hasOwnProperty(e)},t.eachVisual=function(t,e,n){q(t)?E(t,e,n):e.call(n,t)},t.mapVisual=function(e,n,i){var r,o=Y(e)?[]:q(e)?{}:(r=!0,null);return t.eachVisual(e,(function(t,e){var a=n.call(i,t,e);r?o=a:o[e]=a})),o},t.retrieveVisuals=function(e){var n,i={};return e&&CD(t.visualHandlers,(function(t,r){e.hasOwnProperty(r)&&(i[r]=e[r],n=!0)})),n?i:null},t.prepareVisualTypes=function(t){if(Y(t))t=t.slice();else{if(!DD(t))return[];var e=[];CD(t,(function(t,n){e.push(n)})),t=e}return t.sort((function(t,e){return"color"===e&&"color"!==t&&0===t.indexOf("color")?1:-1})),t},t.dependsOn=function(t,e){return"color"===e?!(!t||0!==t.indexOf(e)):t===e},t.findPieceIndex=function(t,e,n){for(var i,r=1/0,o=0,a=e.length;ou[1]&&(u[1]=l);var h=e.get("colorMappingBy"),c={type:a.name,dataExtent:u,visual:a.range};"color"!==c.type||"index"!==h&&"id"!==h?c.mappingMethod="linear":(c.mappingMethod="category",c.loop=!0);var p=new kD(c);return WD(p).drColorMappingBy=h,p}(0,r,o,0,u,d);E(d,(function(t,e){if(t.depth>=n.length||t===n[t.depth]){var o=function(t,e,n,i,r,o){var a=A({},e);if(r){var s=r.type,l="color"===s&&WD(r).drColorMappingBy,u="index"===l?i:"id"===l?o.mapIdToIndex(n.getId()):n.getValue(t.get("visualDimension"));a[s]=r.mapValueToVisual(u)}return a}(r,u,t,e,f,i);YD(t,o,n,i)}}))}else s=XD(u),h.fill=s}}function XD(t){var e=UD(t,"color");if(e){var n=UD(t,"colorAlpha"),i=UD(t,"colorSaturation");return i&&(e=ni(e,null,null,i)),n&&(e=ii(e,n)),e}}function UD(t,e){var n=t[e];if(null!=n&&"none"!==n)return n}function ZD(t,e){var n=t.get(e);return Y(n)&&n.length?{name:e,range:n}:null}var jD=Math.max,qD=Math.min,KD=it,$D=E,JD=["itemStyle","borderWidth"],QD=["itemStyle","gapWidth"],tA=["upperLabel","show"],eA=["upperLabel","height"],nA={seriesType:"treemap",reset:function(t,e,n,i){var r=n.getWidth(),o=n.getHeight(),a=t.option,s=kp(t.getBoxLayoutParams(),{width:n.getWidth(),height:n.getHeight()}),l=a.size||[],u=$r(KD(s.width,l[0]),r),h=$r(KD(s.height,l[1]),o),c=i&&i.type,p=iD(i,["treemapZoomToNode","treemapRootToNode"],t),d="treemapRender"===c||"treemapMove"===c?i.rootRect:null,f=t.getViewRoot(),g=rD(f);if("treemapMove"!==c){var y="treemapZoomToNode"===c?function(t,e,n,i,r){var o,a=(e||{}).node,s=[i,r];if(!a||a===n)return s;var l=i*r,u=l*t.option.zoomToNodeRatio;for(;o=a.parentNode;){for(var h=0,c=o.children,p=0,d=c.length;poo&&(u=oo),a=o}ua[1]&&(a[1]=e)}))):a=[NaN,NaN];return{sum:i,dataExtent:a}}(e,a,s);if(0===u.sum)return t.viewChildren=[];if(u.sum=function(t,e,n,i,r){if(!i)return n;for(var o=t.get("visibleMin"),a=r.length,s=a,l=a-1;l>=0;l--){var u=r["asc"===i?a-l-1:l].getValue();u/n*ei&&(i=a));var l=t.area*t.area,u=e*e*n;return l?jD(u*i/l,l/(u*r)):1/0}function oA(t,e,n,i,r){var o=e===n.width?0:1,a=1-o,s=["x","y"],l=["width","height"],u=n[s[o]],h=e?t.area/e:0;(r||h>n[l[a]])&&(h=n[l[a]]);for(var c=0,p=t.length;ci&&(i=e);var o=i%2?i+2:i+3;r=[];for(var a=0;a0&&(m[0]=-m[0],m[1]=-m[1]);var _=v[0]<0?-1:1;if("start"!==i.__position&&"end"!==i.__position){var b=-Math.atan2(v[1],v[0]);u[0].8?"left":h[0]<-.8?"right":"center",p=h[1]>.8?"top":h[1]<-.8?"bottom":"middle";break;case"start":i.x=-h[0]*f+l[0],i.y=-h[1]*g+l[1],c=h[0]>.8?"right":h[0]<-.8?"left":"center",p=h[1]>.8?"bottom":h[1]<-.8?"top":"middle";break;case"insideStartTop":case"insideStart":case"insideStartBottom":i.x=f*_+l[0],i.y=l[1]+w,c=v[0]<0?"right":"left",i.originX=-f*_,i.originY=-w;break;case"insideMiddleTop":case"insideMiddle":case"insideMiddleBottom":case"middle":i.x=x[0],i.y=x[1]+w,c="center",i.originY=-w;break;case"insideEndTop":case"insideEnd":case"insideEndBottom":i.x=-f*_+u[0],i.y=u[1]+w,c=v[0]>=0?"right":"left",i.originX=f*_,i.originY=-w}i.scaleX=i.scaleY=r,i.setStyle({verticalAlign:i.__verticalAlign||p,align:i.__align||c})}}}function S(t,e){var n=t.__specifiedRotation;if(null==n){var i=a.tangentAt(e);t.attr("rotation",(1===e?-1:1)*Math.PI/2-Math.atan2(i[1],i[0]))}else t.attr("rotation",n)}},e}(Br),YA=function(){function t(t){this.group=new Br,this._LineCtor=t||HA}return t.prototype.updateData=function(t){var e=this;this._progressiveEls=null;var n=this,i=n.group,r=n._lineData;n._lineData=t,r||i.removeAll();var o=XA(t);t.diff(r).add((function(n){e._doAdd(t,n,o)})).update((function(n,i){e._doUpdate(r,t,i,n,o)})).remove((function(t){i.remove(r.getItemGraphicEl(t))})).execute()},t.prototype.updateLayout=function(){var t=this._lineData;t&&t.eachItemGraphicEl((function(e,n){e.updateLayout(t,n)}),this)},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=XA(t),this._lineData=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e){function n(t){t.isGroup||function(t){return t.animators&&t.animators.length>0}(t)||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}this._progressiveEls=[];for(var i=t.start;i=0?i+=u:i-=u:f>=0?i-=u:i+=u}return i}function ek(t,e){var n=[],i=Dn,r=[[],[],[]],o=[[],[]],a=[];e/=2,t.eachEdge((function(t,s){var l=t.getLayout(),u=t.getVisual("fromSymbol"),h=t.getVisual("toSymbol");l.__original||(l.__original=[Tt(l[0]),Tt(l[1])],l[2]&&l.__original.push(Tt(l[2])));var c=l.__original;if(null!=l[2]){if(It(r[0],c[0]),It(r[1],c[2]),It(r[2],c[1]),u&&"none"!==u){var p=SA(t.node1),d=tk(r,c[0],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[0][0]=n[3],r[1][0]=n[4],i(r[0][1],r[1][1],r[2][1],d,n),r[0][1]=n[3],r[1][1]=n[4]}if(h&&"none"!==h){p=SA(t.node2),d=tk(r,c[1],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[1][0]=n[1],r[2][0]=n[2],i(r[0][1],r[1][1],r[2][1],d,n),r[1][1]=n[1],r[2][1]=n[2]}It(l[0],r[0]),It(l[1],r[2]),It(l[2],r[1])}else{if(It(o[0],c[0]),It(o[1],c[1]),kt(a,o[1],o[0]),Et(a,a),u&&"none"!==u){p=SA(t.node1);At(o[0],o[0],a,p*e)}if(h&&"none"!==h){p=SA(t.node2);At(o[1],o[1],a,-p*e)}It(l[0],o[0]),It(l[1],o[1])}}))}function nk(t){return"view"===t.type}var ik=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){var n=new xS,i=new YA,r=this.group;this._controller=new nT(e.getZr()),this._controllerHost={target:r},r.add(n.group),r.add(i.group),this._symbolDraw=n,this._lineDraw=i,this._firstRender=!0},e.prototype.render=function(t,e,n){var i=this,r=t.coordinateSystem;this._model=t;var o=this._symbolDraw,a=this._lineDraw,s=this.group;if(nk(r)){var l={x:r.x,y:r.y,scaleX:r.scaleX,scaleY:r.scaleY};this._firstRender?s.attr(l):vh(s,l,t)}ek(t.getGraph(),wA(t));var u=t.getData();o.updateData(u);var h=t.getEdgeData();a.updateData(h),this._updateNodeAndLinkScale(),this._updateController(t,e,n),clearTimeout(this._layoutTimeout);var c=t.forceLayout,p=t.get(["force","layoutAnimation"]);c&&this._startForceLayoutIteration(c,p);var d=t.get("layout");u.graph.eachNode((function(e){var n=e.dataIndex,r=e.getGraphicEl(),o=e.getModel();if(r){r.off("drag").off("dragend");var a=o.get("draggable");a&&r.on("drag",(function(o){switch(d){case"force":c.warmUp(),!i._layouting&&i._startForceLayoutIteration(c,p),c.setFixed(n),u.setItemLayout(n,[r.x,r.y]);break;case"circular":u.setItemLayout(n,[r.x,r.y]),e.setLayout({fixed:!0},!0),TA(t,"symbolSize",e,[o.offsetX,o.offsetY]),i.updateLayout(t);break;default:u.setItemLayout(n,[r.x,r.y]),_A(t.getGraph(),t),i.updateLayout(t)}})).on("dragend",(function(){c&&c.setUnfixed(n)})),r.setDraggable(a,!!o.get("cursor")),"adjacency"===o.get(["emphasis","focus"])&&(rl(r).focus=e.getAdjacentDataIndices())}})),u.graph.eachEdge((function(t){var e=t.getGraphicEl(),n=t.getModel().get(["emphasis","focus"]);e&&"adjacency"===n&&(rl(e).focus={edge:[t.dataIndex],node:[t.node1.dataIndex,t.node2.dataIndex]})}));var f="circular"===t.get("layout")&&t.get(["circular","rotateLabel"]),g=u.getLayout("cx"),y=u.getLayout("cy");u.graph.eachNode((function(t){DA(t,f,g,y)})),this._firstRender=!1},e.prototype.dispose=function(){this.remove(),this._controller&&this._controller.dispose(),this._controllerHost=null},e.prototype._startForceLayoutIteration=function(t,e){var n=this;!function i(){t.step((function(t){n.updateLayout(n._model),(n._layouting=!t)&&(e?n._layoutTimeout=setTimeout(i,16):i())}))}()},e.prototype._updateController=function(t,e,n){var i=this,r=this._controller,o=this._controllerHost,a=this.group;r.setPointerChecker((function(e,i,r){var o=a.getBoundingRect();return o.applyTransform(a.transform),o.contain(i,r)&&!hT(e,n,t)})),nk(t.coordinateSystem)?(r.enable(t.get("roam")),o.zoomLimit=t.get("scaleLimit"),o.zoom=t.coordinateSystem.getZoom(),r.off("pan").off("zoom").on("pan",(function(e){aT(o,e.dx,e.dy),n.dispatchAction({seriesId:t.id,type:"graphRoam",dx:e.dx,dy:e.dy})})).on("zoom",(function(e){sT(o,e.scale,e.originX,e.originY),n.dispatchAction({seriesId:t.id,type:"graphRoam",zoom:e.scale,originX:e.originX,originY:e.originY}),i._updateNodeAndLinkScale(),ek(t.getGraph(),wA(t)),i._lineDraw.updateLayout(),n.updateLabelLayout()}))):r.disable()},e.prototype._updateNodeAndLinkScale=function(){var t=this._model,e=t.getData(),n=wA(t);e.eachItemGraphicEl((function(t,e){t&&t.setSymbolScale(n)}))},e.prototype.updateLayout=function(t){ek(t.getGraph(),wA(t)),this._symbolDraw.updateLayout(),this._lineDraw.updateLayout()},e.prototype.remove=function(){clearTimeout(this._layoutTimeout),this._layouting=!1,this._layoutTimeout=null,this._symbolDraw&&this._symbolDraw.remove(),this._lineDraw&&this._lineDraw.remove()},e.type="graph",e}(Og);function rk(t){return"_EC_"+t}var ok=function(){function t(t){this.type="graph",this.nodes=[],this.edges=[],this._nodesMap={},this._edgesMap={},this._directed=t||!1}return t.prototype.isDirected=function(){return this._directed},t.prototype.addNode=function(t,e){t=null==t?""+e:""+t;var n=this._nodesMap;if(!n[rk(t)]){var i=new ak(t,e);return i.hostGraph=this,this.nodes.push(i),n[rk(t)]=i,i}},t.prototype.getNodeByIndex=function(t){var e=this.data.getRawIndex(t);return this.nodes[e]},t.prototype.getNodeById=function(t){return this._nodesMap[rk(t)]},t.prototype.addEdge=function(t,e,n){var i=this._nodesMap,r=this._edgesMap;if(j(t)&&(t=this.nodes[t]),j(e)&&(e=this.nodes[e]),t instanceof ak||(t=i[rk(t)]),e instanceof ak||(e=i[rk(e)]),t&&e){var o=t.id+"-"+e.id,a=new sk(t,e,n);return a.hostGraph=this,this._directed&&(t.outEdges.push(a),e.inEdges.push(a)),t.edges.push(a),t!==e&&e.edges.push(a),this.edges.push(a),r[o]=a,a}},t.prototype.getEdgeByIndex=function(t){var e=this.edgeData.getRawIndex(t);return this.edges[e]},t.prototype.getEdge=function(t,e){t instanceof ak&&(t=t.id),e instanceof ak&&(e=e.id);var n=this._edgesMap;return this._directed?n[t+"-"+e]:n[t+"-"+e]||n[e+"-"+t]},t.prototype.eachNode=function(t,e){for(var n=this.nodes,i=n.length,r=0;r=0&&t.call(e,n[r],r)},t.prototype.eachEdge=function(t,e){for(var n=this.edges,i=n.length,r=0;r=0&&n[r].node1.dataIndex>=0&&n[r].node2.dataIndex>=0&&t.call(e,n[r],r)},t.prototype.breadthFirstTraverse=function(t,e,n,i){if(e instanceof ak||(e=this._nodesMap[rk(e)]),e){for(var r="out"===n?"outEdges":"in"===n?"inEdges":"edges",o=0;o=0&&n.node2.dataIndex>=0}));for(r=0,o=i.length;r=0&&this[t][e].setItemVisual(this.dataIndex,n,i)},getVisual:function(n){return this[t][e].getItemVisual(this.dataIndex,n)},setLayout:function(n,i){this.dataIndex>=0&&this[t][e].setItemLayout(this.dataIndex,n,i)},getLayout:function(){return this[t][e].getItemLayout(this.dataIndex)},getGraphicEl:function(){return this[t][e].getItemGraphicEl(this.dataIndex)},getRawIndex:function(){return this[t][e].getRawIndex(this.dataIndex)}}}function uk(t,e,n,i,r){for(var o=new ok(i),a=0;a "+p)),u++)}var d,f=n.get("coordinateSystem");if("cartesian2d"===f||"polar"===f)d=_x(t,n);else{var g=wd.get(f),y=g&&g.dimensions||[];P(y,"value")<0&&y.concat(["value"]);var v=px(t,{coordDimensions:y,encodeDefine:n.getEncode()}).dimensions;(d=new cx(v,n)).initData(t)}var m=new cx(["value"],n);return m.initData(l,s),r&&r(d,m),ZC({mainData:d,struct:o,structAttr:"graph",datas:{node:d,edge:m},datasAttr:{node:"data",edge:"edgeData"}}),o.update(),o}R(ak,lk("hostGraph","data")),R(sk,lk("hostGraph","edgeData"));var hk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments);var n=this;function i(){return n._categoriesData}this.legendVisualProvider=new NM(i,i),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeDefaultAndTheme=function(e){t.prototype.mergeDefaultAndTheme.apply(this,arguments),Co(e,"edgeLabel",["show"])},e.prototype.getInitialData=function(t,e){var n,i=t.edges||t.links||[],r=t.data||t.nodes||[],o=this;if(r&&i){dA(n=this)&&(n.__curvenessList=[],n.__edgeMap={},fA(n));var a=uk(r,i,this,!0,(function(t,e){t.wrapMethod("getItemModel",(function(t){var e=o._categoriesModels[t.getShallow("category")];return e&&(e.parentModel=t.parentModel,t.parentModel=e),t}));var n=Cc.prototype.getModel;function i(t,e){var i=n.call(this,t,e);return i.resolveParentPath=r,i}function r(t){if(t&&("label"===t[0]||"label"===t[1])){var e=t.slice();return"label"===t[0]?e[0]="edgeLabel":"label"===t[1]&&(e[1]="edgeLabel"),e}return t}e.wrapMethod("getItemModel",(function(t){return t.resolveParentPath=r,t.getModel=i,t}))}));return E(a.edges,(function(t){!function(t,e,n,i){if(dA(n)){var r=gA(t,e,n),o=n.__edgeMap,a=o[yA(r)];o[r]&&!a?o[r].isForward=!0:a&&o[r]&&(a.isForward=!0,o[r].isForward=!1),o[r]=o[r]||[],o[r].push(i)}}(t.node1,t.node2,this,t.dataIndex)}),this),a.data}},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.getCategoriesData=function(){return this._categoriesData},e.prototype.formatTooltip=function(t,e,n){if("edge"===n){var i=this.getData(),r=this.getDataParams(t,n),o=i.graph.getEdgeByIndex(t),a=i.getName(o.node1.dataIndex),s=i.getName(o.node2.dataIndex),l=[];return null!=a&&l.push(a),null!=s&&l.push(s),og("nameValue",{name:l.join(" > "),value:r.value,noValue:null==r.value})}return vg({series:this,dataIndex:t,multipleSeries:e})},e.prototype._updateCategoriesData=function(){var t=z(this.option.categories||[],(function(t){return null!=t.value?t:A({value:0},t)})),e=new cx(["value"],this);e.initData(t),this._categoriesData=e,this._categoriesModels=e.mapArray((function(t){return e.getItemModel(t)}))},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.isAnimationEnabled=function(){return t.prototype.isAnimationEnabled.call(this)&&!("force"===this.get("layout")&&this.get(["force","layoutAnimation"]))},e.type="series.graph",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={z:2,coordinateSystem:"view",legendHoverLink:!0,layout:null,circular:{rotateLabel:!1},force:{initLayout:null,repulsion:[0,50],gravity:.1,friction:.6,edgeLength:30,layoutAnimation:!0},left:"center",top:"center",symbol:"circle",symbolSize:10,edgeSymbol:["none","none"],edgeSymbolSize:10,edgeLabel:{position:"middle",distance:5},draggable:!1,roam:!1,center:null,zoom:1,nodeScaleRatio:.6,label:{show:!1,formatter:"{b}"},itemStyle:{},lineStyle:{color:"#aaa",width:1,opacity:.5},emphasis:{scale:!0,label:{show:!0}},select:{itemStyle:{borderColor:"#212121"}}},e}(bg),ck={type:"graphRoam",event:"graphRoam",update:"none"};var pk=function(){this.angle=0,this.width=10,this.r=10,this.x=0,this.y=0},dk=function(t){function e(e){var n=t.call(this,e)||this;return n.type="pointer",n}return n(e,t),e.prototype.getDefaultShape=function(){return new pk},e.prototype.buildPath=function(t,e){var n=Math.cos,i=Math.sin,r=e.r,o=e.width,a=e.angle,s=e.x-n(a)*o*(o>=r/3?1:2),l=e.y-i(a)*o*(o>=r/3?1:2);a=e.angle-Math.PI/2,t.moveTo(s,l),t.lineTo(e.x+n(a)*o,e.y+i(a)*o),t.lineTo(e.x+n(e.angle)*r,e.y+i(e.angle)*r),t.lineTo(e.x-n(a)*o,e.y-i(a)*o),t.lineTo(s,l)},e}(ks);function fk(t,e){var n=null==t?"":t+"";return e&&(U(e)?n=e.replace("{value}",n):X(e)&&(n=e(t))),n}var gk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll();var i=t.get(["axisLine","lineStyle","color"]),r=function(t,e){var n=t.get("center"),i=e.getWidth(),r=e.getHeight(),o=Math.min(i,r);return{cx:$r(n[0],e.getWidth()),cy:$r(n[1],e.getHeight()),r:$r(t.get("radius"),o/2)}}(t,n);this._renderMain(t,e,n,i,r),this._data=t.getData()},e.prototype.dispose=function(){},e.prototype._renderMain=function(t,e,n,i,r){var o=this.group,a=t.get("clockwise"),s=-t.get("startAngle")/180*Math.PI,l=-t.get("endAngle")/180*Math.PI,u=t.getModel("axisLine"),h=u.get("roundCap")?JS:Fu,c=u.get("show"),p=u.getModel("lineStyle"),d=p.get("width"),f=[s,l];us(f,!a);for(var g=(l=f[1])-(s=f[0]),y=s,v=[],m=0;c&&m=t&&(0===e?0:i[e-1][0])Math.PI/2&&(V+=Math.PI):"tangential"===z?V=-M-Math.PI/2:j(z)&&(V=z*Math.PI/180),0===V?c.add(new Xs({style:oc(x,{text:O,x:N,y:E,verticalAlign:h<-.8?"top":h>.8?"bottom":"middle",align:u<-.4?"left":u>.4?"right":"center"},{inheritColor:R}),silent:!0})):c.add(new Xs({style:oc(x,{text:O,x:N,y:E,verticalAlign:"middle",align:"center"},{inheritColor:R}),silent:!0,originX:N,originY:E,rotation:V}))}if(m.get("show")&&k!==_){P=(P=m.get("distance"))?P+l:l;for(var B=0;B<=b;B++){u=Math.cos(M),h=Math.sin(M);var F=new Ku({shape:{x1:u*(f-P)+p,y1:h*(f-P)+d,x2:u*(f-S-P)+p,y2:h*(f-S-P)+d},silent:!0,style:D});"auto"===D.stroke&&F.setStyle({stroke:i((k+B/b)/_)}),c.add(F),M+=T}M-=T}else M+=I}},e.prototype._renderPointer=function(t,e,n,i,r,o,a,s,l){var u=this.group,h=this._data,c=this._progressEls,p=[],d=t.get(["pointer","show"]),f=t.getModel("progress"),g=f.get("show"),y=t.getData(),v=y.mapDimension("value"),m=+t.get("min"),x=+t.get("max"),_=[m,x],b=[o,a];function w(e,n){var i,o=y.getItemModel(e).getModel("pointer"),a=$r(o.get("width"),r.r),s=$r(o.get("length"),r.r),l=t.get(["pointer","icon"]),u=o.get("offsetCenter"),h=$r(u[0],r.r),c=$r(u[1],r.r),p=o.get("keepAspect");return(i=l?Xy(l,h-a/2,c-s,a,s,null,p):new dk({shape:{angle:-Math.PI/2,width:a,r:s,x:h,y:c}})).rotation=-(n+Math.PI/2),i.x=r.cx,i.y=r.cy,i}function S(t,e){var n=f.get("roundCap")?JS:Fu,i=f.get("overlap"),a=i?f.get("width"):l/y.count(),u=i?r.r-a:r.r-(t+1)*a,h=i?r.r:r.r-t*a,c=new n({shape:{startAngle:o,endAngle:e,cx:r.cx,cy:r.cy,clockwise:s,r0:u,r:h}});return i&&(c.z2=Kr(y.get(v,t),[m,x],[100,0],!0)),c}(g||d)&&(y.diff(h).add((function(e){var n=y.get(v,e);if(d){var i=w(e,o);mh(i,{rotation:-((isNaN(+n)?b[0]:Kr(n,_,b,!0))+Math.PI/2)},t),u.add(i),y.setItemGraphicEl(e,i)}if(g){var r=S(e,o),a=f.get("clip");mh(r,{shape:{endAngle:Kr(n,_,b,a)}},t),u.add(r),ol(t.seriesIndex,y.dataType,e,r),p[e]=r}})).update((function(e,n){var i=y.get(v,e);if(d){var r=h.getItemGraphicEl(n),a=r?r.rotation:o,s=w(e,a);s.rotation=a,vh(s,{rotation:-((isNaN(+i)?b[0]:Kr(i,_,b,!0))+Math.PI/2)},t),u.add(s),y.setItemGraphicEl(e,s)}if(g){var l=c[n],m=S(e,l?l.shape.endAngle:o),x=f.get("clip");vh(m,{shape:{endAngle:Kr(i,_,b,x)}},t),u.add(m),ol(t.seriesIndex,y.dataType,e,m),p[e]=m}})).execute(),y.each((function(t){var e=y.getItemModel(t),n=e.getModel("emphasis"),r=n.get("focus"),o=n.get("blurScope"),a=n.get("disabled");if(d){var s=y.getItemGraphicEl(t),l=y.getItemVisual(t,"style"),u=l.fill;if(s instanceof Ns){var h=s.style;s.useStyle(A({image:h.image,x:h.x,y:h.y,width:h.width,height:h.height},l))}else s.useStyle(l),"pointer"!==s.type&&s.setColor(u);s.setStyle(e.getModel(["pointer","itemStyle"]).getItemStyle()),"auto"===s.style.fill&&s.setStyle("fill",i(Kr(y.get(v,t),_,[0,1],!0))),s.z2EmphasisLift=0,$l(s,e),Zl(s,r,o,a)}if(g){var c=p[t];c.useStyle(y.getItemVisual(t,"style")),c.setStyle(e.getModel(["progress","itemStyle"]).getItemStyle()),c.z2EmphasisLift=0,$l(c,e),Zl(c,r,o,a)}})),this._progressEls=p)},e.prototype._renderAnchor=function(t,e){var n=t.getModel("anchor");if(n.get("show")){var i=n.get("size"),r=n.get("icon"),o=n.get("offsetCenter"),a=n.get("keepAspect"),s=Xy(r,e.cx-i/2+$r(o[0],e.r),e.cy-i/2+$r(o[1],e.r),i,i,null,a);s.z2=n.get("showAbove")?1:0,s.setStyle(n.getModel("itemStyle").getItemStyle()),this.group.add(s)}},e.prototype._renderTitleAndDetail=function(t,e,n,i,r){var o=this,a=t.getData(),s=a.mapDimension("value"),l=+t.get("min"),u=+t.get("max"),h=new Br,c=[],p=[],d=t.isAnimationEnabled(),f=t.get(["pointer","showAbove"]);a.diff(this._data).add((function(t){c[t]=new Xs({silent:!0}),p[t]=new Xs({silent:!0})})).update((function(t,e){c[t]=o._titleEls[e],p[t]=o._detailEls[e]})).execute(),a.each((function(e){var n=a.getItemModel(e),o=a.get(s,e),g=new Br,y=i(Kr(o,[l,u],[0,1],!0)),v=n.getModel("title");if(v.get("show")){var m=v.get("offsetCenter"),x=r.cx+$r(m[0],r.r),_=r.cy+$r(m[1],r.r);(D=c[e]).attr({z2:f?0:2,style:oc(v,{x:x,y:_,text:a.getName(e),align:"center",verticalAlign:"middle"},{inheritColor:y})}),g.add(D)}var b=n.getModel("detail");if(b.get("show")){var w=b.get("offsetCenter"),S=r.cx+$r(w[0],r.r),M=r.cy+$r(w[1],r.r),I=$r(b.get("width"),r.r),T=$r(b.get("height"),r.r),C=t.get(["progress","show"])?a.getItemVisual(e,"style").fill:y,D=p[e],A=b.get("formatter");D.attr({z2:f?0:2,style:oc(b,{x:S,y:M,text:fk(o,A),width:isNaN(I)?null:I,height:isNaN(T)?null:T,align:"center",verticalAlign:"middle"},{inheritColor:C})}),dc(D,{normal:b},o,(function(t){return fk(t,A)})),d&&fc(D,e,a,t,{getFormattedLabel:function(t,e,n,i,r,a){return fk(a?a.interpolatedValue:o,A)}}),g.add(D)}h.add(g)})),this.group.add(h),this._titleEls=c,this._detailEls=p},e.type="gauge",e}(Og),yk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath="itemStyle",n}return n(e,t),e.prototype.getInitialData=function(t,e){return RM(this,["value"])},e.type="series.gauge",e.defaultOption={z:2,colorBy:"data",center:["50%","50%"],legendHoverLink:!0,radius:"75%",startAngle:225,endAngle:-45,clockwise:!0,min:0,max:100,splitNumber:10,axisLine:{show:!0,roundCap:!1,lineStyle:{color:[[1,"#E6EBF8"]],width:10}},progress:{show:!1,overlap:!0,width:10,roundCap:!1,clip:!0},splitLine:{show:!0,length:10,distance:10,lineStyle:{color:"#63677A",width:3,type:"solid"}},axisTick:{show:!0,splitNumber:5,length:6,distance:10,lineStyle:{color:"#63677A",width:1,type:"solid"}},axisLabel:{show:!0,distance:15,color:"#464646",fontSize:12,rotate:0},pointer:{icon:null,offsetCenter:[0,0],show:!0,showAbove:!0,length:"60%",width:6,keepAspect:!1},anchor:{show:!1,showAbove:!1,size:6,icon:"circle",offsetCenter:[0,0],keepAspect:!1,itemStyle:{color:"#fff",borderWidth:0,borderColor:"#5470c6"}},title:{show:!0,offsetCenter:[0,"20%"],color:"#464646",fontSize:16,valueAnimation:!1},detail:{show:!0,backgroundColor:"rgba(0,0,0,0)",borderWidth:0,borderColor:"#ccc",width:100,height:null,padding:[5,10],offsetCenter:[0,"40%"],color:"#464646",fontSize:30,fontWeight:"bold",lineHeight:30,valueAnimation:!1}},e}(bg);var vk=["itemStyle","opacity"],mk=function(t){function e(e,n){var i=t.call(this)||this,r=i,o=new Zu,a=new Xs;return r.setTextContent(a),i.setTextGuideLine(o),i.updateData(e,n,!0),i}return n(e,t),e.prototype.updateData=function(t,e,n){var i=this,r=t.hostModel,o=t.getItemModel(e),a=t.getItemLayout(e),s=o.getModel("emphasis"),l=o.get(vk);l=null==l?1:l,n||Sh(i),i.useStyle(t.getItemVisual(e,"style")),i.style.lineJoin="round",n?(i.setShape({points:a.points}),i.style.opacity=0,mh(i,{style:{opacity:l}},r,e)):vh(i,{style:{opacity:l},shape:{points:a.points}},r,e),$l(i,o),this._updateLabel(t,e),Zl(this,s.get("focus"),s.get("blurScope"),s.get("disabled"))},e.prototype._updateLabel=function(t,e){var n=this,i=this.getTextGuideLine(),r=n.getTextContent(),o=t.hostModel,a=t.getItemModel(e),s=t.getItemLayout(e).label,l=t.getItemVisual(e,"style"),u=l.fill;ic(r,rc(a),{labelFetcher:t.hostModel,labelDataIndex:e,defaultOpacity:l.opacity,defaultText:t.getName(e)},{normal:{align:s.textAlign,verticalAlign:s.verticalAlign}}),n.setTextConfig({local:!0,inside:!!s.inside,insideStroke:u,outsideFill:u});var h=s.linePoints;i.setShape({points:h}),n.textGuideLineConfig={anchor:h?new De(h[0][0],h[0][1]):null},vh(r,{style:{x:s.x,y:s.y}},o,e),r.attr({rotation:s.rotation,originX:s.x,originY:s.y,z2:10}),kb(n,Lb(a),{stroke:u})},e}(Xu),xk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreLabelLineUpdate=!0,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this._data,o=this.group;i.diff(r).add((function(t){var e=new mk(i,t);i.setItemGraphicEl(t,e),o.add(e)})).update((function(t,e){var n=r.getItemGraphicEl(e);n.updateData(i,t),o.add(n),i.setItemGraphicEl(t,n)})).remove((function(e){wh(r.getItemGraphicEl(e),t,e)})).execute(),this._data=i},e.prototype.remove=function(){this.group.removeAll(),this._data=null},e.prototype.dispose=function(){},e.type="funnel",e}(Og),_k=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new NM(W(this.getData,this),W(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.getInitialData=function(t,e){return RM(this,{coordDimensions:["value"],encodeDefaulter:H(ed,this)})},e.prototype._defaultLabelLine=function(t){Co(t,"labelLine",["show"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.prototype.getDataParams=function(e){var n=this.getData(),i=t.prototype.getDataParams.call(this,e),r=n.mapDimension("value"),o=n.getSum(r);return i.percent=o?+(n.get(r,e)/o*100).toFixed(2):0,i.$vars.push("percent"),i},e.type="series.funnel",e.defaultOption={z:2,legendHoverLink:!0,colorBy:"data",left:80,top:60,right:80,bottom:60,minSize:"0%",maxSize:"100%",sort:"descending",orient:"vertical",gap:0,funnelAlign:"center",label:{show:!0,position:"outer"},labelLine:{show:!0,length:20,lineStyle:{width:1}},itemStyle:{borderColor:"#fff",borderWidth:1},emphasis:{label:{show:!0}},select:{itemStyle:{borderColor:"#212121"}}},e}(bg);function bk(t,e){t.eachSeriesByType("funnel",(function(t){var n=t.getData(),i=n.mapDimension("value"),r=t.get("sort"),o=function(t,e){return kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e),a=t.get("orient"),s=o.width,l=o.height,u=function(t,e){for(var n=t.mapDimension("value"),i=t.mapArray(n,(function(t){return t})),r=[],o="ascending"===e,a=0,s=t.count();a5)return;var i=this._model.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]);"none"!==i.behavior&&this._dispatchExpand({axisExpandWindow:i.axisExpandWindow})}this._mouseDownPoint=null},mousemove:function(t){if(!this._mouseDownPoint&&Rk(this,"mousemove")){var e=this._model,n=e.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]),i=n.behavior;"jump"===i&&this._throttledDispatchExpand.debounceNextCall(e.get("axisExpandDebounce")),this._throttledDispatchExpand("none"===i?null:{axisExpandWindow:n.axisExpandWindow,animation:"jump"===i?null:{duration:0}})}}};function Rk(t,e){var n=t._model;return n.get("axisExpandable")&&n.get("axisExpandTriggerOn")===e}var Nk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){t.prototype.init.apply(this,arguments),this.mergeOption({})},e.prototype.mergeOption=function(t){var e=this.option;t&&C(e,t,!0),this._initDimensions()},e.prototype.contains=function(t,e){var n=t.get("parallelIndex");return null!=n&&e.getComponent("parallel",n)===this},e.prototype.setAxisExpand=function(t){E(["axisExpandable","axisExpandCenter","axisExpandCount","axisExpandWidth","axisExpandWindow"],(function(e){t.hasOwnProperty(e)&&(this.option[e]=t[e])}),this)},e.prototype._initDimensions=function(){var t=this.dimensions=[],e=this.parallelAxisIndex=[];E(B(this.ecModel.queryComponents({mainType:"parallelAxis"}),(function(t){return(t.get("parallelIndex")||0)===this.componentIndex}),this),(function(n){t.push("dim"+n.get("dim")),e.push(n.componentIndex)}))},e.type="parallel",e.dependencies=["parallelAxis"],e.layoutMode="box",e.defaultOption={z:0,left:80,top:60,right:80,bottom:60,layout:"horizontal",axisExpandable:!1,axisExpandCenter:null,axisExpandCount:0,axisExpandWidth:50,axisExpandRate:17,axisExpandDebounce:50,axisExpandSlideTriggerArea:[-.15,.05,.4],axisExpandTriggerOn:"click",parallelAxisDefault:null},e}(zp),Ek=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.type=r||"value",a.axisIndex=o,a}return n(e,t),e.prototype.isHorizontal=function(){return"horizontal"!==this.coordinateSystem.getModel().get("layout")},e}(ab);function zk(t,e,n,i,r,o){t=t||0;var a=n[1]-n[0];if(null!=r&&(r=Bk(r,[0,a])),null!=o&&(o=Math.max(o,null!=r?r:0)),"all"===i){var s=Math.abs(e[1]-e[0]);s=Bk(s,[0,a]),r=o=Bk(s,[r,o]),i=0}e[0]=Bk(e[0],n),e[1]=Bk(e[1],n);var l=Vk(e,i);e[i]+=t;var u,h=r||0,c=n.slice();return l.sign<0?c[0]+=h:c[1]-=h,e[i]=Bk(e[i],c),u=Vk(e,i),null!=r&&(u.sign!==l.sign||u.spano&&(e[1-i]=e[i]+u.sign*o),e}function Vk(t,e){var n=t[e]-t[1-e];return{span:Math.abs(n),sign:n>0?-1:n<0?1:e?-1:1}}function Bk(t,e){return Math.min(null!=e[1]?e[1]:1/0,Math.max(null!=e[0]?e[0]:-1/0,t))}var Fk=E,Gk=Math.min,Wk=Math.max,Hk=Math.floor,Yk=Math.ceil,Xk=Jr,Uk=Math.PI,Zk=function(){function t(t,e,n){this.type="parallel",this._axesMap=yt(),this._axesLayout={},this.dimensions=t.dimensions,this._model=t,this._init(t,e,n)}return t.prototype._init=function(t,e,n){var i=t.dimensions,r=t.parallelAxisIndex;Fk(i,(function(t,n){var i=r[n],o=e.getComponent("parallelAxis",i),a=this._axesMap.set(t,new Ek(t,b_(o),[0,0],o.get("type"),i)),s="category"===a.type;a.onBand=s&&o.get("boundaryGap"),a.inverse=o.get("inverse"),o.axis=a,a.model=o,a.coordinateSystem=o.coordinateSystem=this}),this)},t.prototype.update=function(t,e){this._updateAxesFromSeries(this._model,t)},t.prototype.containPoint=function(t){var e=this._makeLayoutInfo(),n=e.axisBase,i=e.layoutBase,r=e.pixelDimIndex,o=t[1-r],a=t[r];return o>=n&&o<=n+e.axisLength&&a>=i&&a<=i+e.layoutLength},t.prototype.getModel=function(){return this._model},t.prototype._updateAxesFromSeries=function(t,e){e.eachSeries((function(n){if(t.contains(n,e)){var i=n.getData();Fk(this.dimensions,(function(t){var e=this._axesMap.get(t);e.scale.unionExtentFromData(i,i.mapDimension(t)),__(e.scale,e.model)}),this)}}),this)},t.prototype.resize=function(t,e){this._rect=kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),this._layoutAxes()},t.prototype.getRect=function(){return this._rect},t.prototype._makeLayoutInfo=function(){var t,e=this._model,n=this._rect,i=["x","y"],r=["width","height"],o=e.get("layout"),a="horizontal"===o?0:1,s=n[r[a]],l=[0,s],u=this.dimensions.length,h=jk(e.get("axisExpandWidth"),l),c=jk(e.get("axisExpandCount")||0,[0,u]),p=e.get("axisExpandable")&&u>3&&u>c&&c>1&&h>0&&s>0,d=e.get("axisExpandWindow");d?(t=jk(d[1]-d[0],l),d[1]=d[0]+t):(t=jk(h*(c-1),l),(d=[h*(e.get("axisExpandCenter")||Hk(u/2))-t/2])[1]=d[0]+t);var f=(s-t)/(u-c);f<3&&(f=0);var g=[Hk(Xk(d[0]/h,1))+1,Yk(Xk(d[1]/h,1))-1],y=f/h*d[0];return{layout:o,pixelDimIndex:a,layoutBase:n[i[a]],layoutLength:s,axisBase:n[i[1-a]],axisLength:n[r[1-a]],axisExpandable:p,axisExpandWidth:h,axisCollapseWidth:f,axisExpandWindow:d,axisCount:u,winInnerIndices:g,axisExpandWindow0Pos:y}},t.prototype._layoutAxes=function(){var t=this._rect,e=this._axesMap,n=this.dimensions,i=this._makeLayoutInfo(),r=i.layout;e.each((function(t){var e=[0,i.axisLength],n=t.inverse?1:0;t.setExtent(e[n],e[1-n])})),Fk(n,(function(e,n){var o=(i.axisExpandable?Kk:qk)(n,i),a={horizontal:{x:o.position,y:i.axisLength},vertical:{x:0,y:o.position}},s={horizontal:Uk/2,vertical:0},l=[a[r].x+t.x,a[r].y+t.y],u=s[r],h=[1,0,0,1,0,0];Se(h,h,u),we(h,h,l),this._axesLayout[e]={position:l,rotation:u,transform:h,axisNameAvailableWidth:o.axisNameAvailableWidth,axisLabelShow:o.axisLabelShow,nameTruncateMaxWidth:o.nameTruncateMaxWidth,tickDirection:1,labelDirection:1}}),this)},t.prototype.getAxis=function(t){return this._axesMap.get(t)},t.prototype.dataToPoint=function(t,e){return this.axisCoordToPoint(this._axesMap.get(e).dataToCoord(t),e)},t.prototype.eachActiveState=function(t,e,n,i){null==n&&(n=0),null==i&&(i=t.count());var r=this._axesMap,o=this.dimensions,a=[],s=[];E(o,(function(e){a.push(t.mapDimension(e)),s.push(r.get(e).model)}));for(var l=this.hasAxisBrushed(),u=n;ur*(1-h[0])?(l="jump",a=s-r*(1-h[2])):(a=s-r*h[1])>=0&&(a=s-r*(1-h[1]))<=0&&(a=0),(a*=e.axisExpandWidth/u)?zk(a,i,o,"all"):l="none";else{var p=i[1]-i[0];(i=[Wk(0,o[1]*s/p-p/2)])[1]=Gk(o[1],i[0]+p),i[0]=i[1]-p}return{axisExpandWindow:i,behavior:l}},t}();function jk(t,e){return Gk(Wk(t,e[0]),e[1])}function qk(t,e){var n=e.layoutLength/(e.axisCount-1);return{position:n*t,axisNameAvailableWidth:n,axisLabelShow:!0}}function Kk(t,e){var n,i,r=e.layoutLength,o=e.axisExpandWidth,a=e.axisCount,s=e.axisCollapseWidth,l=e.winInnerIndices,u=s,h=!1;return t=0;n--)Qr(e[n])},e.prototype.getActiveState=function(t){var e=this.activeIntervals;if(!e.length)return"normal";if(null==t||isNaN(+t))return"inactive";if(1===e.length){var n=e[0];if(n[0]<=t&&t<=n[1])return"active"}else for(var i=0,r=e.length;i6}(t)||o){if(a&&!o){"single"===s.brushMode&&vL(t);var l=T(s);l.brushType=RL(l.brushType,a),l.panelId=a===Qk?null:a.panelId,o=t._creatingCover=uL(t,l),t._covers.push(o)}if(o){var u=zL[RL(t._brushType,a)];o.__brushOption.range=u.getCreatingRange(kL(t,o,t._track)),i&&(hL(t,o),u.updateCommon(t,o)),cL(t,o),r={isEnd:i}}}else i&&"single"===s.brushMode&&s.removeOnClick&&gL(t,e,n)&&vL(t)&&(r={isEnd:i,removeOnClick:!0});return r}function RL(t,e){return"auto"===t?e.defaultBrushType:t}var NL={mousedown:function(t){if(this._dragging)EL(this,t);else if(!t.target||!t.target.draggable){LL(t);var e=this.group.transformCoordToLocal(t.offsetX,t.offsetY);this._creatingCover=null,(this._creatingPanel=gL(this,t,e))&&(this._dragging=!0,this._track=[e.slice()])}},mousemove:function(t){var e=t.offsetX,n=t.offsetY,i=this.group.transformCoordToLocal(e,n);if(function(t,e,n){if(t._brushType&&!function(t,e,n){var i=t._zr;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}(t,e.offsetX,e.offsetY)){var i=t._zr,r=t._covers,o=gL(t,e,n);if(!t._dragging)for(var a=0;a=0&&(o[r[a].depth]=new Cc(r[a],this,e));var s=uk(i,n,this,!0,(function(t,e){t.wrapMethod("getItemModel",(function(t,e){var n=t.parentModel,i=n.getData().getItemLayout(e);if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t})),e.wrapMethod("getItemModel",(function(t,e){var n=t.parentModel,i=n.getGraph().getEdgeByIndex(e).node1.getLayout();if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t}))}));return s.data},e.prototype.setNodePosition=function(t,e){var n=(this.option.data||this.option.nodes)[t];n.localX=e[0],n.localY=e[1]},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.formatTooltip=function(t,e,n){function i(t){return isNaN(t)||null==t}if("edge"===n){var r=this.getDataParams(t,n),o=r.data,a=r.value;return og("nameValue",{name:o.source+" -- "+o.target,value:a,noValue:i(a)})}var s=this.getGraph().getNodeByIndex(t).getLayout().value,l=this.getDataParams(t,n).data.name;return og("nameValue",{name:null!=l?l+"":null,value:s,noValue:i(s)})},e.prototype.optionUpdated=function(){},e.prototype.getDataParams=function(e,n){var i=t.prototype.getDataParams.call(this,e,n);if(null==i.value&&"node"===n){var r=this.getGraph().getNodeByIndex(e).getLayout().value;i.value=r}return i},e.type="series.sankey",e.defaultOption={z:2,coordinateSystem:"view",left:"5%",top:"5%",right:"20%",bottom:"5%",orient:"horizontal",nodeWidth:20,nodeGap:8,draggable:!0,layoutIterations:32,label:{show:!0,position:"right",fontSize:12},edgeLabel:{show:!1,fontSize:12},levels:[],nodeAlign:"justify",lineStyle:{color:"#314656",opacity:.2,curveness:.5},emphasis:{label:{show:!0},lineStyle:{opacity:.5}},select:{itemStyle:{borderColor:"#212121"}},animationEasing:"linear",animationDuration:1e3},e}(bg);function QL(t,e){t.eachSeriesByType("sankey",(function(t){var n=t.get("nodeWidth"),i=t.get("nodeGap"),r=function(t,e){return kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=r;var o=r.width,a=r.height,s=t.getGraph(),l=s.nodes,u=s.edges;!function(t){E(t,(function(t){var e=uP(t.outEdges,lP),n=uP(t.inEdges,lP),i=t.getValue()||0,r=Math.max(e,n,i);t.setLayout({value:r},!0)}))}(l),function(t,e,n,i,r,o,a,s,l){(function(t,e,n,i,r,o,a){for(var s=[],l=[],u=[],h=[],c=0,p=0;p=0;v&&y.depth>d&&(d=y.depth),g.setLayout({depth:v?y.depth:c},!0),"vertical"===o?g.setLayout({dy:n},!0):g.setLayout({dx:n},!0);for(var m=0;mc-1?d:c-1;a&&"left"!==a&&function(t,e,n,i){if("right"===e){for(var r=[],o=t,a=0;o.length;){for(var s=0;s0;o--)nP(s,l*=.99,a),eP(s,r,n,i,a),hP(s,l,a),eP(s,r,n,i,a)}(t,e,o,r,i,a,s),function(t,e){var n="vertical"===e?"x":"y";E(t,(function(t){t.outEdges.sort((function(t,e){return t.node2.getLayout()[n]-e.node2.getLayout()[n]})),t.inEdges.sort((function(t,e){return t.node1.getLayout()[n]-e.node1.getLayout()[n]}))})),E(t,(function(t){var e=0,n=0;E(t.outEdges,(function(t){t.setLayout({sy:e},!0),e+=t.getLayout().dy})),E(t.inEdges,(function(t){t.setLayout({ty:n},!0),n+=t.getLayout().dy}))}))}(t,s)}(l,u,n,i,o,a,0!==B(l,(function(t){return 0===t.getLayout().value})).length?0:t.get("layoutIterations"),t.get("orient"),t.get("nodeAlign"))}))}function tP(t){var e=t.hostGraph.data.getRawDataItem(t.dataIndex);return null!=e.depth&&e.depth>=0}function eP(t,e,n,i,r){var o="vertical"===r?"x":"y";E(t,(function(t){var a,s,l;t.sort((function(t,e){return t.getLayout()[o]-e.getLayout()[o]}));for(var u=0,h=t.length,c="vertical"===r?"dx":"dy",p=0;p0&&(a=s.getLayout()[o]+l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]+s.getLayout()[c]+e;if((l=u-e-("vertical"===r?i:n))>0){a=s.getLayout()[o]-l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0),u=a;for(p=h-2;p>=0;--p)(l=(s=t[p]).getLayout()[o]+s.getLayout()[c]+e-u)>0&&(a=s.getLayout()[o]-l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]}}))}function nP(t,e,n){E(t.slice().reverse(),(function(t){E(t,(function(t){if(t.outEdges.length){var i=uP(t.outEdges,iP,n)/uP(t.outEdges,lP);if(isNaN(i)){var r=t.outEdges.length;i=r?uP(t.outEdges,rP,n)/r:0}if("vertical"===n){var o=t.getLayout().x+(i-sP(t,n))*e;t.setLayout({x:o},!0)}else{var a=t.getLayout().y+(i-sP(t,n))*e;t.setLayout({y:a},!0)}}}))}))}function iP(t,e){return sP(t.node2,e)*t.getValue()}function rP(t,e){return sP(t.node2,e)}function oP(t,e){return sP(t.node1,e)*t.getValue()}function aP(t,e){return sP(t.node1,e)}function sP(t,e){return"vertical"===e?t.getLayout().x+t.getLayout().dx/2:t.getLayout().y+t.getLayout().dy/2}function lP(t){return t.getValue()}function uP(t,e,n){for(var i=0,r=t.length,o=-1;++oo&&(o=e)})),E(n,(function(e){var n=new kD({type:"color",mappingMethod:"linear",dataExtent:[r,o],visual:t.get("color")}).mapValueToVisual(e.getLayout().value),i=e.getModel().get(["itemStyle","color"]);null!=i?(e.setVisual("color",i),e.setVisual("style",{fill:i})):(e.setVisual("color",n),e.setVisual("style",{fill:n}))}))}i.length&&E(i,(function(t){var e=t.getModel().get("lineStyle");t.setVisual("style",e)}))}))}var pP=function(){function t(){}return t.prototype._hasEncodeRule=function(t){var e=this.getEncode();return e&&null!=e.get(t)},t.prototype.getInitialData=function(t,e){var n,i,r=e.getComponent("xAxis",this.get("xAxisIndex")),o=e.getComponent("yAxis",this.get("yAxisIndex")),a=r.get("type"),s=o.get("type");"category"===a?(t.layout="horizontal",n=r.getOrdinalMeta(),i=!this._hasEncodeRule("x")):"category"===s?(t.layout="vertical",n=o.getOrdinalMeta(),i=!this._hasEncodeRule("y")):t.layout=t.layout||"horizontal";var l=["x","y"],u="horizontal"===t.layout?0:1,h=this._baseAxisDim=l[u],c=l[1-u],p=[r,o],d=p[u].get("type"),f=p[1-u].get("type"),g=t.data;if(g&&i){var y=[];E(g,(function(t,e){var n;Y(t)?(n=t.slice(),t.unshift(e)):Y(t.value)?((n=A({},t)).value=n.value.slice(),t.value.unshift(e)):n=t,y.push(n)})),t.data=y}var v=this.defaultValueDimensions,m=[{name:h,type:Ym(d),ordinalMeta:n,otherDims:{tooltip:!1,itemName:0},dimsDef:["base"]},{name:c,type:Ym(f),dimsDef:v.slice()}];return RM(this,{coordDimensions:m,dimensionsCount:v.length+1,encodeDefaulter:H(td,m,this)})},t.prototype.getBaseAxis=function(){var t=this._baseAxisDim;return this.ecModel.getComponent(t+"Axis",this.get(t+"AxisIndex")).axis},t}(),dP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.defaultValueDimensions=[{name:"min",defaultTooltip:!0},{name:"Q1",defaultTooltip:!0},{name:"median",defaultTooltip:!0},{name:"Q3",defaultTooltip:!0},{name:"max",defaultTooltip:!0}],n.visualDrawType="stroke",n}return n(e,t),e.type="series.boxplot",e.dependencies=["xAxis","yAxis","grid"],e.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,layout:null,boxWidth:[7,50],itemStyle:{color:"#fff",borderWidth:1},emphasis:{scale:!0,itemStyle:{borderWidth:2,shadowBlur:5,shadowOffsetX:1,shadowOffsetY:1,shadowColor:"rgba(0,0,0,0.2)"}},animationDuration:800},e}(bg);R(dP,pP,!0);var fP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this.group,o=this._data;this._data||r.removeAll();var a="horizontal"===t.get("layout")?1:0;i.diff(o).add((function(t){if(i.hasValue(t)){var e=vP(i.getItemLayout(t),i,t,a,!0);i.setItemGraphicEl(t,e),r.add(e)}})).update((function(t,e){var n=o.getItemGraphicEl(e);if(i.hasValue(t)){var s=i.getItemLayout(t);n?(Sh(n),mP(s,n,i,t)):n=vP(s,i,t,a),r.add(n),i.setItemGraphicEl(t,n)}else r.remove(n)})).remove((function(t){var e=o.getItemGraphicEl(t);e&&r.remove(e)})).execute(),this._data=i},e.prototype.remove=function(t){var e=this.group,n=this._data;this._data=null,n&&n.eachItemGraphicEl((function(t){t&&e.remove(t)}))},e.type="boxplot",e}(Og),gP=function(){},yP=function(t){function e(e){var n=t.call(this,e)||this;return n.type="boxplotBoxPath",n}return n(e,t),e.prototype.getDefaultShape=function(){return new gP},e.prototype.buildPath=function(t,e){var n=e.points,i=0;for(t.moveTo(n[i][0],n[i][1]),i++;i<4;i++)t.lineTo(n[i][0],n[i][1]);for(t.closePath();ig){var _=[v,x];i.push(_)}}}return{boxData:n,outliers:i}}(e.getRawData(),t.config);return[{dimensions:["ItemName","Low","Q1","Q2","Q3","High"],data:i.boxData},{data:i.outliers}]}};var SP=["itemStyle","borderColor"],MP=["itemStyle","borderColor0"],IP=["itemStyle","borderColorDoji"],TP=["itemStyle","color"],CP=["itemStyle","color0"];function DP(t,e){return e.get(t>0?TP:CP)}function AP(t,e){return e.get(0===t?IP:t>0?SP:MP)}var kP={seriesType:"candlestick",plan:kg(),performRawSeries:!0,reset:function(t,e){if(!e.isSeriesFiltered(t))return!t.pipelineContext.large&&{progress:function(t,e){for(var n;null!=(n=t.next());){var i=e.getItemModel(n),r=e.getItemLayout(n).sign,o=i.getItemStyle();o.fill=DP(r,i),o.stroke=AP(r,i)||o.fill,A(e.ensureUniqueItemVisual(n,"style"),o)}}}}},LP=["color","borderColor"],PP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeClipPath(),this._progressiveEls=null,this._updateDrawMode(t),this._isLargeDraw?this._renderLarge(t):this._renderNormal(t)},e.prototype.incrementalPrepareRender=function(t,e,n){this._clear(),this._updateDrawMode(t)},e.prototype.incrementalRender=function(t,e,n,i){this._progressiveEls=[],this._isLargeDraw?this._incrementalRenderLarge(t,e):this._incrementalRenderNormal(t,e)},e.prototype.eachRendered=function(t){Jh(this._progressiveEls||this.group,t)},e.prototype._updateDrawMode=function(t){var e=t.pipelineContext.large;null!=this._isLargeDraw&&e===this._isLargeDraw||(this._isLargeDraw=e,this._clear())},e.prototype._renderNormal=function(t){var e=t.getData(),n=this._data,i=this.group,r=e.getLayout("isSimpleBox"),o=t.get("clip",!0),a=t.coordinateSystem,s=a.getArea&&a.getArea();this._data||i.removeAll(),e.diff(n).add((function(n){if(e.hasValue(n)){var a=e.getItemLayout(n);if(o&&EP(s,a))return;var l=NP(a,n,!0);mh(l,{shape:{points:a.ends}},t,n),zP(l,e,n,r),i.add(l),e.setItemGraphicEl(n,l)}})).update((function(a,l){var u=n.getItemGraphicEl(l);if(e.hasValue(a)){var h=e.getItemLayout(a);o&&EP(s,h)?i.remove(u):(u?(vh(u,{shape:{points:h.ends}},t,a),Sh(u)):u=NP(h),zP(u,e,a,r),i.add(u),e.setItemGraphicEl(a,u))}else i.remove(u)})).remove((function(t){var e=n.getItemGraphicEl(t);e&&i.remove(e)})).execute(),this._data=e},e.prototype._renderLarge=function(t){this._clear(),GP(t,this.group);var e=t.get("clip",!0)?PS(t.coordinateSystem,!1,t):null;e?this.group.setClipPath(e):this.group.removeClipPath()},e.prototype._incrementalRenderNormal=function(t,e){for(var n,i=e.getData(),r=i.getLayout("isSimpleBox");null!=(n=t.next());){var o=NP(i.getItemLayout(n));zP(o,i,n,r),o.incremental=!0,this.group.add(o),this._progressiveEls.push(o)}},e.prototype._incrementalRenderLarge=function(t,e){GP(e,this.group,this._progressiveEls,!0)},e.prototype.remove=function(t){this._clear()},e.prototype._clear=function(){this.group.removeAll(),this._data=null},e.type="candlestick",e}(Og),OP=function(){},RP=function(t){function e(e){var n=t.call(this,e)||this;return n.type="normalCandlestickBox",n}return n(e,t),e.prototype.getDefaultShape=function(){return new OP},e.prototype.buildPath=function(t,e){var n=e.points;this.__simpleBox?(t.moveTo(n[4][0],n[4][1]),t.lineTo(n[6][0],n[6][1])):(t.moveTo(n[0][0],n[0][1]),t.lineTo(n[1][0],n[1][1]),t.lineTo(n[2][0],n[2][1]),t.lineTo(n[3][0],n[3][1]),t.closePath(),t.moveTo(n[4][0],n[4][1]),t.lineTo(n[5][0],n[5][1]),t.moveTo(n[6][0],n[6][1]),t.lineTo(n[7][0],n[7][1]))},e}(ks);function NP(t,e,n){var i=t.ends;return new RP({shape:{points:n?VP(i,t):i},z2:100})}function EP(t,e){for(var n=!0,i=0;id?x[1]:m[1],ends:w,brushRect:T(f,g,c)})}function M(t,n){var i=[];return i[0]=n,i[1]=t,isNaN(n)||isNaN(t)?[NaN,NaN]:e.dataToPoint(i)}function I(t,e,n){var r=e.slice(),o=e.slice();r[0]=Vh(r[0]+i/2,1,!1),o[0]=Vh(o[0]-i/2,1,!0),n?t.push(r,o):t.push(o,r)}function T(t,e,n){var r=M(t,n),o=M(e,n);return r[0]-=i/2,o[0]-=i/2,{x:r[0],y:r[1],width:i,height:o[1]-r[1]}}function C(t){return t[0]=Vh(t[0],1),t}}}}};function UP(t,e,n,i,r,o){return n>i?-1:n0?t.get(r,e-1)<=i?1:-1:1}function ZP(t,e){var n=e.rippleEffectColor||e.color;t.eachChild((function(t){t.attr({z:e.z,zlevel:e.zlevel,style:{stroke:"stroke"===e.brushType?n:null,fill:"fill"===e.brushType?n:null}})}))}var jP=function(t){function e(e,n){var i=t.call(this)||this,r=new fS(e,n),o=new Br;return i.add(r),i.add(o),i.updateData(e,n),i}return n(e,t),e.prototype.stopEffectAnimation=function(){this.childAt(1).removeAll()},e.prototype.startEffectAnimation=function(t){for(var e=t.symbolType,n=t.color,i=t.rippleNumber,r=this.childAt(1),o=0;o0&&(o=this._getLineLength(i)/l*1e3),o!==this._period||a!==this._loop||s!==this._roundTrip){i.stopAnimation();var h=void 0;h=X(u)?u(n):u,i.__t>0&&(h=-o*i.__t),this._animateSymbol(i,o,h,a,s)}this._period=o,this._loop=a,this._roundTrip=s}},e.prototype._animateSymbol=function(t,e,n,i,r){if(e>0){t.__t=0;var o=this,a=t.animate("",i).when(r?2*e:e,{__t:r?2:1}).delay(n).during((function(){o._updateSymbolPosition(t)}));i||a.done((function(){o.remove(t)})),a.start()}},e.prototype._getLineLength=function(t){return Vt(t.__p1,t.__cp1)+Vt(t.__cp1,t.__p2)},e.prototype._updateAnimationPoints=function(t,e){t.__p1=e[0],t.__p2=e[1],t.__cp1=e[2]||[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]},e.prototype.updateData=function(t,e,n){this.childAt(0).updateData(t,e,n),this._updateEffectSymbol(t,e)},e.prototype._updateSymbolPosition=function(t){var e=t.__p1,n=t.__p2,i=t.__cp1,r=t.__t<1?t.__t:2-t.__t,o=[t.x,t.y],a=o.slice(),s=In,l=Tn;o[0]=s(e[0],i[0],n[0],r),o[1]=s(e[1],i[1],n[1],r);var u=t.__t<1?l(e[0],i[0],n[0],r):l(n[0],i[0],e[0],1-r),h=t.__t<1?l(e[1],i[1],n[1],r):l(n[1],i[1],e[1],1-r);t.rotation=-Math.atan2(h,u)-Math.PI/2,"line"!==this._symbolType&&"rect"!==this._symbolType&&"roundRect"!==this._symbolType||(void 0!==t.__lastT&&t.__lastT=0&&!(i[o]<=e);o--);o=Math.min(o,r-2)}else{for(o=a;oe);o++);o=Math.min(o-1,r-2)}var s=(e-i[o])/(i[o+1]-i[o]),l=n[o],u=n[o+1];t.x=l[0]*(1-s)+s*u[0],t.y=l[1]*(1-s)+s*u[1];var h=t.__t<1?u[0]-l[0]:l[0]-u[0],c=t.__t<1?u[1]-l[1]:l[1]-u[1];t.rotation=-Math.atan2(c,h)-Math.PI/2,this._lastFrame=o,this._lastFramePercent=e,t.ignore=!1}},e}($P),tO=function(){this.polyline=!1,this.curveness=0,this.segs=[]},eO=function(t){function e(e){var n=t.call(this,e)||this;return n._off=0,n.hoverDataIdx=-1,n}return n(e,t),e.prototype.reset=function(){this.notClear=!1,this._off=0},e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new tO},e.prototype.buildPath=function(t,e){var n,i=e.segs,r=e.curveness;if(e.polyline)for(n=this._off;n0){t.moveTo(i[n++],i[n++]);for(var a=1;a0){var c=(s+u)/2-(l-h)*r,p=(l+h)/2-(u-s)*r;t.quadraticCurveTo(c,p,u,h)}else t.lineTo(u,h)}this.incremental&&(this._off=n,this.notClear=!0)},e.prototype.findDataIndex=function(t,e){var n=this.shape,i=n.segs,r=n.curveness,o=this.style.lineWidth;if(n.polyline)for(var a=0,s=0;s0)for(var u=i[s++],h=i[s++],c=1;c0){if(ds(u,h,(u+p)/2-(h-d)*r,(h+d)/2-(p-u)*r,p,d,o,t,e))return a}else if(cs(u,h,p,d,o,t,e))return a;a++}return-1},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect();return t=n[0],e=n[1],i.contain(t,e)?(this.hoverDataIdx=this.findDataIndex(t,e))>=0:(this.hoverDataIdx=-1,!1)},e.prototype.getBoundingRect=function(){var t=this._rect;if(!t){for(var e=this.shape.segs,n=1/0,i=1/0,r=-1/0,o=-1/0,a=0;a0&&(o.dataIndex=n+t.__startIndex)}))},t.prototype._clear=function(){this._newAdded=[],this.group.removeAll()},t}(),iO={seriesType:"lines",plan:kg(),reset:function(t){var e=t.coordinateSystem;if(e){var n=t.get("polyline"),i=t.pipelineContext.large;return{progress:function(r,o){var a=[];if(i){var s=void 0,l=r.end-r.start;if(n){for(var u=0,h=r.start;h0&&(l||s.configLayer(o,{motionBlur:!0,lastFrameAlpha:Math.max(Math.min(a/10+.9,1),0)})),r.updateData(i);var u=t.get("clip",!0)&&PS(t.coordinateSystem,!1,t);u?this.group.setClipPath(u):this.group.removeClipPath(),this._lastZlevel=o,this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateLineDraw(i,t).incrementalPrepareUpdate(i),this._clearLayer(n),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._lineDraw.incrementalUpdate(t,e.getData()),this._finished=t.end===e.getData().count()},e.prototype.eachRendered=function(t){this._lineDraw&&this._lineDraw.eachRendered(t)},e.prototype.updateTransform=function(t,e,n){var i=t.getData(),r=t.pipelineContext;if(!this._finished||r.large||r.progressiveRender)return{update:!0};var o=iO.reset(t,e,n);o.progress&&o.progress({start:0,end:i.count(),count:i.count()},i),this._lineDraw.updateLayout(),this._clearLayer(n)},e.prototype._updateLineDraw=function(t,e){var n=this._lineDraw,i=this._showEffect(e),r=!!e.get("polyline"),o=e.pipelineContext.large;return n&&i===this._hasEffet&&r===this._isPolyline&&o===this._isLargeDraw||(n&&n.remove(),n=this._lineDraw=o?new nO:new YA(r?i?QP:JP:i?$P:HA),this._hasEffet=i,this._isPolyline=r,this._isLargeDraw=o),this.group.add(n.group),n},e.prototype._showEffect=function(t){return!!t.get(["effect","show"])},e.prototype._clearLayer=function(t){var e=t.getZr();"svg"===e.painter.getType()||null==this._lastZlevel||e.painter.getLayer(this._lastZlevel).clear(!0)},e.prototype.remove=function(t,e){this._lineDraw&&this._lineDraw.remove(),this._lineDraw=null,this._clearLayer(e)},e.prototype.dispose=function(t,e){this.remove(t,e)},e.type="lines",e}(Og),oO="undefined"==typeof Uint32Array?Array:Uint32Array,aO="undefined"==typeof Float64Array?Array:Float64Array;function sO(t){var e=t.data;e&&e[0]&&e[0][0]&&e[0][0].coord&&(t.data=z(e,(function(t){var e={coords:[t[0].coord,t[1].coord]};return t[0].name&&(e.fromName=t[0].name),t[1].name&&(e.toName=t[1].name),D([e,t[0],t[1]])})))}var lO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath="lineStyle",n.visualDrawType="stroke",n}return n(e,t),e.prototype.init=function(e){e.data=e.data||[],sO(e);var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count)),t.prototype.init.apply(this,arguments)},e.prototype.mergeOption=function(e){if(sO(e),e.data){var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count))}t.prototype.mergeOption.apply(this,arguments)},e.prototype.appendData=function(t){var e=this._processFlatCoordsArray(t.data);e.flatCoords&&(this._flatCoords?(this._flatCoords=vt(this._flatCoords,e.flatCoords),this._flatCoordsOffset=vt(this._flatCoordsOffset,e.flatCoordsOffset)):(this._flatCoords=e.flatCoords,this._flatCoordsOffset=e.flatCoordsOffset),t.data=new Float32Array(e.count)),this.getRawData().appendData(t.data)},e.prototype._getCoordsFromItemModel=function(t){var e=this.getData().getItemModel(t),n=e.option instanceof Array?e.option:e.getShallow("coords");return n},e.prototype.getLineCoordsCount=function(t){return this._flatCoordsOffset?this._flatCoordsOffset[2*t+1]:this._getCoordsFromItemModel(t).length},e.prototype.getLineCoords=function(t,e){if(this._flatCoordsOffset){for(var n=this._flatCoordsOffset[2*t],i=this._flatCoordsOffset[2*t+1],r=0;r ")})},e.prototype.preventIncremental=function(){return!!this.get(["effect","show"])},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?1e4:this.get("progressive"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?2e4:this.get("progressiveThreshold"):t},e.prototype.getZLevelKey=function(){var t=this.getModel("effect"),e=t.get("trailLength");return this.getData().count()>this.getProgressiveThreshold()?this.id:t.get("show")&&e>0?e+"":""},e.type="series.lines",e.dependencies=["grid","polar","geo","calendar"],e.defaultOption={coordinateSystem:"geo",z:2,legendHoverLink:!0,xAxisIndex:0,yAxisIndex:0,symbol:["none","none"],symbolSize:[10,10],geoIndex:0,effect:{show:!1,period:4,constantSpeed:0,symbol:"circle",symbolSize:3,loop:!0,trailLength:.2},large:!1,largeThreshold:2e3,polyline:!1,clip:!0,label:{show:!1,position:"end"},lineStyle:{opacity:.5}},e}(bg);function uO(t){return t instanceof Array||(t=[t,t]),t}var hO={seriesType:"lines",reset:function(t){var e=uO(t.get("symbol")),n=uO(t.get("symbolSize")),i=t.getData();return i.setVisual("fromSymbol",e&&e[0]),i.setVisual("toSymbol",e&&e[1]),i.setVisual("fromSymbolSize",n&&n[0]),i.setVisual("toSymbolSize",n&&n[1]),{dataEach:i.hasItemOption?function(t,e){var n=t.getItemModel(e),i=uO(n.getShallow("symbol",!0)),r=uO(n.getShallow("symbolSize",!0));i[0]&&t.setItemVisual(e,"fromSymbol",i[0]),i[1]&&t.setItemVisual(e,"toSymbol",i[1]),r[0]&&t.setItemVisual(e,"fromSymbolSize",r[0]),r[1]&&t.setItemVisual(e,"toSymbolSize",r[1])}:null}}};var cO=function(){function t(){this.blurSize=30,this.pointSize=20,this.maxOpacity=1,this.minOpacity=0,this._gradientPixels={inRange:null,outOfRange:null};var t=h.createCanvas();this.canvas=t}return t.prototype.update=function(t,e,n,i,r,o){var a=this._getBrush(),s=this._getGradient(r,"inRange"),l=this._getGradient(r,"outOfRange"),u=this.pointSize+this.blurSize,h=this.canvas,c=h.getContext("2d"),p=t.length;h.width=e,h.height=n;for(var d=0;d0){var I=o(v)?s:l;v>0&&(v=v*S+w),x[_++]=I[M],x[_++]=I[M+1],x[_++]=I[M+2],x[_++]=I[M+3]*v*256}else _+=4}return c.putImageData(m,0,0),h},t.prototype._getBrush=function(){var t=this._brushCanvas||(this._brushCanvas=h.createCanvas()),e=this.pointSize+this.blurSize,n=2*e;t.width=n,t.height=n;var i=t.getContext("2d");return i.clearRect(0,0,n,n),i.shadowOffsetX=n,i.shadowBlur=this.blurSize,i.shadowColor="#000",i.beginPath(),i.arc(-e,e,this.pointSize,0,2*Math.PI,!0),i.closePath(),i.fill(),t},t.prototype._getGradient=function(t,e){for(var n=this._gradientPixels,i=n[e]||(n[e]=new Uint8ClampedArray(1024)),r=[0,0,0,0],o=0,a=0;a<256;a++)t[e](a/255,!0,r),i[o++]=r[0],i[o++]=r[1],i[o++]=r[2],i[o++]=r[3];return i},t}();function pO(t){var e=t.dimensions;return"lng"===e[0]&&"lat"===e[1]}var dO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i;e.eachComponent("visualMap",(function(e){e.eachTargetSeries((function(n){n===t&&(i=e)}))})),this._progressiveEls=null,this.group.removeAll();var r=t.coordinateSystem;"cartesian2d"===r.type||"calendar"===r.type?this._renderOnCartesianAndCalendar(t,n,0,t.getData().count()):pO(r)&&this._renderOnGeo(r,t,i,n)},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll()},e.prototype.incrementalRender=function(t,e,n,i){var r=e.coordinateSystem;r&&(pO(r)?this.render(e,n,i):(this._progressiveEls=[],this._renderOnCartesianAndCalendar(e,i,t.start,t.end,!0)))},e.prototype.eachRendered=function(t){Jh(this._progressiveEls||this.group,t)},e.prototype._renderOnCartesianAndCalendar=function(t,e,n,i,r){var o,a,s,l,u=t.coordinateSystem,h=OS(u,"cartesian2d");if(h){var c=u.getAxis("x"),p=u.getAxis("y");0,o=c.getBandWidth()+.5,a=p.getBandWidth()+.5,s=c.scale.getExtent(),l=p.scale.getExtent()}for(var d=this.group,f=t.getData(),g=t.getModel(["emphasis","itemStyle"]).getItemStyle(),y=t.getModel(["blur","itemStyle"]).getItemStyle(),v=t.getModel(["select","itemStyle"]).getItemStyle(),m=t.get(["itemStyle","borderRadius"]),x=rc(t),_=t.getModel("emphasis"),b=_.get("focus"),w=_.get("blurScope"),S=_.get("disabled"),M=h?[f.mapDimension("x"),f.mapDimension("y"),f.mapDimension("value")]:[f.mapDimension("time"),f.mapDimension("value")],I=n;Is[1]||Al[1])continue;var k=u.dataToPoint([D,A]);T=new Ws({shape:{x:k[0]-o/2,y:k[1]-a/2,width:o,height:a},style:C})}else{if(isNaN(f.get(M[1],I)))continue;T=new Ws({z2:1,shape:u.dataToRect([f.get(M[0],I)]).contentShape,style:C})}if(f.hasItemOption){var L=f.getItemModel(I),P=L.getModel("emphasis");g=P.getModel("itemStyle").getItemStyle(),y=L.getModel(["blur","itemStyle"]).getItemStyle(),v=L.getModel(["select","itemStyle"]).getItemStyle(),m=L.get(["itemStyle","borderRadius"]),b=P.get("focus"),w=P.get("blurScope"),S=P.get("disabled"),x=rc(L)}T.shape.r=m;var O=t.getRawValue(I),R="-";O&&null!=O[2]&&(R=O[2]+""),ic(T,x,{labelFetcher:t,labelDataIndex:I,defaultOpacity:C.opacity,defaultText:R}),T.ensureState("emphasis").style=g,T.ensureState("blur").style=y,T.ensureState("select").style=v,Zl(T,b,w,S),T.incremental=r,r&&(T.states.emphasis.hoverLayer=!0),d.add(T),f.setItemGraphicEl(I,T),this._progressiveEls&&this._progressiveEls.push(T)}},e.prototype._renderOnGeo=function(t,e,n,i){var r=n.targetVisuals.inRange,o=n.targetVisuals.outOfRange,a=e.getData(),s=this._hmLayer||this._hmLayer||new cO;s.blurSize=e.get("blurSize"),s.pointSize=e.get("pointSize"),s.minOpacity=e.get("minOpacity"),s.maxOpacity=e.get("maxOpacity");var l=t.getViewRect().clone(),u=t.getRoamTransform();l.applyTransform(u);var h=Math.max(l.x,0),c=Math.max(l.y,0),p=Math.min(l.width+l.x,i.getWidth()),d=Math.min(l.height+l.y,i.getHeight()),f=p-h,g=d-c,y=[a.mapDimension("lng"),a.mapDimension("lat"),a.mapDimension("value")],v=a.mapArray(y,(function(e,n,i){var r=t.dataToPoint([e,n]);return r[0]-=h,r[1]-=c,r.push(i),r})),m=n.getExtent(),x="visualMap.continuous"===n.type?function(t,e){var n=t[1]-t[0];return e=[(e[0]-t[0])/n,(e[1]-t[0])/n],function(t){return t>=e[0]&&t<=e[1]}}(m,n.option.range):function(t,e,n){var i=t[1]-t[0],r=(e=z(e,(function(e){return{interval:[(e.interval[0]-t[0])/i,(e.interval[1]-t[0])/i]}}))).length,o=0;return function(t){var i;for(i=o;i=0;i--){var a;if((a=e[i].interval)[0]<=t&&t<=a[1]){o=i;break}}return i>=0&&i=0?1:-1:o>0?1:-1}(n,o,r,i,c),function(t,e,n,i,r,o,a,s,l,u){var h,c=l.valueDim,p=l.categoryDim,d=Math.abs(n[p.wh]),f=t.getItemVisual(e,"symbolSize");h=Y(f)?f.slice():null==f?["100%","100%"]:[f,f];h[p.index]=$r(h[p.index],d),h[c.index]=$r(h[c.index],i?d:Math.abs(o)),u.symbolSize=h;var g=u.symbolScale=[h[0]/s,h[1]/s];g[c.index]*=(l.isHorizontal?-1:1)*a}(t,e,r,o,0,c.boundingLength,c.pxSign,u,i,c),function(t,e,n,i,r){var o=t.get(gO)||0;o&&(vO.attr({scaleX:e[0],scaleY:e[1],rotation:n}),vO.updateTransform(),o/=vO.getLineScale(),o*=e[i.valueDim.index]);r.valueLineWidth=o||0}(n,c.symbolScale,l,i,c);var p=c.symbolSize,d=Zy(n.get("symbolOffset"),p);return function(t,e,n,i,r,o,a,s,l,u,h,c){var p=h.categoryDim,d=h.valueDim,f=c.pxSign,g=Math.max(e[d.index]+s,0),y=g;if(i){var v=Math.abs(l),m=it(t.get("symbolMargin"),"15%")+"",x=!1;m.lastIndexOf("!")===m.length-1&&(x=!0,m=m.slice(0,m.length-1));var _=$r(m,e[d.index]),b=Math.max(g+2*_,0),w=x?0:2*_,S=vo(i),M=S?i:NO((v+w)/b);b=g+2*(_=(v-M*g)/2/(x?M:Math.max(M-1,1))),w=x?0:2*_,S||"fixed"===i||(M=u?NO((Math.abs(u)+w)/b):0),y=M*b-w,c.repeatTimes=M,c.symbolMargin=_}var I=f*(y/2),T=c.pathPosition=[];T[p.index]=n[p.wh]/2,T[d.index]="start"===a?I:"end"===a?l-I:l/2,o&&(T[0]+=o[0],T[1]+=o[1]);var C=c.bundlePosition=[];C[p.index]=n[p.xy],C[d.index]=n[d.xy];var D=c.barRectShape=A({},n);D[d.wh]=f*Math.max(Math.abs(n[d.wh]),Math.abs(T[d.index]+I)),D[p.wh]=n[p.wh];var k=c.clipShape={};k[p.xy]=-n[p.xy],k[p.wh]=h.ecSize[p.wh],k[d.xy]=0,k[d.wh]=n[d.wh]}(n,p,r,o,0,d,s,c.valueLineWidth,c.boundingLength,c.repeatCutLength,i,c),c}function _O(t,e){return t.toGlobalCoord(t.dataToCoord(t.scale.parse(e)))}function bO(t){var e=t.symbolPatternSize,n=Xy(t.symbolType,-e/2,-e/2,e,e);return n.attr({culling:!0}),"image"!==n.type&&n.setStyle({strokeNoScale:!0}),n}function wO(t,e,n,i){var r=t.__pictorialBundle,o=n.symbolSize,a=n.valueLineWidth,s=n.pathPosition,l=e.valueDim,u=n.repeatTimes||0,h=0,c=o[e.valueDim.index]+a+2*n.symbolMargin;for(PO(t,(function(t){t.__pictorialAnimationIndex=h,t.__pictorialRepeatTimes=u,h0:i<0)&&(r=u-1-t),e[l.index]=c*(r-u/2+.5)+s[l.index],{x:e[0],y:e[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation}}}function SO(t,e,n,i){var r=t.__pictorialBundle,o=t.__pictorialMainPath;o?OO(o,null,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation},n,i):(o=t.__pictorialMainPath=bO(n),r.add(o),OO(o,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:0,scaleY:0,rotation:n.rotation},{scaleX:n.symbolScale[0],scaleY:n.symbolScale[1]},n,i))}function MO(t,e,n){var i=A({},e.barRectShape),r=t.__pictorialBarRect;r?OO(r,null,{shape:i},e,n):((r=t.__pictorialBarRect=new Ws({z2:2,shape:i,silent:!0,style:{stroke:"transparent",fill:"transparent",lineWidth:0}})).disableMorphing=!0,t.add(r))}function IO(t,e,n,i){if(n.symbolClip){var r=t.__pictorialClipPath,o=A({},n.clipShape),a=e.valueDim,s=n.animationModel,l=n.dataIndex;if(r)vh(r,{shape:o},s,l);else{o[a.wh]=0,r=new Ws({shape:o}),t.__pictorialBundle.setClipPath(r),t.__pictorialClipPath=r;var u={};u[a.wh]=n.clipShape[a.wh],Qh[i?"updateProps":"initProps"](r,{shape:u},s,l)}}}function TO(t,e){var n=t.getItemModel(e);return n.getAnimationDelayParams=CO,n.isAnimationEnabled=DO,n}function CO(t){return{index:t.__pictorialAnimationIndex,count:t.__pictorialRepeatTimes}}function DO(){return this.parentModel.isAnimationEnabled()&&!!this.getShallow("animation")}function AO(t,e,n,i){var r=new Br,o=new Br;return r.add(o),r.__pictorialBundle=o,o.x=n.bundlePosition[0],o.y=n.bundlePosition[1],n.symbolRepeat?wO(r,e,n):SO(r,0,n),MO(r,n,i),IO(r,e,n,i),r.__pictorialShapeStr=LO(t,n),r.__pictorialSymbolMeta=n,r}function kO(t,e,n,i){var r=i.__pictorialBarRect;r&&r.removeTextContent();var o=[];PO(i,(function(t){o.push(t)})),i.__pictorialMainPath&&o.push(i.__pictorialMainPath),i.__pictorialClipPath&&(n=null),E(o,(function(t){_h(t,{scaleX:0,scaleY:0},n,e,(function(){i.parent&&i.parent.remove(i)}))})),t.setItemGraphicEl(e,null)}function LO(t,e){return[t.getItemVisual(e.dataIndex,"symbol")||"none",!!e.symbolRepeat,!!e.symbolClip].join(":")}function PO(t,e,n){E(t.__pictorialBundle.children(),(function(i){i!==t.__pictorialBarRect&&e.call(n,i)}))}function OO(t,e,n,i,r,o){e&&t.attr(e),i.symbolClip&&!r?n&&t.attr(n):n&&Qh[r?"updateProps":"initProps"](t,n,i.animationModel,i.dataIndex,o)}function RO(t,e,n){var i=n.dataIndex,r=n.itemModel,o=r.getModel("emphasis"),a=o.getModel("itemStyle").getItemStyle(),s=r.getModel(["blur","itemStyle"]).getItemStyle(),l=r.getModel(["select","itemStyle"]).getItemStyle(),u=r.getShallow("cursor"),h=o.get("focus"),c=o.get("blurScope"),p=o.get("scale");PO(t,(function(t){if(t instanceof Ns){var e=t.style;t.useStyle(A({image:e.image,x:e.x,y:e.y,width:e.width,height:e.height},n.style))}else t.useStyle(n.style);var i=t.ensureState("emphasis");i.style=a,p&&(i.scaleX=1.1*t.scaleX,i.scaleY=1.1*t.scaleY),t.ensureState("blur").style=s,t.ensureState("select").style=l,u&&(t.cursor=u),t.z2=n.z2}));var d=e.valueDim.posDesc[+(n.boundingLength>0)],f=t.__pictorialBarRect;f.ignoreClip=!0,ic(f,rc(r),{labelFetcher:e.seriesModel,labelDataIndex:i,defaultText:pS(e.seriesModel.getData(),i),inheritColor:n.style.fill,defaultOpacity:n.style.opacity,defaultOutsidePosition:d}),Zl(t,h,c,o.get("disabled"))}function NO(t){var e=Math.round(t);return Math.abs(t-e)<1e-4?e:Math.ceil(t)}var EO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n.defaultSymbol="roundRect",n}return n(e,t),e.prototype.getInitialData=function(e){return e.stack=null,t.prototype.getInitialData.apply(this,arguments)},e.type="series.pictorialBar",e.dependencies=["grid"],e.defaultOption=kc(qS.defaultOption,{symbol:"circle",symbolSize:null,symbolRotate:null,symbolPosition:null,symbolOffset:null,symbolMargin:null,symbolRepeat:!1,symbolRepeatDirection:"end",symbolClip:!1,symbolBoundingData:null,symbolPatternSize:400,barGap:"-100%",clip:!1,progressive:0,emphasis:{scale:!1},select:{itemStyle:{borderColor:"#212121"}}}),e}(qS);var zO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._layers=[],n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this,o=this.group,a=t.getLayerSeries(),s=i.getLayout("layoutInfo"),l=s.rect,u=s.boundaryGap;function h(t){return t.name}o.x=0,o.y=l.y+u[0];var c=new Gm(this._layersSeries||[],a,h,h),p=[];function d(e,n,s){var l=r._layers;if("remove"!==e){for(var u,h,c=[],d=[],f=a[n].indices,g=0;go&&(o=s),i.push(s)}for(var u=0;uo&&(o=c)}return{y0:r,max:o}}(l),h=u.y0,c=n/u.max,p=o.length,d=o[0].indices.length,f=0;fI&&!so(C-I)&&C0?(r.virtualPiece?r.virtualPiece.updateData(!1,i,t,e,n):(r.virtualPiece=new GO(i,t,e,n),l.add(r.virtualPiece)),o.piece.off("click"),r.virtualPiece.on("click",(function(t){r._rootToNode(o.parentNode)}))):r.virtualPiece&&(l.remove(r.virtualPiece),r.virtualPiece=null)}(a,s),this._initEvents(),this._oldChildren=h},e.prototype._initEvents=function(){var t=this;this.group.off("click"),this.group.on("click",(function(e){var n=!1;t.seriesModel.getViewRoot().eachNode((function(i){if(!n&&i.piece&&i.piece===e.target){var r=i.getModel().get("nodeClick");if("rootToNode"===r)t._rootToNode(i);else if("link"===r){var o=i.getModel(),a=o.get("link");if(a)Mp(a,o.get("target",!0)||"_blank")}n=!0}}))}))},e.prototype._rootToNode=function(t){t!==this.seriesModel.getViewRoot()&&this.api.dispatchAction({type:WO,from:this.uid,seriesId:this.seriesModel.id,targetNode:t})},e.prototype.containPoint=function(t,e){var n=e.getData().getItemLayout(0);if(n){var i=t[0]-n.cx,r=t[1]-n.cy,o=Math.sqrt(i*i+r*r);return o<=n.r&&o>=n.r0}},e.type="sunburst",e}(Og),XO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreStyleOnData=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};UO(n);var i=this._levelModels=z(t.levels||[],(function(t){return new Cc(t,this,e)}),this),r=nD.createTree(n,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=r.getNodeByDataIndex(e),o=i[n.depth];return o&&(t.parentModel=o),t}))}));return r.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treePathInfo=aD(i,this),n},e.prototype.getLevelModel=function(t){return this._levelModels&&this._levelModels[t.depth]},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){pD(this)},e.type="series.sunburst",e.defaultOption={z:2,center:["50%","50%"],radius:[0,"75%"],clockwise:!0,startAngle:90,minAngle:0,stillShowZeroSum:!0,nodeClick:"rootToNode",renderLabelForZeroData:!1,label:{rotate:"radial",show:!0,opacity:1,align:"center",position:"inside",distance:5,silent:!0},itemStyle:{borderWidth:1,borderColor:"white",borderType:"solid",shadowBlur:0,shadowColor:"rgba(0, 0, 0, 0.2)",shadowOffsetX:0,shadowOffsetY:0,opacity:1},emphasis:{focus:"descendant"},blur:{itemStyle:{opacity:.2},label:{opacity:.1}},animationType:"expansion",animationDuration:1e3,animationDurationUpdate:500,data:[],sort:"desc"},e}(bg);function UO(t){var e=0;E(t.children,(function(t){UO(t);var n=t.value;Y(n)&&(n=n[0]),e+=n}));var n=t.value;Y(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),Y(t.value)?t.value[0]=n:t.value=n}var ZO=Math.PI/180;function jO(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.get("center"),i=t.get("radius");Y(i)||(i=[0,i]),Y(e)||(e=[e,e]);var r=n.getWidth(),o=n.getHeight(),a=Math.min(r,o),s=$r(e[0],r),l=$r(e[1],o),u=$r(i[0],a/2),h=$r(i[1],a/2),c=-t.get("startAngle")*ZO,p=t.get("minAngle")*ZO,d=t.getData().tree.root,f=t.getViewRoot(),g=f.depth,y=t.get("sort");null!=y&&qO(f,y);var v=0;E(f.children,(function(t){!isNaN(t.getValue())&&v++}));var m=f.getValue(),x=Math.PI/(m||v)*2,_=f.depth>0,b=f.height-(_?-1:1),w=(h-u)/(b||1),S=t.get("clockwise"),M=t.get("stillShowZeroSum"),I=S?1:-1,T=function(e,n){if(e){var i=n;if(e!==d){var r=e.getValue(),o=0===m&&M?x:r*x;o1;)r=r.parentNode;var o=n.getColorFromPalette(r.name||r.dataIndex+"",e);return t.depth>1&&U(o)&&(o=$n(o,(t.depth-1)/(i-1)*.5)),o}(r,t,i.root.height)),A(n.ensureUniqueItemVisual(r.dataIndex,"style"),o)}))}))}var $O={color:"fill",borderColor:"stroke"},JO={symbol:1,symbolSize:1,symbolKeepAspect:1,legendIcon:1,visualMeta:1,liftZ:1,decal:1},QO=Vo(),tR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){this.currentZLevel=this.get("zlevel",!0),this.currentZ=this.get("z",!0)},e.prototype.getInitialData=function(t,e){return _x(null,this)},e.prototype.getDataParams=function(e,n,i){var r=t.prototype.getDataParams.call(this,e,n);return i&&(r.info=QO(i).info),r},e.type="series.custom",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={coordinateSystem:"cartesian2d",z:2,legendHoverLink:!0,clip:!1},e}(bg);function eR(t,e){return e=e||[0,0],z(["x","y"],(function(n,i){var r=this.getAxis(n),o=e[i],a=t[i]/2;return"category"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a))}),this)}function nR(t,e){return e=e||[0,0],z([0,1],(function(n){var i=e[n],r=t[n]/2,o=[],a=[];return o[n]=i-r,a[n]=i+r,o[1-n]=a[1-n]=e[1-n],Math.abs(this.dataToPoint(o)[n]-this.dataToPoint(a)[n])}),this)}function iR(t,e){var n=this.getAxis(),i=e instanceof Array?e[0]:e,r=(t instanceof Array?t[0]:t)/2;return"category"===n.type?n.getBandWidth():Math.abs(n.dataToCoord(i-r)-n.dataToCoord(i+r))}function rR(t,e){return e=e||[0,0],z(["Radius","Angle"],(function(n,i){var r=this["get"+n+"Axis"](),o=e[i],a=t[i]/2,s="category"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a));return"Angle"===n&&(s=s*Math.PI/180),s}),this)}function oR(t,e,n,i){return t&&(t.legacy||!1!==t.legacy&&!n&&!i&&"tspan"!==e&&("text"===e||_t(t,"text")))}function aR(t,e,n){var i,r,o,a=t;if("text"===e)o=a;else{o={},_t(a,"text")&&(o.text=a.text),_t(a,"rich")&&(o.rich=a.rich),_t(a,"textFill")&&(o.fill=a.textFill),_t(a,"textStroke")&&(o.stroke=a.textStroke),_t(a,"fontFamily")&&(o.fontFamily=a.fontFamily),_t(a,"fontSize")&&(o.fontSize=a.fontSize),_t(a,"fontStyle")&&(o.fontStyle=a.fontStyle),_t(a,"fontWeight")&&(o.fontWeight=a.fontWeight),r={type:"text",style:o,silent:!0},i={};var s=_t(a,"textPosition");n?i.position=s?a.textPosition:"inside":s&&(i.position=a.textPosition),_t(a,"textPosition")&&(i.position=a.textPosition),_t(a,"textOffset")&&(i.offset=a.textOffset),_t(a,"textRotation")&&(i.rotation=a.textRotation),_t(a,"textDistance")&&(i.distance=a.textDistance)}return sR(o,t),E(o.rich,(function(t){sR(t,t)})),{textConfig:i,textContent:r}}function sR(t,e){e&&(e.font=e.textFont||e.font,_t(e,"textStrokeWidth")&&(t.lineWidth=e.textStrokeWidth),_t(e,"textAlign")&&(t.align=e.textAlign),_t(e,"textVerticalAlign")&&(t.verticalAlign=e.textVerticalAlign),_t(e,"textLineHeight")&&(t.lineHeight=e.textLineHeight),_t(e,"textWidth")&&(t.width=e.textWidth),_t(e,"textHeight")&&(t.height=e.textHeight),_t(e,"textBackgroundColor")&&(t.backgroundColor=e.textBackgroundColor),_t(e,"textPadding")&&(t.padding=e.textPadding),_t(e,"textBorderColor")&&(t.borderColor=e.textBorderColor),_t(e,"textBorderWidth")&&(t.borderWidth=e.textBorderWidth),_t(e,"textBorderRadius")&&(t.borderRadius=e.textBorderRadius),_t(e,"textBoxShadowColor")&&(t.shadowColor=e.textBoxShadowColor),_t(e,"textBoxShadowBlur")&&(t.shadowBlur=e.textBoxShadowBlur),_t(e,"textBoxShadowOffsetX")&&(t.shadowOffsetX=e.textBoxShadowOffsetX),_t(e,"textBoxShadowOffsetY")&&(t.shadowOffsetY=e.textBoxShadowOffsetY))}function lR(t,e,n){var i=t;i.textPosition=i.textPosition||n.position||"inside",null!=n.offset&&(i.textOffset=n.offset),null!=n.rotation&&(i.textRotation=n.rotation),null!=n.distance&&(i.textDistance=n.distance);var r=i.textPosition.indexOf("inside")>=0,o=t.fill||"#000";uR(i,e);var a=null==i.textFill;return r?a&&(i.textFill=n.insideFill||"#fff",!i.textStroke&&n.insideStroke&&(i.textStroke=n.insideStroke),!i.textStroke&&(i.textStroke=o),null==i.textStrokeWidth&&(i.textStrokeWidth=2)):(a&&(i.textFill=t.fill||n.outsideFill||"#000"),!i.textStroke&&n.outsideStroke&&(i.textStroke=n.outsideStroke)),i.text=e.text,i.rich=e.rich,E(e.rich,(function(t){uR(t,t)})),i}function uR(t,e){e&&(_t(e,"fill")&&(t.textFill=e.fill),_t(e,"stroke")&&(t.textStroke=e.fill),_t(e,"lineWidth")&&(t.textStrokeWidth=e.lineWidth),_t(e,"font")&&(t.font=e.font),_t(e,"fontStyle")&&(t.fontStyle=e.fontStyle),_t(e,"fontWeight")&&(t.fontWeight=e.fontWeight),_t(e,"fontSize")&&(t.fontSize=e.fontSize),_t(e,"fontFamily")&&(t.fontFamily=e.fontFamily),_t(e,"align")&&(t.textAlign=e.align),_t(e,"verticalAlign")&&(t.textVerticalAlign=e.verticalAlign),_t(e,"lineHeight")&&(t.textLineHeight=e.lineHeight),_t(e,"width")&&(t.textWidth=e.width),_t(e,"height")&&(t.textHeight=e.height),_t(e,"backgroundColor")&&(t.textBackgroundColor=e.backgroundColor),_t(e,"padding")&&(t.textPadding=e.padding),_t(e,"borderColor")&&(t.textBorderColor=e.borderColor),_t(e,"borderWidth")&&(t.textBorderWidth=e.borderWidth),_t(e,"borderRadius")&&(t.textBorderRadius=e.borderRadius),_t(e,"shadowColor")&&(t.textBoxShadowColor=e.shadowColor),_t(e,"shadowBlur")&&(t.textBoxShadowBlur=e.shadowBlur),_t(e,"shadowOffsetX")&&(t.textBoxShadowOffsetX=e.shadowOffsetX),_t(e,"shadowOffsetY")&&(t.textBoxShadowOffsetY=e.shadowOffsetY),_t(e,"textShadowColor")&&(t.textShadowColor=e.textShadowColor),_t(e,"textShadowBlur")&&(t.textShadowBlur=e.textShadowBlur),_t(e,"textShadowOffsetX")&&(t.textShadowOffsetX=e.textShadowOffsetX),_t(e,"textShadowOffsetY")&&(t.textShadowOffsetY=e.textShadowOffsetY))}var hR={position:["x","y"],scale:["scaleX","scaleY"],origin:["originX","originY"]},cR=G(hR),pR=(V(mr,(function(t,e){return t[e]=1,t}),{}),mr.join(", "),["","style","shape","extra"]),dR=Vo();function fR(t,e,n,i,r){var o=t+"Animation",a=gh(t,i,r)||{},s=dR(e).userDuring;return a.duration>0&&(a.during=s?W(bR,{el:e,userDuring:s}):null,a.setToFinal=!0,a.scope=t),A(a,n[o]),a}function gR(t,e,n,i){var r=(i=i||{}).dataIndex,o=i.isInit,a=i.clearStyle,s=n.isAnimationEnabled(),l=dR(t),u=e.style;l.userDuring=e.during;var h={},c={};if(function(t,e,n){for(var i=0;i=0)){var c=t.getAnimationStyleProps(),p=c?c.style:null;if(p){!r&&(r=i.style={});var d=G(n);for(u=0;u0&&t.animateFrom(p,d)}else!function(t,e,n,i,r){if(r){var o=fR("update",t,e,i,n);o.duration>0&&t.animateFrom(r,o)}}(t,e,r||0,n,h);yR(t,e),u?t.dirty():t.markRedraw()}function yR(t,e){for(var n=dR(t).leaveToProps,i=0;i=0){!o&&(o=i[t]={});var p=G(a);for(h=0;hi[1]&&i.reverse(),{coordSys:{type:"polar",cx:t.cx,cy:t.cy,r:i[1],r0:i[0]},api:{coord:function(i){var r=e.dataToRadius(i[0]),o=n.dataToAngle(i[1]),a=t.coordToPoint([r,o]);return a.push(r,o*Math.PI/180),a},size:W(rR,t)}}},calendar:function(t){var e=t.getRect(),n=t.getRangeInfo();return{coordSys:{type:"calendar",x:e.x,y:e.y,width:e.width,height:e.height,cellWidth:t.getCellWidth(),cellHeight:t.getCellHeight(),rangeInfo:{start:n.start,end:n.end,weeks:n.weeks,dayCount:n.allDay}},api:{coord:function(e,n){return t.dataToPoint(e,n)}}}}};function BR(t){return t instanceof ks}function FR(t){return t instanceof Da}var GR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){this._progressiveEls=null;var r=this._data,o=t.getData(),a=this.group,s=UR(t,o,e,n);r||a.removeAll(),o.diff(r).add((function(e){jR(n,null,e,s(e,i),t,a,o)})).remove((function(e){var n=r.getItemGraphicEl(e);n&&vR(n,QO(n).option,t)})).update((function(e,l){var u=r.getItemGraphicEl(l);jR(n,u,e,s(e,i),t,a,o)})).execute();var l=t.get("clip",!0)?PS(t.coordinateSystem,!1,t):null;l?a.setClipPath(l):a.removeClipPath(),this._data=o},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll(),this._data=null},e.prototype.incrementalRender=function(t,e,n,i,r){var o=e.getData(),a=UR(e,o,n,i),s=this._progressiveEls=[];function l(t){t.isGroup||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}for(var u=t.start;u=0?e.getStore().get(r,n):void 0}var o=e.get(i.name,n),a=i&&i.ordinalMeta;return a?a.categories[o]:o},styleEmphasis:function(n,i){0;null==i&&(i=s);var r=m(i,AR).getItemStyle(),o=x(i,AR),a=oc(o,null,null,!0,!0);a.text=o.getShallow("show")?ot(t.getFormattedLabel(i,AR),t.getFormattedLabel(i,kR),pS(e,i)):null;var l=ac(o,null,!0);return b(n,r),r=lR(r,a,l),n&&_(r,n),r.legacy=!0,r},visual:function(t,n){if(null==n&&(n=s),_t($O,t)){var i=e.getItemVisual(n,"style");return i?i[$O[t]]:null}if(_t(JO,t))return e.getItemVisual(n,t)},barLayout:function(t){if("cartesian2d"===o.type){return function(t){var e=[],n=t.axis,i="axis0";if("category"===n.type){for(var r=n.getBandWidth(),o=0;o=c;f--){var g=e.childAt(f);tN(e,g,r)}}(t,c,n,i,r),a>=0?o.replaceAt(c,a):o.add(c),c}function KR(t,e,n){var i,r=QO(t),o=e.type,a=e.shape,s=e.style;return n.isUniversalTransitionEnabled()||null!=o&&o!==r.customGraphicType||"path"===o&&((i=a)&&(_t(i,"pathData")||_t(i,"d")))&&rN(a)!==r.customPathData||"image"===o&&_t(s,"image")&&s.image!==r.customImagePath}function $R(t,e,n){var i=e?JR(t,e):t,r=e?QR(t,i,AR):t.style,o=t.type,a=i?i.textConfig:null,s=t.textContent,l=s?e?JR(s,e):s:null;if(r&&(n.isLegacy||oR(r,o,!!a,!!l))){n.isLegacy=!0;var u=aR(r,o,!e);!a&&u.textConfig&&(a=u.textConfig),!l&&u.textContent&&(l=u.textContent)}if(!e&&l){var h=l;!h.type&&(h.type="text")}var c=e?n[e]:n.normal;c.cfg=a,c.conOpt=l}function JR(t,e){return e?t?t[e]:null:t}function QR(t,e,n){var i=e&&e.style;return null==i&&n===AR&&t&&(i=t.styleEmphasis),i}function tN(t,e,n){e&&vR(e,QO(t).option,n)}function eN(t,e){var n=t&&t.name;return null!=n?n:"e\0\0"+e}function nN(t,e){var n=this.context,i=null!=t?n.newChildren[t]:null,r=null!=e?n.oldChildren[e]:null;qR(n.api,r,n.dataIndex,i,n.seriesModel,n.group)}function iN(t){var e=this.context,n=e.oldChildren[t];n&&vR(n,QO(n).option,e.seriesModel)}function rN(t){return t&&(t.pathData||t.d)}var oN=Vo(),aN=T,sN=W,lN=function(){function t(){this._dragging=!1,this.animationThreshold=15}return t.prototype.render=function(t,e,n,i){var r=e.get("value"),o=e.get("status");if(this._axisModel=t,this._axisPointerModel=e,this._api=n,i||this._lastValue!==r||this._lastStatus!==o){this._lastValue=r,this._lastStatus=o;var a=this._group,s=this._handle;if(!o||"hide"===o)return a&&a.hide(),void(s&&s.hide());a&&a.show(),s&&s.show();var l={};this.makeElOption(l,r,t,e,n);var u=l.graphicKey;u!==this._lastGraphicKey&&this.clear(n),this._lastGraphicKey=u;var h=this._moveAnimation=this.determineAnimation(t,e);if(a){var c=H(uN,e,h);this.updatePointerEl(a,l,c),this.updateLabelEl(a,l,c,e)}else a=this._group=new Br,this.createPointerEl(a,l,t,e),this.createLabelEl(a,l,t,e),n.getZr().add(a);dN(a,e,!0),this._renderHandle(r)}},t.prototype.remove=function(t){this.clear(t)},t.prototype.dispose=function(t){this.clear(t)},t.prototype.determineAnimation=function(t,e){var n=e.get("animation"),i=t.axis,r="category"===i.type,o=e.get("snap");if(!o&&!r)return!1;if("auto"===n||null==n){var a=this.animationThreshold;if(r&&i.getBandWidth()>a)return!0;if(o){var s=wI(t).seriesDataCount,l=i.getExtent();return Math.abs(l[0]-l[1])/s>a}return!1}return!0===n},t.prototype.makeElOption=function(t,e,n,i,r){},t.prototype.createPointerEl=function(t,e,n,i){var r=e.pointer;if(r){var o=oN(t).pointerEl=new Qh[r.type](aN(e.pointer));t.add(o)}},t.prototype.createLabelEl=function(t,e,n,i){if(e.label){var r=oN(t).labelEl=new Xs(aN(e.label));t.add(r),cN(r,i)}},t.prototype.updatePointerEl=function(t,e,n){var i=oN(t).pointerEl;i&&e.pointer&&(i.setStyle(e.pointer.style),n(i,{shape:e.pointer.shape}))},t.prototype.updateLabelEl=function(t,e,n,i){var r=oN(t).labelEl;r&&(r.setStyle(e.label.style),n(r,{x:e.label.x,y:e.label.y}),cN(r,i))},t.prototype._renderHandle=function(t){if(!this._dragging&&this.updateHandleTransform){var e,n=this._axisPointerModel,i=this._api.getZr(),r=this._handle,o=n.getModel("handle"),a=n.get("status");if(!o.get("show")||!a||"hide"===a)return r&&i.remove(r),void(this._handle=null);this._handle||(e=!0,r=this._handle=Uh(o.get("icon"),{cursor:"move",draggable:!0,onmousemove:function(t){de(t.event)},onmousedown:sN(this._onHandleDragMove,this,0,0),drift:sN(this._onHandleDragMove,this),ondragend:sN(this._onHandleDragEnd,this)}),i.add(r)),dN(r,n,!1),r.setStyle(o.getItemStyle(null,["color","borderColor","borderWidth","opacity","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"]));var s=o.get("size");Y(s)||(s=[s,s]),r.scaleX=s[0]/2,r.scaleY=s[1]/2,Hg(this,"_doDispatchAxisPointer",o.get("throttle")||0,"fixRate"),this._moveHandleToValue(t,e)}},t.prototype._moveHandleToValue=function(t,e){uN(this._axisPointerModel,!e&&this._moveAnimation,this._handle,pN(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},t.prototype._onHandleDragMove=function(t,e){var n=this._handle;if(n){this._dragging=!0;var i=this.updateHandleTransform(pN(n),[t,e],this._axisModel,this._axisPointerModel);this._payloadInfo=i,n.stopAnimation(),n.attr(pN(i)),oN(n).lastProp=null,this._doDispatchAxisPointer()}},t.prototype._doDispatchAxisPointer=function(){if(this._handle){var t=this._payloadInfo,e=this._axisModel;this._api.dispatchAction({type:"updateAxisPointer",x:t.cursorPoint[0],y:t.cursorPoint[1],tooltipOption:t.tooltipOption,axesInfo:[{axisDim:e.axis.dim,axisIndex:e.componentIndex}]})}},t.prototype._onHandleDragEnd=function(){if(this._dragging=!1,this._handle){var t=this._axisPointerModel.get("value");this._moveHandleToValue(t),this._api.dispatchAction({type:"hideTip"})}},t.prototype.clear=function(t){this._lastValue=null,this._lastStatus=null;var e=t.getZr(),n=this._group,i=this._handle;e&&n&&(this._lastGraphicKey=null,n&&e.remove(n),i&&e.remove(i),this._group=null,this._handle=null,this._payloadInfo=null),Yg(this,"_doDispatchAxisPointer")},t.prototype.doClear=function(){},t.prototype.buildLabel=function(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}},t}();function uN(t,e,n,i){hN(oN(n).lastProp,i)||(oN(n).lastProp=i,e?vh(n,i,t):(n.stopAnimation(),n.attr(i)))}function hN(t,e){if(q(t)&&q(e)){var n=!0;return E(e,(function(e,i){n=n&&hN(t[i],e)})),!!n}return t===e}function cN(t,e){t[e.get(["label","show"])?"show":"hide"]()}function pN(t){return{x:t.x||0,y:t.y||0,rotation:t.rotation||0}}function dN(t,e,n){var i=e.get("z"),r=e.get("zlevel");t&&t.traverse((function(t){"group"!==t.type&&(null!=i&&(t.z=i),null!=r&&(t.zlevel=r),t.silent=n)}))}function fN(t){var e,n=t.get("type"),i=t.getModel(n+"Style");return"line"===n?(e=i.getLineStyle()).fill=null:"shadow"===n&&((e=i.getAreaStyle()).stroke=null),e}function gN(t,e,n,i,r){var o=yN(n.get("value"),e.axis,e.ecModel,n.get("seriesDataIndices"),{precision:n.get(["label","precision"]),formatter:n.get(["label","formatter"])}),a=n.getModel("label"),s=vp(a.get("padding")||0),l=a.getFont(),u=Sr(o,l),h=r.position,c=u.width+s[1]+s[3],p=u.height+s[0]+s[2],d=r.align;"right"===d&&(h[0]-=c),"center"===d&&(h[0]-=c/2);var f=r.verticalAlign;"bottom"===f&&(h[1]-=p),"middle"===f&&(h[1]-=p/2),function(t,e,n,i){var r=i.getWidth(),o=i.getHeight();t[0]=Math.min(t[0]+e,r)-e,t[1]=Math.min(t[1]+n,o)-n,t[0]=Math.max(t[0],0),t[1]=Math.max(t[1],0)}(h,c,p,i);var g=a.get("backgroundColor");g&&"auto"!==g||(g=e.get(["axisLine","lineStyle","color"])),t.label={x:h[0],y:h[1],style:oc(a,{text:o,font:l,fill:a.getTextColor(),padding:s,backgroundColor:g}),z2:10}}function yN(t,e,n,i,r){t=e.scale.parse(t);var o=e.scale.getLabel({value:t},{precision:r.precision}),a=r.formatter;if(a){var s={value:S_(e,{value:t}),axisDimension:e.dim,axisIndex:e.index,seriesData:[]};E(i,(function(t){var e=n.getSeriesByIndex(t.seriesIndex),i=t.dataIndexInside,r=e&&e.getDataParams(i);r&&s.seriesData.push(r)})),U(a)?o=a.replace("{value}",o):X(a)&&(o=a(s))}return o}function vN(t,e,n){var i=[1,0,0,1,0,0];return Se(i,i,n.rotation),we(i,i,n.position),Fh([t.dataToCoord(e),(n.labelOffset||0)+(n.labelDirection||1)*(n.labelMargin||0)],i)}function mN(t,e,n,i,r,o){var a=dI.innerTextLayout(n.rotation,0,n.labelDirection);n.labelMargin=r.get(["label","margin"]),gN(e,i,r,o,{position:vN(i.axis,t,n),align:a.textAlign,verticalAlign:a.textVerticalAlign})}function xN(t,e,n){return{x1:t[n=n||0],y1:t[1-n],x2:e[n],y2:e[1-n]}}function _N(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}}function bN(t,e,n,i,r,o){return{cx:t,cy:e,r0:n,r:i,startAngle:r,endAngle:o,clockwise:!0}}var wN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.grid,s=i.get("type"),l=SN(a,o).getOtherAxis(o).getGlobalExtent(),u=o.toGlobalCoord(o.dataToCoord(e,!0));if(s&&"none"!==s){var h=fN(i),c=MN[s](o,u,l);c.style=h,t.graphicKey=c.type,t.pointer=c}mN(e,t,iI(a.model,n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=iI(e.axis.grid.model,e,{labelInside:!1});i.labelMargin=n.get(["handle","margin"]);var r=vN(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.grid,a=r.getGlobalExtent(!0),s=SN(o,r).getOtherAxis(r).getGlobalExtent(),l="x"===r.dim?0:1,u=[t.x,t.y];u[l]+=e[l],u[l]=Math.min(a[1],u[l]),u[l]=Math.max(a[0],u[l]);var h=(s[1]+s[0])/2,c=[h,h];c[l]=u[l];return{x:u[0],y:u[1],rotation:t.rotation,cursorPoint:c,tooltipOption:[{verticalAlign:"middle"},{align:"center"}][l]}},e}(lN);function SN(t,e){var n={};return n[e.dim+"AxisIndex"]=e.index,t.getCartesian(n)}var MN={line:function(t,e,n){return{type:"Line",subPixelOptimize:!0,shape:xN([e,n[0]],[e,n[1]],IN(t))}},shadow:function(t,e,n){var i=Math.max(1,t.getBandWidth()),r=n[1]-n[0];return{type:"Rect",shape:_N([e-i/2,n[0]],[i,r],IN(t))}}};function IN(t){return"x"===t.dim?0:1}var TN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="axisPointer",e.defaultOption={show:"auto",z:50,type:"line",snap:!1,triggerTooltip:!0,triggerEmphasis:!0,value:null,status:null,link:[],animation:null,animationDurationUpdate:200,lineStyle:{color:"#B9BEC9",width:1,type:"dashed"},shadowStyle:{color:"rgba(210,219,238,0.2)"},label:{show:!0,formatter:null,precision:"auto",margin:3,color:"#fff",padding:[5,7,5,7],backgroundColor:"auto",borderColor:null,borderWidth:0,borderRadius:3},handle:{show:!1,icon:"M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z",size:45,margin:50,color:"#333",shadowBlur:3,shadowColor:"#aaa",shadowOffsetX:0,shadowOffsetY:2,throttle:40}},e}(zp),CN=Vo(),DN=E;function AN(t,e,n){if(!r.node){var i=e.getZr();CN(i).records||(CN(i).records={}),function(t,e){if(CN(t).initialized)return;function n(n,i){t.on(n,(function(n){var r=function(t){var e={showTip:[],hideTip:[]},n=function(i){var r=e[i.type];r?r.push(i):(i.dispatchAction=n,t.dispatchAction(i))};return{dispatchAction:n,pendings:e}}(e);DN(CN(t).records,(function(t){t&&i(t,n,r.dispatchAction)})),function(t,e){var n,i=t.showTip.length,r=t.hideTip.length;i?n=t.showTip[i-1]:r&&(n=t.hideTip[r-1]);n&&(n.dispatchAction=null,e.dispatchAction(n))}(r.pendings,e)}))}CN(t).initialized=!0,n("click",H(LN,"click")),n("mousemove",H(LN,"mousemove")),n("globalout",kN)}(i,e),(CN(i).records[t]||(CN(i).records[t]={})).handler=n}}function kN(t,e,n){t.handler("leave",null,n)}function LN(t,e,n,i){e.handler(t,n,i)}function PN(t,e){if(!r.node){var n=e.getZr();(CN(n).records||{})[t]&&(CN(n).records[t]=null)}}var ON=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=e.getComponent("tooltip"),r=t.get("triggerOn")||i&&i.get("triggerOn")||"mousemove|click";AN("axisPointer",n,(function(t,e,n){"none"!==r&&("leave"===t||r.indexOf(t)>=0)&&n({type:"updateAxisPointer",currTrigger:t,x:e&&e.offsetX,y:e&&e.offsetY})}))},e.prototype.remove=function(t,e){PN("axisPointer",e)},e.prototype.dispose=function(t,e){PN("axisPointer",e)},e.type="axisPointer",e}(Ag);function RN(t,e){var n,i=[],r=t.seriesIndex;if(null==r||!(n=e.getSeriesByIndex(r)))return{point:[]};var o=n.getData(),a=zo(o,t);if(null==a||a<0||Y(a))return{point:[]};var s=o.getItemGraphicEl(a),l=n.coordinateSystem;if(n.getTooltipPosition)i=n.getTooltipPosition(a)||[];else if(l&&l.dataToPoint)if(t.isStacked){var u=l.getBaseAxis(),h=l.getOtherAxis(u).dim,c=u.dim,p="x"===h||"radius"===h?1:0,d=o.mapDimension(c),f=[];f[p]=o.get(d,a),f[1-p]=o.get(o.getCalculationInfo("stackResultDimension"),a),i=l.dataToPoint(f)||[]}else i=l.dataToPoint(o.getValues(z(l.dimensions,(function(t){return o.mapDimension(t)})),a))||[];else if(s){var g=s.getBoundingRect().clone();g.applyTransform(s.transform),i=[g.x+g.width/2,g.y+g.height/2]}return{point:i,el:s}}var NN=Vo();function EN(t,e,n){var i=t.currTrigger,r=[t.x,t.y],o=t,a=t.dispatchAction||W(n.dispatchAction,n),s=e.getComponent("axisPointer").coordSysAxesInfo;if(s){GN(r)&&(r=RN({seriesIndex:o.seriesIndex,dataIndex:o.dataIndex},e).point);var l=GN(r),u=o.axesInfo,h=s.axesInfo,c="leave"===i||GN(r),p={},d={},f={list:[],map:{}},g={showPointer:H(VN,d),showTooltip:H(BN,f)};E(s.coordSysMap,(function(t,e){var n=l||t.containPoint(r);E(s.coordSysAxesInfo[e],(function(t,e){var i=t.axis,o=function(t,e){for(var n=0;n<(t||[]).length;n++){var i=t[n];if(e.axis.dim===i.axisDim&&e.axis.model.componentIndex===i.axisIndex)return i}}(u,t);if(!c&&n&&(!u||o)){var a=o&&o.value;null!=a||l||(a=i.pointToData(r)),null!=a&&zN(t,a,g,!1,p)}}))}));var y={};return E(h,(function(t,e){var n=t.linkGroup;n&&!d[e]&&E(n.axesInfo,(function(e,i){var r=d[i];if(e!==t&&r){var o=r.value;n.mapper&&(o=t.axis.scale.parse(n.mapper(o,FN(e),FN(t)))),y[t.key]=o}}))})),E(y,(function(t,e){zN(h[e],t,g,!0,p)})),function(t,e,n){var i=n.axesInfo=[];E(e,(function(e,n){var r=e.axisPointerModel.option,o=t[n];o?(!e.useHandle&&(r.status="show"),r.value=o.value,r.seriesDataIndices=(o.payloadBatch||[]).slice()):!e.useHandle&&(r.status="hide"),"show"===r.status&&i.push({axisDim:e.axis.dim,axisIndex:e.axis.model.componentIndex,value:r.value})}))}(d,h,p),function(t,e,n,i){if(GN(e)||!t.list.length)return void i({type:"hideTip"});var r=((t.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};i({type:"showTip",escapeConnect:!0,x:e[0],y:e[1],tooltipOption:n.tooltipOption,position:n.position,dataIndexInside:r.dataIndexInside,dataIndex:r.dataIndex,seriesIndex:r.seriesIndex,dataByCoordSys:t.list})}(f,r,t,a),function(t,e,n){var i=n.getZr(),r="axisPointerLastHighlights",o=NN(i)[r]||{},a=NN(i)[r]={};E(t,(function(t,e){var n=t.axisPointerModel.option;"show"===n.status&&t.triggerEmphasis&&E(n.seriesDataIndices,(function(t){var e=t.seriesIndex+" | "+t.dataIndex;a[e]=t}))}));var s=[],l=[];E(o,(function(t,e){!a[e]&&l.push(t)})),E(a,(function(t,e){!o[e]&&s.push(t)})),l.length&&n.dispatchAction({type:"downplay",escapeConnect:!0,notBlur:!0,batch:l}),s.length&&n.dispatchAction({type:"highlight",escapeConnect:!0,notBlur:!0,batch:s})}(h,0,n),p}}function zN(t,e,n,i,r){var o=t.axis;if(!o.scale.isBlank()&&o.containData(e))if(t.involveSeries){var a=function(t,e){var n=e.axis,i=n.dim,r=t,o=[],a=Number.MAX_VALUE,s=-1;return E(e.seriesModels,(function(e,l){var u,h,c=e.getData().mapDimensionsAll(i);if(e.getAxisTooltipData){var p=e.getAxisTooltipData(c,t,n);h=p.dataIndices,u=p.nestestValue}else{if(!(h=e.getData().indicesOfNearest(c[0],t,"category"===n.type?.5:null)).length)return;u=e.getData().get(c[0],h[0])}if(null!=u&&isFinite(u)){var d=t-u,f=Math.abs(d);f<=a&&((f=0&&s<0)&&(a=f,s=d,r=u,o.length=0),E(h,(function(t){o.push({seriesIndex:e.seriesIndex,dataIndexInside:t,dataIndex:e.getData().getRawIndex(t)})})))}})),{payloadBatch:o,snapToValue:r}}(e,t),s=a.payloadBatch,l=a.snapToValue;s[0]&&null==r.seriesIndex&&A(r,s[0]),!i&&t.snap&&o.containData(l)&&null!=l&&(e=l),n.showPointer(t,e,s),n.showTooltip(t,a,l)}else n.showPointer(t,e)}function VN(t,e,n,i){t[e.key]={value:n,payloadBatch:i}}function BN(t,e,n,i){var r=n.payloadBatch,o=e.axis,a=o.model,s=e.axisPointerModel;if(e.triggerTooltip&&r.length){var l=e.coordSys.model,u=MI(l),h=t.map[u];h||(h=t.map[u]={coordSysId:l.id,coordSysIndex:l.componentIndex,coordSysType:l.type,coordSysMainType:l.mainType,dataByAxis:[]},t.list.push(h)),h.dataByAxis.push({axisDim:o.dim,axisIndex:a.componentIndex,axisType:a.type,axisId:a.id,value:i,valueLabelOpt:{precision:s.get(["label","precision"]),formatter:s.get(["label","formatter"])},seriesDataIndices:r.slice()})}}function FN(t){var e=t.axis.model,n={},i=n.axisDim=t.axis.dim;return n.axisIndex=n[i+"AxisIndex"]=e.componentIndex,n.axisName=n[i+"AxisName"]=e.name,n.axisId=n[i+"AxisId"]=e.id,n}function GN(t){return!t||null==t[0]||isNaN(t[0])||null==t[1]||isNaN(t[1])}function WN(t){TI.registerAxisPointerClass("CartesianAxisPointer",wN),t.registerComponentModel(TN),t.registerComponentView(ON),t.registerPreprocessor((function(t){if(t){(!t.axisPointer||0===t.axisPointer.length)&&(t.axisPointer={});var e=t.axisPointer.link;e&&!Y(e)&&(t.axisPointer.link=[e])}})),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,(function(t,e){t.getComponent("axisPointer").coordSysAxesInfo=xI(t,e)})),t.registerAction({type:"updateAxisPointer",event:"updateAxisPointer",update:":updateAxisPointer"},EN)}var HN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis;"angle"===o.dim&&(this.animationThreshold=Math.PI/18);var a=o.polar,s=a.getOtherAxis(o).getExtent(),l=o.dataToCoord(e),u=i.get("type");if(u&&"none"!==u){var h=fN(i),c=YN[u](o,a,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}var p=function(t,e,n,i,r){var o=e.axis,a=o.dataToCoord(t),s=i.getAngleAxis().getExtent()[0];s=s/180*Math.PI;var l,u,h,c=i.getRadiusAxis().getExtent();if("radius"===o.dim){var p=[1,0,0,1,0,0];Se(p,p,s),we(p,p,[i.cx,i.cy]),l=Fh([a,-r],p);var d=e.getModel("axisLabel").get("rotate")||0,f=dI.innerTextLayout(s,d*Math.PI/180,-1);u=f.textAlign,h=f.textVerticalAlign}else{var g=c[1];l=i.coordToPoint([g+r,a]);var y=i.cx,v=i.cy;u=Math.abs(l[0]-y)/g<.3?"center":l[0]>y?"left":"right",h=Math.abs(l[1]-v)/g<.3?"middle":l[1]>v?"top":"bottom"}return{position:l,align:u,verticalAlign:h}}(e,n,0,a,i.get(["label","margin"]));gN(t,n,i,r,p)},e}(lN);var YN={line:function(t,e,n,i){return"angle"===t.dim?{type:"Line",shape:xN(e.coordToPoint([i[0],n]),e.coordToPoint([i[1],n]))}:{type:"Circle",shape:{cx:e.cx,cy:e.cy,r:n}}},shadow:function(t,e,n,i){var r=Math.max(1,t.getBandWidth()),o=Math.PI/180;return"angle"===t.dim?{type:"Sector",shape:bN(e.cx,e.cy,i[0],i[1],(-n-r/2)*o,(r/2-n)*o)}:{type:"Sector",shape:bN(e.cx,e.cy,n-r/2,n+r/2,0,2*Math.PI)}}},XN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.findAxisModel=function(t){var e;return this.ecModel.eachComponent(t,(function(t){t.getCoordSysModel()===this&&(e=t)}),this),e},e.type="polar",e.dependencies=["radiusAxis","angleAxis"],e.defaultOption={z:0,center:["50%","50%"],radius:"80%"},e}(zp),UN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents("polar",Wo).models[0]},e.type="polarAxis",e}(zp);R(UN,D_);var ZN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="angleAxis",e}(UN),jN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="radiusAxis",e}(UN),qN=function(t){function e(e,n){return t.call(this,"radius",e,n)||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},e}(ab);qN.prototype.dataToRadius=ab.prototype.dataToCoord,qN.prototype.radiusToData=ab.prototype.coordToData;var KN=Vo(),$N=function(t){function e(e,n){return t.call(this,"angle",e,n||[0,360])||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},e.prototype.calculateCategoryInterval=function(){var t=this,e=t.getLabelModel(),n=t.scale,i=n.getExtent(),r=n.count();if(i[1]-i[0]<1)return 0;var o=i[0],a=t.dataToCoord(o+1)-t.dataToCoord(o),s=Math.abs(a),l=Sr(null==o?"":o+"",e.getFont(),"center","top"),u=Math.max(l.height,7)/s;isNaN(u)&&(u=1/0);var h=Math.max(0,Math.floor(u)),c=KN(t.model),p=c.lastAutoInterval,d=c.lastTickCount;return null!=p&&null!=d&&Math.abs(p-h)<=1&&Math.abs(d-r)<=1&&p>h?h=p:(c.lastTickCount=r,c.lastAutoInterval=h),h},e}(ab);$N.prototype.dataToAngle=ab.prototype.dataToCoord,$N.prototype.angleToData=ab.prototype.coordToData;var JN=["radius","angle"],QN=function(){function t(t){this.dimensions=JN,this.type="polar",this.cx=0,this.cy=0,this._radiusAxis=new qN,this._angleAxis=new $N,this.axisPointerEnabled=!0,this.name=t||"",this._radiusAxis.polar=this._angleAxis.polar=this}return t.prototype.containPoint=function(t){var e=this.pointToCoord(t);return this._radiusAxis.contain(e[0])&&this._angleAxis.contain(e[1])},t.prototype.containData=function(t){return this._radiusAxis.containData(t[0])&&this._angleAxis.containData(t[1])},t.prototype.getAxis=function(t){return this["_"+t+"Axis"]},t.prototype.getAxes=function(){return[this._radiusAxis,this._angleAxis]},t.prototype.getAxesByScale=function(t){var e=[],n=this._angleAxis,i=this._radiusAxis;return n.scale.type===t&&e.push(n),i.scale.type===t&&e.push(i),e},t.prototype.getAngleAxis=function(){return this._angleAxis},t.prototype.getRadiusAxis=function(){return this._radiusAxis},t.prototype.getOtherAxis=function(t){var e=this._angleAxis;return t===e?this._radiusAxis:e},t.prototype.getBaseAxis=function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAngleAxis()},t.prototype.getTooltipAxes=function(t){var e=null!=t&&"auto"!==t?this.getAxis(t):this.getBaseAxis();return{baseAxes:[e],otherAxes:[this.getOtherAxis(e)]}},t.prototype.dataToPoint=function(t,e){return this.coordToPoint([this._radiusAxis.dataToRadius(t[0],e),this._angleAxis.dataToAngle(t[1],e)])},t.prototype.pointToData=function(t,e){var n=this.pointToCoord(t);return[this._radiusAxis.radiusToData(n[0],e),this._angleAxis.angleToData(n[1],e)]},t.prototype.pointToCoord=function(t){var e=t[0]-this.cx,n=t[1]-this.cy,i=this.getAngleAxis(),r=i.getExtent(),o=Math.min(r[0],r[1]),a=Math.max(r[0],r[1]);i.inverse?o=a-360:a=o+360;var s=Math.sqrt(e*e+n*n);e/=s,n/=s;for(var l=Math.atan2(-n,e)/Math.PI*180,u=la;)l+=360*u;return[s,l]},t.prototype.coordToPoint=function(t){var e=t[0],n=t[1]/180*Math.PI;return[Math.cos(n)*e+this.cx,-Math.sin(n)*e+this.cy]},t.prototype.getArea=function(){var t=this.getAngleAxis(),e=this.getRadiusAxis().getExtent().slice();e[0]>e[1]&&e.reverse();var n=t.getExtent(),i=Math.PI/180,r=1e-4;return{cx:this.cx,cy:this.cy,r0:e[0],r:e[1],startAngle:-n[0]*i,endAngle:-n[1]*i,clockwise:t.inverse,contain:function(t,e){var n=t-this.cx,i=e-this.cy,o=n*n+i*i,a=this.r,s=this.r0;return a!==s&&o-r<=a*a&&o+r>=s*s}}},t.prototype.convertToPixel=function(t,e,n){return tE(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return tE(e)===this?this.pointToData(n):null},t}();function tE(t){var e=t.seriesModel,n=t.polarModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}function eE(t,e){var n=this,i=n.getAngleAxis(),r=n.getRadiusAxis();if(i.scale.setExtent(1/0,-1/0),r.scale.setExtent(1/0,-1/0),t.eachSeries((function(t){if(t.coordinateSystem===n){var e=t.getData();E(C_(e,"radius"),(function(t){r.scale.unionExtentFromData(e,t)})),E(C_(e,"angle"),(function(t){i.scale.unionExtentFromData(e,t)}))}})),__(i.scale,i.model),__(r.scale,r.model),"category"===i.type&&!i.onBand){var o=i.getExtent(),a=360/i.scale.count();i.inverse?o[1]+=a:o[1]-=a,i.setExtent(o[0],o[1])}}function nE(t,e){var n;if(t.type=e.get("type"),t.scale=b_(e),t.onBand=e.get("boundaryGap")&&"category"===t.type,t.inverse=e.get("inverse"),function(t){return"angleAxis"===t.mainType}(e)){t.inverse=t.inverse!==e.get("clockwise");var i=e.get("startAngle"),r=null!==(n=e.get("endAngle"))&&void 0!==n?n:i+(t.inverse?-360:360);t.setExtent(i,r)}e.axis=t,t.model=e}var iE={dimensions:JN,create:function(t,e){var n=[];return t.eachComponent("polar",(function(t,i){var r=new QN(i+"");r.update=eE;var o=r.getRadiusAxis(),a=r.getAngleAxis(),s=t.findAxisModel("radiusAxis"),l=t.findAxisModel("angleAxis");nE(o,s),nE(a,l),function(t,e,n){var i=e.get("center"),r=n.getWidth(),o=n.getHeight();t.cx=$r(i[0],r),t.cy=$r(i[1],o);var a=t.getRadiusAxis(),s=Math.min(r,o)/2,l=e.get("radius");null==l?l=[0,"100%"]:Y(l)||(l=[0,l]);var u=[$r(l[0],s),$r(l[1],s)];a.inverse?a.setExtent(u[1],u[0]):a.setExtent(u[0],u[1])}(r,t,e),n.push(r),t.coordinateSystem=r,r.model=t})),t.eachSeries((function(t){if("polar"===t.get("coordinateSystem")){var e=t.getReferringComponents("polar",Wo).models[0];0,t.coordinateSystem=e.coordinateSystem}})),n}},rE=["axisLine","axisLabel","axisTick","minorTick","splitLine","minorSplitLine","splitArea"];function oE(t,e,n){e[1]>e[0]&&(e=e.slice().reverse());var i=t.coordToPoint([e[0],n]),r=t.coordToPoint([e[1],n]);return{x1:i[0],y1:i[1],x2:r[0],y2:r[1]}}function aE(t){return t.getRadiusAxis().inverse?0:1}function sE(t){var e=t[0],n=t[t.length-1];e&&n&&Math.abs(Math.abs(e.coord-n.coord)-360)<1e-4&&t.pop()}var lE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass="PolarAxisPointer",n}return n(e,t),e.prototype.render=function(t,e){if(this.group.removeAll(),t.get("show")){var n=t.axis,i=n.polar,r=i.getRadiusAxis().getExtent(),o=n.getTicksCoords(),a=n.getMinorTicksCoords(),s=z(n.getViewLabels(),(function(t){t=T(t);var e=n.scale,i="ordinal"===e.type?e.getRawOrdinalNumber(t.tickValue):t.tickValue;return t.coord=n.dataToCoord(i),t}));sE(s),sE(o),E(rE,(function(e){!t.get([e,"show"])||n.scale.isBlank()&&"axisLine"!==e||uE[e](this.group,t,i,o,a,r,s)}),this)}},e.type="angleAxis",e}(TI),uE={axisLine:function(t,e,n,i,r,o){var a,s=e.getModel(["axisLine","lineStyle"]),l=n.getAngleAxis(),u=Math.PI/180,h=l.getExtent(),c=aE(n),p=c?0:1,d=360===Math.abs(h[1]-h[0])?"Circle":"Arc";(a=0===o[p]?new Qh[d]({shape:{cx:n.cx,cy:n.cy,r:o[c],startAngle:-h[0]*u,endAngle:-h[1]*u,clockwise:l.inverse},style:s.getLineStyle(),z2:1,silent:!0}):new Wu({shape:{cx:n.cx,cy:n.cy,r:o[c],r0:o[p]},style:s.getLineStyle(),z2:1,silent:!0})).style.fill=null,t.add(a)},axisTick:function(t,e,n,i,r,o){var a=e.getModel("axisTick"),s=(a.get("inside")?-1:1)*a.get("length"),l=o[aE(n)],u=z(i,(function(t){return new Ku({shape:oE(n,[l,l+s],t.coord)})}));t.add(Nh(u,{style:k(a.getModel("lineStyle").getLineStyle(),{stroke:e.get(["axisLine","lineStyle","color"])})}))},minorTick:function(t,e,n,i,r,o){if(r.length){for(var a=e.getModel("axisTick"),s=e.getModel("minorTick"),l=(a.get("inside")?-1:1)*s.get("length"),u=o[aE(n)],h=[],c=0;cf?"left":"right",v=Math.abs(d[1]-g)/p<.3?"middle":d[1]>g?"top":"bottom";if(s&&s[c]){var m=s[c];q(m)&&m.textStyle&&(a=new Cc(m.textStyle,l,l.ecModel))}var x=new Xs({silent:dI.isLabelSilent(e),style:oc(a,{x:d[0],y:d[1],fill:a.getTextColor()||e.get(["axisLine","lineStyle","color"]),text:i.formattedLabel,align:y,verticalAlign:v})});if(t.add(x),h){var _=dI.makeAxisEventDataBase(e);_.targetType="axisLabel",_.value=i.rawLabel,rl(x).eventData=_}}),this)},splitLine:function(t,e,n,i,r,o){var a=e.getModel("splitLine").getModel("lineStyle"),s=a.get("color"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=0;h=0?"p":"n",C=b;m&&(i[s][I]||(i[s][I]={p:b,n:b}),C=i[s][I][T]);var D=void 0,A=void 0,k=void 0,L=void 0;if("radius"===c.dim){var P=c.dataToCoord(M)-b,O=o.dataToCoord(I);Math.abs(P)=L})}}}))}var vE={startAngle:90,clockwise:!0,splitNumber:12,axisLabel:{rotate:0}},mE={splitNumber:5},xE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="polar",e}(Ag);function _E(t,e){e=e||{};var n=t.coordinateSystem,i=t.axis,r={},o=i.position,a=i.orient,s=n.getRect(),l=[s.x,s.x+s.width,s.y,s.y+s.height],u={horizontal:{top:l[2],bottom:l[3]},vertical:{left:l[0],right:l[1]}};r.position=["vertical"===a?u.vertical[o]:l[0],"horizontal"===a?u.horizontal[o]:l[3]];r.rotation=Math.PI/2*{horizontal:0,vertical:1}[a];r.labelDirection=r.tickDirection=r.nameDirection={top:-1,bottom:1,right:1,left:-1}[o],t.get(["axisTick","inside"])&&(r.tickDirection=-r.tickDirection),it(e.labelInside,t.get(["axisLabel","inside"]))&&(r.labelDirection=-r.labelDirection);var h=e.rotate;return null==h&&(h=t.get(["axisLabel","rotate"])),r.labelRotation="top"===o?-h:h,r.z2=1,r}var bE=["axisLine","axisTickLabel","axisName"],wE=["splitArea","splitLine"],SE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass="SingleAxisPointer",n}return n(e,t),e.prototype.render=function(e,n,i,r){var o=this.group;o.removeAll();var a=this._axisGroup;this._axisGroup=new Br;var s=_E(e),l=new dI(e,s);E(bE,l.add,l),o.add(this._axisGroup),o.add(l.getGroup()),E(wE,(function(t){e.get([t,"show"])&&ME[t](this,this.group,this._axisGroup,e)}),this),Hh(a,this._axisGroup,e),t.prototype.render.call(this,e,n,i,r)},e.prototype.remove=function(){AI(this)},e.type="singleAxis",e}(TI),ME={splitLine:function(t,e,n,i){var r=i.axis;if(!r.scale.isBlank()){var o=i.getModel("splitLine"),a=o.getModel("lineStyle"),s=a.get("color");s=s instanceof Array?s:[s];for(var l=a.get("width"),u=i.coordinateSystem.getRect(),h=r.isHorizontal(),c=[],p=0,d=r.getTicksCoords({tickModel:o}),f=[],g=[],y=0;y=e.y&&t[1]<=e.y+e.height:n.contain(n.toLocalCoord(t[1]))&&t[0]>=e.y&&t[0]<=e.y+e.height},t.prototype.pointToData=function(t){var e=this.getAxis();return[e.coordToData(e.toLocalCoord(t["horizontal"===e.orient?0:1]))]},t.prototype.dataToPoint=function(t){var e=this.getAxis(),n=this.getRect(),i=[],r="horizontal"===e.orient?0:1;return t instanceof Array&&(t=t[0]),i[r]=e.toGlobalCoord(e.dataToCoord(+t)),i[1-r]=0===r?n.y+n.height/2:n.x+n.width/2,i},t.prototype.convertToPixel=function(t,e,n){return AE(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return AE(e)===this?this.pointToData(n):null},t}();function AE(t){var e=t.seriesModel,n=t.singleAxisModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}var kE={create:function(t,e){var n=[];return t.eachComponent("singleAxis",(function(i,r){var o=new DE(i,t,e);o.name="single_"+r,o.resize(i,e),i.coordinateSystem=o,n.push(o)})),t.eachSeries((function(t){if("singleAxis"===t.get("coordinateSystem")){var e=t.getReferringComponents("singleAxis",Wo).models[0];t.coordinateSystem=e&&e.coordinateSystem}})),n},dimensions:CE},LE=["x","y"],PE=["width","height"],OE=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.coordinateSystem,s=EE(a,1-NE(o)),l=a.dataToPoint(e)[0],u=i.get("type");if(u&&"none"!==u){var h=fN(i),c=RE[u](o,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}mN(e,t,_E(n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=_E(e,{labelInside:!1});i.labelMargin=n.get(["handle","margin"]);var r=vN(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.coordinateSystem,a=NE(r),s=EE(o,a),l=[t.x,t.y];l[a]+=e[a],l[a]=Math.min(s[1],l[a]),l[a]=Math.max(s[0],l[a]);var u=EE(o,1-a),h=(u[1]+u[0])/2,c=[h,h];return c[a]=l[a],{x:l[0],y:l[1],rotation:t.rotation,cursorPoint:c,tooltipOption:{verticalAlign:"middle"}}},e}(lN),RE={line:function(t,e,n){return{type:"Line",subPixelOptimize:!0,shape:xN([e,n[0]],[e,n[1]],NE(t))}},shadow:function(t,e,n){var i=t.getBandWidth(),r=n[1]-n[0];return{type:"Rect",shape:_N([e-i/2,n[0]],[i,r],NE(t))}}};function NE(t){return t.isHorizontal()?0:1}function EE(t,e){var n=t.getRect();return[n[LE[e]],n[LE[e]]+n[PE[e]]]}var zE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="single",e}(Ag);var VE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e,n,i){var r=Rp(e);t.prototype.init.apply(this,arguments),BE(e,r)},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),BE(this.option,e)},e.prototype.getCellSize=function(){return this.option.cellSize},e.type="calendar",e.defaultOption={z:2,left:80,top:60,cellSize:20,orient:"horizontal",splitLine:{show:!0,lineStyle:{color:"#000",width:1,type:"solid"}},itemStyle:{color:"#fff",borderWidth:1,borderColor:"#ccc"},dayLabel:{show:!0,firstDay:0,position:"start",margin:"50%",color:"#000"},monthLabel:{show:!0,position:"start",margin:5,align:"center",formatter:null,color:"#000"},yearLabel:{show:!0,position:null,margin:30,formatter:null,color:"#ccc",fontFamily:"sans-serif",fontWeight:"bolder",fontSize:20}},e}(zp);function BE(t,e){var n,i=t.cellSize;1===(n=Y(i)?i:t.cellSize=[i,i]).length&&(n[1]=n[0]);var r=z([0,1],(function(t){return function(t,e){return null!=t[Cp[e][0]]||null!=t[Cp[e][1]]&&null!=t[Cp[e][2]]}(e,t)&&(n[t]="auto"),null!=n[t]&&"auto"!==n[t]}));Op(t,e,{type:"box",ignoreSize:r})}var FE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=this.group;i.removeAll();var r=t.coordinateSystem,o=r.getRangeInfo(),a=r.getOrient(),s=e.getLocaleModel();this._renderDayRect(t,o,i),this._renderLines(t,o,a,i),this._renderYearText(t,o,a,i),this._renderMonthText(t,s,a,i),this._renderWeekText(t,s,o,a,i)},e.prototype._renderDayRect=function(t,e,n){for(var i=t.coordinateSystem,r=t.getModel("itemStyle").getItemStyle(),o=i.getCellWidth(),a=i.getCellHeight(),s=e.start.time;s<=e.end.time;s=i.getNextNDay(s,1).time){var l=i.dataToRect([s],!1).tl,u=new Ws({shape:{x:l[0],y:l[1],width:o,height:a},cursor:"default",style:r});n.add(u)}},e.prototype._renderLines=function(t,e,n,i){var r=this,o=t.coordinateSystem,a=t.getModel(["splitLine","lineStyle"]).getLineStyle(),s=t.get(["splitLine","show"]),l=a.lineWidth;this._tlpoints=[],this._blpoints=[],this._firstDayOfMonth=[],this._firstDayPoints=[];for(var u=e.start,h=0;u.time<=e.end.time;h++){p(u.formatedDate),0===h&&(u=o.getDateInfo(e.start.y+"-"+e.start.m));var c=u.date;c.setMonth(c.getMonth()+1),u=o.getDateInfo(c)}function p(e){r._firstDayOfMonth.push(o.getDateInfo(e)),r._firstDayPoints.push(o.dataToRect([e],!1).tl);var l=r._getLinePointsOfOneWeek(t,e,n);r._tlpoints.push(l[0]),r._blpoints.push(l[l.length-1]),s&&r._drawSplitline(l,a,i)}p(o.getNextNDay(e.end.time,1).formatedDate),s&&this._drawSplitline(r._getEdgesPoints(r._tlpoints,l,n),a,i),s&&this._drawSplitline(r._getEdgesPoints(r._blpoints,l,n),a,i)},e.prototype._getEdgesPoints=function(t,e,n){var i=[t[0].slice(),t[t.length-1].slice()],r="horizontal"===n?0:1;return i[0][r]=i[0][r]-e/2,i[1][r]=i[1][r]+e/2,i},e.prototype._drawSplitline=function(t,e,n){var i=new Zu({z2:20,shape:{points:t},style:e});n.add(i)},e.prototype._getLinePointsOfOneWeek=function(t,e,n){for(var i=t.coordinateSystem,r=i.getDateInfo(e),o=[],a=0;a<7;a++){var s=i.getNextNDay(r.time,a),l=i.dataToRect([s.time],!1);o[2*s.day]=l.tl,o[2*s.day+1]=l["horizontal"===n?"bl":"tr"]}return o},e.prototype._formatterLabel=function(t,e){return U(t)&&t?(n=t,E(e,(function(t,e){n=n.replace("{"+e+"}",i?re(t):t)})),n):X(t)?t(e):e.nameMap;var n,i},e.prototype._yearTextPositionControl=function(t,e,n,i,r){var o=e[0],a=e[1],s=["center","bottom"];"bottom"===i?(a+=r,s=["center","top"]):"left"===i?o-=r:"right"===i?(o+=r,s=["center","top"]):a-=r;var l=0;return"left"!==i&&"right"!==i||(l=Math.PI/2),{rotation:l,x:o,y:a,style:{align:s[0],verticalAlign:s[1]}}},e.prototype._renderYearText=function(t,e,n,i){var r=t.getModel("yearLabel");if(r.get("show")){var o=r.get("margin"),a=r.get("position");a||(a="horizontal"!==n?"top":"left");var s=[this._tlpoints[this._tlpoints.length-1],this._blpoints[0]],l=(s[0][0]+s[1][0])/2,u=(s[0][1]+s[1][1])/2,h="horizontal"===n?0:1,c={top:[l,s[h][1]],bottom:[l,s[1-h][1]],left:[s[1-h][0],u],right:[s[h][0],u]},p=e.start.y;+e.end.y>+e.start.y&&(p=p+"-"+e.end.y);var d=r.get("formatter"),f={start:e.start.y,end:e.end.y,nameMap:p},g=this._formatterLabel(d,f),y=new Xs({z2:30,style:oc(r,{text:g}),silent:r.get("silent")});y.attr(this._yearTextPositionControl(y,c[a],n,a,o)),i.add(y)}},e.prototype._monthTextPositionControl=function(t,e,n,i,r){var o="left",a="top",s=t[0],l=t[1];return"horizontal"===n?(l+=r,e&&(o="center"),"start"===i&&(a="bottom")):(s+=r,e&&(a="middle"),"start"===i&&(o="right")),{x:s,y:l,align:o,verticalAlign:a}},e.prototype._renderMonthText=function(t,e,n,i){var r=t.getModel("monthLabel");if(r.get("show")){var o=r.get("nameMap"),a=r.get("margin"),s=r.get("position"),l=r.get("align"),u=[this._tlpoints,this._blpoints];o&&!U(o)||(o&&(e=Vc(o)||e),o=e.get(["time","monthAbbr"])||[]);var h="start"===s?0:1,c="horizontal"===n?0:1;a="start"===s?-a:a;for(var p="center"===l,d=r.get("silent"),f=0;f=i.start.time&&n.timea.end.time&&t.reverse(),t},t.prototype._getRangeInfo=function(t){var e,n=[this.getDateInfo(t[0]),this.getDateInfo(t[1])];n[0].time>n[1].time&&(e=!0,n.reverse());var i=Math.floor(n[1].time/GE)-Math.floor(n[0].time/GE)+1,r=new Date(n[0].time),o=r.getDate(),a=n[1].date.getDate();r.setDate(o+i-1);var s=r.getDate();if(s!==a)for(var l=r.getTime()-n[1].time>0?1:-1;(s=r.getDate())!==a&&(r.getTime()-n[1].time)*l>0;)i-=l,r.setDate(s-l);var u=Math.floor((i+n[0].day+6)/7),h=e?1-u:u-1;return e&&n.reverse(),{range:[n[0].formatedDate,n[1].formatedDate],start:n[0],end:n[1],allDay:i,weeks:u,nthWeek:h,fweek:n[0].day,lweek:n[1].day}},t.prototype._getDateByWeeksAndDay=function(t,e,n){var i=this._getRangeInfo(n);if(t>i.weeks||0===t&&ei.lweek)return null;var r=7*(t-1)-i.fweek+e,o=new Date(i.start.time);return o.setDate(+i.start.d+r),this.getDateInfo(o)},t.create=function(e,n){var i=[];return e.eachComponent("calendar",(function(r){var o=new t(r,e,n);i.push(o),r.coordinateSystem=o})),e.eachSeries((function(t){"calendar"===t.get("coordinateSystem")&&(t.coordinateSystem=i[t.get("calendarIndex")||0])})),i},t.dimensions=["time","value"],t}();function HE(t){var e=t.calendarModel,n=t.seriesModel;return e?e.coordinateSystem:n?n.coordinateSystem:null}function YE(t,e){var n;return E(e,(function(e){null!=t[e]&&"auto"!==t[e]&&(n=!0)})),n}var XE=["transition","enterFrom","leaveTo"],UE=XE.concat(["enterAnimation","updateAnimation","leaveAnimation"]);function ZE(t,e,n){if(n&&(!t[n]&&e[n]&&(t[n]={}),t=t[n],e=e[n]),t&&e)for(var i=n?XE:UE,r=0;r=0;l--){var p,d,f;if(f=null!=(d=Ro((p=n[l]).id,null))?r.get(d):null){var g=f.parent,y=(c=KE(g),{}),v=Lp(f,p,g===i?{width:o,height:a}:{width:c.width,height:c.height},null,{hv:p.hv,boundingMode:p.bounding},y);if(!KE(f).isNew&&v){for(var m=p.transition,x={},_=0;_=0)?x[b]=w:f[b]=w}vh(f,x,t,0)}else f.attr(y)}}},e.prototype._clear=function(){var t=this,e=this._elMap;e.each((function(n){tz(n,KE(n).option,e,t._lastGraphicModel)})),this._elMap=yt()},e.prototype.dispose=function(){this._clear()},e.type="graphic",e}(Ag);function JE(t){var e=_t(qE,t)?qE[t]:Lh(t);var n=new e({});return KE(n).type=t,n}function QE(t,e,n,i){var r=JE(n);return e.add(r),i.set(t,r),KE(r).id=t,KE(r).isNew=!0,r}function tz(t,e,n,i){t&&t.parent&&("group"===t.type&&t.traverse((function(t){tz(t,e,n,i)})),vR(t,e,i),n.removeKey(KE(t).id))}function ez(t,e,n,i){t.isGroup||E([["cursor",Da.prototype.cursor],["zlevel",i||0],["z",n||0],["z2",0]],(function(n){var i=n[0];_t(e,i)?t[i]=rt(e[i],n[1]):null==t[i]&&(t[i]=n[1])})),E(G(e),(function(n){if(0===n.indexOf("on")){var i=e[n];t[n]=X(i)?i:null}})),_t(e,"draggable")&&(t.draggable=e.draggable),null!=e.name&&(t.name=e.name),null!=e.id&&(t.id=e.id)}var nz=["x","y","radius","angle","single"],iz=["cartesian2d","polar","singleAxis"];function rz(t){return t+"Axis"}function oz(t,e){var n,i=yt(),r=[],o=yt();t.eachComponent({mainType:"dataZoom",query:e},(function(t){o.get(t.uid)||s(t)}));do{n=!1,t.eachComponent("dataZoom",a)}while(n);function a(t){!o.get(t.uid)&&function(t){var e=!1;return t.eachTargetAxis((function(t,n){var r=i.get(t);r&&r[n]&&(e=!0)})),e}(t)&&(s(t),n=!0)}function s(t){o.set(t.uid,!0),r.push(t),t.eachTargetAxis((function(t,e){(i.get(t)||i.set(t,[]))[e]=!0}))}return r}function az(t){var e=t.ecModel,n={infoList:[],infoMap:yt()};return t.eachTargetAxis((function(t,i){var r=e.getComponent(rz(t),i);if(r){var o=r.getCoordSysModel();if(o){var a=o.uid,s=n.infoMap.get(a);s||(s={model:o,axisModels:[]},n.infoList.push(s),n.infoMap.set(a,s)),s.axisModels.push(r)}}})),n}var sz=function(){function t(){this.indexList=[],this.indexMap=[]}return t.prototype.add=function(t){this.indexMap[t]||(this.indexList.push(t),this.indexMap[t]=!0)},t}(),lz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._autoThrottle=!0,n._noTarget=!0,n._rangePropMode=["percent","percent"],n}return n(e,t),e.prototype.init=function(t,e,n){var i=uz(t);this.settledOption=i,this.mergeDefaultAndTheme(t,n),this._doInit(i)},e.prototype.mergeOption=function(t){var e=uz(t);C(this.option,t,!0),C(this.settledOption,e,!0),this._doInit(e)},e.prototype._doInit=function(t){var e=this.option;this._setDefaultThrottle(t),this._updateRangeUse(t);var n=this.settledOption;E([["start","startValue"],["end","endValue"]],(function(t,i){"value"===this._rangePropMode[i]&&(e[t[0]]=n[t[0]]=null)}),this),this._resetTarget()},e.prototype._resetTarget=function(){var t=this.get("orient",!0),e=this._targetAxisInfoMap=yt();this._fillSpecifiedTargetAxis(e)?this._orient=t||this._makeAutoOrientByTargetAxis():(this._orient=t||"horizontal",this._fillAutoTargetAxisByOrient(e,this._orient)),this._noTarget=!0,e.each((function(t){t.indexList.length&&(this._noTarget=!1)}),this)},e.prototype._fillSpecifiedTargetAxis=function(t){var e=!1;return E(nz,(function(n){var i=this.getReferringComponents(rz(n),Ho);if(i.specified){e=!0;var r=new sz;E(i.models,(function(t){r.add(t.componentIndex)})),t.set(n,r)}}),this),e},e.prototype._fillAutoTargetAxisByOrient=function(t,e){var n=this.ecModel,i=!0;if(i){var r="vertical"===e?"y":"x";o(n.findComponents({mainType:r+"Axis"}),r)}i&&o(n.findComponents({mainType:"singleAxis",filter:function(t){return t.get("orient",!0)===e}}),"single");function o(e,n){var r=e[0];if(r){var o=new sz;if(o.add(r.componentIndex),t.set(n,o),i=!1,"x"===n||"y"===n){var a=r.getReferringComponents("grid",Wo).models[0];a&&E(e,(function(t){r.componentIndex!==t.componentIndex&&a===t.getReferringComponents("grid",Wo).models[0]&&o.add(t.componentIndex)}))}}}i&&E(nz,(function(e){if(i){var r=n.findComponents({mainType:rz(e),filter:function(t){return"category"===t.get("type",!0)}});if(r[0]){var o=new sz;o.add(r[0].componentIndex),t.set(e,o),i=!1}}}),this)},e.prototype._makeAutoOrientByTargetAxis=function(){var t;return this.eachTargetAxis((function(e){!t&&(t=e)}),this),"y"===t?"vertical":"horizontal"},e.prototype._setDefaultThrottle=function(t){if(t.hasOwnProperty("throttle")&&(this._autoThrottle=!1),this._autoThrottle){var e=this.ecModel.option;this.option.throttle=e.animation&&e.animationDurationUpdate>0?100:20}},e.prototype._updateRangeUse=function(t){var e=this._rangePropMode,n=this.get("rangeMode");E([["start","startValue"],["end","endValue"]],(function(i,r){var o=null!=t[i[0]],a=null!=t[i[1]];o&&!a?e[r]="percent":!o&&a?e[r]="value":n?e[r]=n[r]:o&&(e[r]="percent")}))},e.prototype.noTarget=function(){return this._noTarget},e.prototype.getFirstTargetAxisModel=function(){var t;return this.eachTargetAxis((function(e,n){null==t&&(t=this.ecModel.getComponent(rz(e),n))}),this),t},e.prototype.eachTargetAxis=function(t,e){this._targetAxisInfoMap.each((function(n,i){E(n.indexList,(function(n){t.call(e,i,n)}))}))},e.prototype.getAxisProxy=function(t,e){var n=this.getAxisModel(t,e);if(n)return n.__dzAxisProxy},e.prototype.getAxisModel=function(t,e){var n=this._targetAxisInfoMap.get(t);if(n&&n.indexMap[e])return this.ecModel.getComponent(rz(t),e)},e.prototype.setRawRange=function(t){var e=this.option,n=this.settledOption;E([["start","startValue"],["end","endValue"]],(function(i){null==t[i[0]]&&null==t[i[1]]||(e[i[0]]=n[i[0]]=t[i[0]],e[i[1]]=n[i[1]]=t[i[1]])}),this),this._updateRangeUse(t)},e.prototype.setCalculatedRange=function(t){var e=this.option;E(["start","startValue","end","endValue"],(function(n){e[n]=t[n]}))},e.prototype.getPercentRange=function(){var t=this.findRepresentativeAxisProxy();if(t)return t.getDataPercentWindow()},e.prototype.getValueRange=function(t,e){if(null!=t||null!=e)return this.getAxisProxy(t,e).getDataValueWindow();var n=this.findRepresentativeAxisProxy();return n?n.getDataValueWindow():void 0},e.prototype.findRepresentativeAxisProxy=function(t){if(t)return t.__dzAxisProxy;for(var e,n=this._targetAxisInfoMap.keys(),i=0;i=0}(e)){var n=rz(this._dimName),i=e.getReferringComponents(n,Wo).models[0];i&&this._axisIndex===i.componentIndex&&t.push(e)}}),this),t},t.prototype.getAxisModel=function(){return this.ecModel.getComponent(this._dimName+"Axis",this._axisIndex)},t.prototype.getMinMaxSpan=function(){return T(this._minMaxSpan)},t.prototype.calculateDataWindow=function(t){var e,n=this._dataExtent,i=this.getAxisModel().axis.scale,r=this._dataZoomModel.getRangePropMode(),o=[0,100],a=[],s=[];dz(["start","end"],(function(l,u){var h=t[l],c=t[l+"Value"];"percent"===r[u]?(null==h&&(h=o[u]),c=i.parse(Kr(h,o,n))):(e=!0,h=Kr(c=null==c?n[u]:i.parse(c),n,o)),s[u]=null==c||isNaN(c)?n[u]:c,a[u]=null==h||isNaN(h)?o[u]:h})),fz(s),fz(a);var l=this._minMaxSpan;function u(t,e,n,r,o){var a=o?"Span":"ValueSpan";zk(0,t,n,"all",l["min"+a],l["max"+a]);for(var s=0;s<2;s++)e[s]=Kr(t[s],n,r,!0),o&&(e[s]=i.parse(e[s]))}return e?u(s,a,n,o,!1):u(a,s,o,n,!0),{valueWindow:s,percentWindow:a}},t.prototype.reset=function(t){if(t===this._dataZoomModel){var e=this.getTargetSeriesModels();this._dataExtent=function(t,e,n){var i=[1/0,-1/0];dz(n,(function(t){!function(t,e,n){e&&E(C_(e,n),(function(n){var i=e.getApproximateExtent(n);i[0]t[1]&&(t[1]=i[1])}))}(i,t.getData(),e)}));var r=t.getAxisModel(),o=v_(r.axis.scale,r,i).calculate();return[o.min,o.max]}(this,this._dimName,e),this._updateMinMaxSpan();var n=this.calculateDataWindow(t.settledOption);this._valueWindow=n.valueWindow,this._percentWindow=n.percentWindow,this._setAxisModel()}},t.prototype.filterData=function(t,e){if(t===this._dataZoomModel){var n=this._dimName,i=this.getTargetSeriesModels(),r=t.get("filterMode"),o=this._valueWindow;"none"!==r&&dz(i,(function(t){var e=t.getData(),i=e.mapDimensionsAll(n);if(i.length){if("weakFilter"===r){var a=e.getStore(),s=z(i,(function(t){return e.getDimensionIndex(t)}),e);e.filterSelf((function(t){for(var e,n,r,l=0;lo[1];if(h&&!c&&!p)return!0;h&&(r=!0),c&&(e=!0),p&&(n=!0)}return r&&e&&n}))}else dz(i,(function(n){if("empty"===r)t.setData(e=e.map(n,(function(t){return function(t){return t>=o[0]&&t<=o[1]}(t)?t:NaN})));else{var i={};i[n]=o,e.selectRange(i)}}));dz(i,(function(t){e.setApproximateExtent(o,t)}))}}))}},t.prototype._updateMinMaxSpan=function(){var t=this._minMaxSpan={},e=this._dataZoomModel,n=this._dataExtent;dz(["min","max"],(function(i){var r=e.get(i+"Span"),o=e.get(i+"ValueSpan");null!=o&&(o=this.getAxisModel().axis.scale.parse(o)),null!=o?r=Kr(n[0]+o,n,[0,100],!0):null!=r&&(o=Kr(r,[0,100],n,!0)-n[0]),t[i+"Span"]=r,t[i+"ValueSpan"]=o}),this)},t.prototype._setAxisModel=function(){var t=this.getAxisModel(),e=this._percentWindow,n=this._valueWindow;if(e){var i=no(n,[0,500]);i=Math.min(i,20);var r=t.axis.scale.rawExtentInfo;0!==e[0]&&r.setDeterminedMinMax("min",+n[0].toFixed(i)),100!==e[1]&&r.setDeterminedMinMax("max",+n[1].toFixed(i)),r.freeze()}},t}();var yz={getTargetSeries:function(t){function e(e){t.eachComponent("dataZoom",(function(n){n.eachTargetAxis((function(i,r){var o=t.getComponent(rz(i),r);e(i,r,o,n)}))}))}e((function(t,e,n,i){n.__dzAxisProxy=null}));var n=[];e((function(e,i,r,o){r.__dzAxisProxy||(r.__dzAxisProxy=new gz(e,i,o,t),n.push(r.__dzAxisProxy))}));var i=yt();return E(n,(function(t){E(t.getTargetSeriesModels(),(function(t){i.set(t.uid,t)}))})),i},overallReset:function(t,e){t.eachComponent("dataZoom",(function(t){t.eachTargetAxis((function(e,n){t.getAxisProxy(e,n).reset(t)})),t.eachTargetAxis((function(n,i){t.getAxisProxy(n,i).filterData(t,e)}))})),t.eachComponent("dataZoom",(function(t){var e=t.findRepresentativeAxisProxy();if(e){var n=e.getDataPercentWindow(),i=e.getDataValueWindow();t.setCalculatedRange({start:n[0],end:n[1],startValue:i[0],endValue:i[1]})}}))}};var vz=!1;function mz(t){vz||(vz=!0,t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,yz),function(t){t.registerAction("dataZoom",(function(t,e){E(oz(e,t),(function(e){e.setRawRange({start:t.start,end:t.end,startValue:t.startValue,endValue:t.endValue})}))}))}(t),t.registerSubTypeDefaulter("dataZoom",(function(){return"slider"})))}function xz(t){t.registerComponentModel(hz),t.registerComponentView(pz),mz(t)}var _z=function(){},bz={};function wz(t,e){bz[t]=e}function Sz(t){return bz[t]}var Mz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){t.prototype.optionUpdated.apply(this,arguments);var e=this.ecModel;E(this.option.feature,(function(t,n){var i=Sz(n);i&&(i.getDefaultOption&&(i.defaultOption=i.getDefaultOption(e)),C(t,i.defaultOption))}))},e.type="toolbox",e.layoutMode={type:"box",ignoreSize:!0},e.defaultOption={show:!0,z:6,orient:"horizontal",left:"right",top:"top",backgroundColor:"transparent",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemSize:15,itemGap:8,showTitle:!0,iconStyle:{borderColor:"#666",color:"none"},emphasis:{iconStyle:{borderColor:"#3E98C5"}},tooltip:{show:!1,position:"bottom"}},e}(zp);function Iz(t,e){var n=vp(e.get("padding")),i=e.getItemStyle(["color","opacity"]);return i.fill=e.get("backgroundColor"),t=new Ws({shape:{x:t.x-n[3],y:t.y-n[0],width:t.width+n[1]+n[3],height:t.height+n[0]+n[2],r:e.get("borderRadius")},style:i,silent:!0,z2:-1})}var Tz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this.group;if(r.removeAll(),t.get("show")){var o=+t.get("itemSize"),a="vertical"===t.get("orient"),s=t.get("feature")||{},l=this._features||(this._features={}),u=[];E(s,(function(t,e){u.push(e)})),new Gm(this._featureNames||[],u).add(h).update(h).remove(H(h,null)).execute(),this._featureNames=u,function(t,e,n){var i=e.getBoxLayoutParams(),r=e.get("padding"),o={width:n.getWidth(),height:n.getHeight()},a=kp(i,o,r);Ap(e.get("orient"),t,e.get("itemGap"),a.width,a.height),Lp(t,i,o,r)}(r,t,n),r.add(Iz(r.getBoundingRect(),t)),a||r.eachChild((function(t){var e=t.__title,i=t.ensureState("emphasis"),a=i.textConfig||(i.textConfig={}),s=t.getTextContent(),l=s&&s.ensureState("emphasis");if(l&&!X(l)&&e){var u=l.style||(l.style={}),h=Sr(e,Xs.makeFont(u)),c=t.x+r.x,p=!1;t.y+r.y+o+h.height>n.getHeight()&&(a.position="top",p=!0);var d=p?-5-h.height:o+10;c+h.width/2>n.getWidth()?(a.position=["100%",d],u.align="right"):c-h.width/2<0&&(a.position=[0,d],u.align="left")}}))}function h(h,c){var p,d=u[h],f=u[c],g=s[d],y=new Cc(g,t,t.ecModel);if(i&&null!=i.newTitle&&i.featureName===d&&(g.title=i.newTitle),d&&!f){if(function(t){return 0===t.indexOf("my")}(d))p={onclick:y.option.onclick,featureName:d};else{var v=Sz(d);if(!v)return;p=new v}l[d]=p}else if(!(p=l[f]))return;p.uid=Ac("toolbox-feature"),p.model=y,p.ecModel=e,p.api=n;var m=p instanceof _z;d||!f?!y.get("show")||m&&p.unusable?m&&p.remove&&p.remove(e,n):(!function(i,s,l){var u,h,c=i.getModel("iconStyle"),p=i.getModel(["emphasis","iconStyle"]),d=s instanceof _z&&s.getIcons?s.getIcons():i.get("icon"),f=i.get("title")||{};U(d)?(u={})[l]=d:u=d;U(f)?(h={})[l]=f:h=f;var g=i.iconPaths={};E(u,(function(l,u){var d=Uh(l,{},{x:-o/2,y:-o/2,width:o,height:o});d.setStyle(c.getItemStyle()),d.ensureState("emphasis").style=p.getItemStyle();var f=new Xs({style:{text:h[u],align:p.get("textAlign"),borderRadius:p.get("textBorderRadius"),padding:p.get("textPadding"),fill:null,font:cc({fontStyle:p.get("textFontStyle"),fontFamily:p.get("textFontFamily"),fontSize:p.get("textFontSize"),fontWeight:p.get("textFontWeight")},e)},ignore:!0});d.setTextContent(f),Kh({el:d,componentModel:t,itemName:u,formatterParamsExtra:{title:h[u]}}),d.__title=h[u],d.on("mouseover",(function(){var e=p.getItemStyle(),i=a?null==t.get("right")&&"right"!==t.get("left")?"right":"left":null==t.get("bottom")&&"bottom"!==t.get("top")?"bottom":"top";f.setStyle({fill:p.get("textFill")||e.fill||e.stroke||"#000",backgroundColor:p.get("textBackgroundColor")}),d.setTextConfig({position:p.get("textPosition")||i}),f.ignore=!t.get("showTitle"),n.enterEmphasis(this)})).on("mouseout",(function(){"emphasis"!==i.get(["iconStatus",u])&&n.leaveEmphasis(this),f.hide()})),("emphasis"===i.get(["iconStatus",u])?Ol:Rl)(d),r.add(d),d.on("click",W(s.onclick,s,e,n,u)),g[u]=d}))}(y,p,d),y.setIconStatus=function(t,e){var n=this.option,i=this.iconPaths;n.iconStatus=n.iconStatus||{},n.iconStatus[t]=e,i[t]&&("emphasis"===e?Ol:Rl)(i[t])},p instanceof _z&&p.render&&p.render(y,e,n,i)):m&&p.dispose&&p.dispose(e,n)}},e.prototype.updateView=function(t,e,n,i){E(this._features,(function(t){t instanceof _z&&t.updateView&&t.updateView(t.model,e,n,i)}))},e.prototype.remove=function(t,e){E(this._features,(function(n){n instanceof _z&&n.remove&&n.remove(t,e)})),this.group.removeAll()},e.prototype.dispose=function(t,e){E(this._features,(function(n){n instanceof _z&&n.dispose&&n.dispose(t,e)}))},e.type="toolbox",e}(Ag);var Cz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.onclick=function(t,e){var n=this.model,i=n.get("name")||t.get("title.0.text")||"echarts",o="svg"===e.getZr().painter.getType(),a=o?"svg":n.get("type",!0)||"png",s=e.getConnectedDataURL({type:a,backgroundColor:n.get("backgroundColor",!0)||t.get("backgroundColor")||"#fff",connectedBackgroundColor:n.get("connectedBackgroundColor"),excludeComponents:n.get("excludeComponents"),pixelRatio:n.get("pixelRatio")}),l=r.browser;if("function"!=typeof MouseEvent||!l.newEdge&&(l.ie||l.edge))if(window.navigator.msSaveOrOpenBlob||o){var u=s.split(","),h=u[0].indexOf("base64")>-1,c=o?decodeURIComponent(u[1]):u[1];h&&(c=window.atob(c));var p=i+"."+a;if(window.navigator.msSaveOrOpenBlob){for(var d=c.length,f=new Uint8Array(d);d--;)f[d]=c.charCodeAt(d);var g=new Blob([f]);window.navigator.msSaveOrOpenBlob(g,p)}else{var y=document.createElement("iframe");document.body.appendChild(y);var v=y.contentWindow,m=v.document;m.open("image/svg+xml","replace"),m.write(c),m.close(),v.focus(),m.execCommand("SaveAs",!0,p),document.body.removeChild(y)}}else{var x=n.get("lang"),_='',b=window.open();b.document.write(_),b.document.title=i}else{var w=document.createElement("a");w.download=i+"."+a,w.target="_blank",w.href=s;var S=new MouseEvent("click",{view:document.defaultView,bubbles:!0,cancelable:!1});w.dispatchEvent(S)}},e.getDefaultOption=function(t){return{show:!0,icon:"M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0",title:t.getLocaleModel().get(["toolbox","saveAsImage","title"]),type:"png",connectedBackgroundColor:"#fff",name:"",excludeComponents:["toolbox"],lang:t.getLocaleModel().get(["toolbox","saveAsImage","lang"])}},e}(_z),Dz="__ec_magicType_stack__",Az=[["line","bar"],["stack"]],kz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getIcons=function(){var t=this.model,e=t.get("icon"),n={};return E(t.get("type"),(function(t){e[t]&&(n[t]=e[t])})),n},e.getDefaultOption=function(t){return{show:!0,type:[],icon:{line:"M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4",bar:"M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7",stack:"M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z"},title:t.getLocaleModel().get(["toolbox","magicType","title"]),option:{},seriesIndex:{}}},e.prototype.onclick=function(t,e,n){var i=this.model,r=i.get(["seriesIndex",n]);if(Lz[n]){var o,a={series:[]};E(Az,(function(t){P(t,n)>=0&&E(t,(function(t){i.setIconStatus(t,"normal")}))})),i.setIconStatus(n,"emphasis"),t.eachComponent({mainType:"series",query:null==r?null:{seriesIndex:r}},(function(t){var e=t.subType,r=t.id,o=Lz[n](e,r,t,i);o&&(k(o,t.option),a.series.push(o));var s=t.coordinateSystem;if(s&&"cartesian2d"===s.type&&("line"===n||"bar"===n)){var l=s.getAxesByScale("ordinal")[0];if(l){var u=l.dim+"Axis",h=t.getReferringComponents(u,Wo).models[0].componentIndex;a[u]=a[u]||[];for(var c=0;c<=h;c++)a[u][h]=a[u][h]||{};a[u][h].boundaryGap="bar"===n}}}));var s=n;"stack"===n&&(o=C({stack:i.option.title.tiled,tiled:i.option.title.stack},i.option.title),"emphasis"!==i.get(["iconStatus",n])&&(s="tiled")),e.dispatchAction({type:"changeMagicType",currentType:s,newOption:a,newTitle:o,featureName:"magicType"})}},e}(_z),Lz={line:function(t,e,n,i){if("bar"===t)return C({id:e,type:"line",data:n.get("data"),stack:n.get("stack"),markPoint:n.get("markPoint"),markLine:n.get("markLine")},i.get(["option","line"])||{},!0)},bar:function(t,e,n,i){if("line"===t)return C({id:e,type:"bar",data:n.get("data"),stack:n.get("stack"),markPoint:n.get("markPoint"),markLine:n.get("markLine")},i.get(["option","bar"])||{},!0)},stack:function(t,e,n,i){var r=n.get("stack")===Dz;if("line"===t||"bar"===t)return i.setIconStatus("stack",r?"normal":"emphasis"),C({id:e,stack:r?"":Dz},i.get(["option","stack"])||{},!0)}};Cm({type:"changeMagicType",event:"magicTypeChanged",update:"prepareAndUpdate"},(function(t,e){e.mergeOption(t.newOption)}));var Pz=new Array(60).join("-"),Oz="\t";function Rz(t){return t.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}var Nz=new RegExp("[\t]+","g");function Ez(t,e){var n=t.split(new RegExp("\n*"+Pz+"\n*","g")),i={series:[]};return E(n,(function(t,n){if(function(t){if(t.slice(0,t.indexOf("\n")).indexOf(Oz)>=0)return!0}(t)){var r=function(t){for(var e=t.split(/\n+/g),n=[],i=z(Rz(e.shift()).split(Nz),(function(t){return{name:t,data:[]}})),r=0;r=0)&&t(r,i._targetInfoList)}))}return t.prototype.setOutputRanges=function(t,e){return this.matchOutputRanges(t,e,(function(t,e,n){if((t.coordRanges||(t.coordRanges=[])).push(e),!t.coordRange){t.coordRange=e;var i=Kz[t.brushType](0,n,e);t.__rangeOffset={offset:Jz[t.brushType](i.values,t.range,[1,1]),xyMinMax:i.xyMinMax}}})),t},t.prototype.matchOutputRanges=function(t,e,n){E(t,(function(t){var i=this.findTargetInfo(t,e);i&&!0!==i&&E(i.coordSyses,(function(i){var r=Kz[t.brushType](1,i,t.range,!0);n(t,r.values,i,e)}))}),this)},t.prototype.setInputRanges=function(t,e){E(t,(function(t){var n,i,r,o,a,s=this.findTargetInfo(t,e);if(t.range=t.range||[],s&&!0!==s){t.panelId=s.panelId;var l=Kz[t.brushType](0,s.coordSys,t.coordRange),u=t.__rangeOffset;t.range=u?Jz[t.brushType](l.values,u.offset,(n=l.xyMinMax,i=u.xyMinMax,r=tV(n),o=tV(i),a=[r[0]/o[0],r[1]/o[1]],isNaN(a[0])&&(a[0]=1),isNaN(a[1])&&(a[1]=1),a)):l.values}}),this)},t.prototype.makePanelOpts=function(t,e){return z(this._targetInfoList,(function(n){var i=n.getPanelRect();return{panelId:n.panelId,defaultBrushType:e?e(n):null,clipPath:BL(i),isTargetByCursor:GL(i,t,n.coordSysModel),getLinearBrushOtherExtent:FL(i)}}))},t.prototype.controlSeries=function(t,e,n){var i=this.findTargetInfo(t,n);return!0===i||i&&P(i.coordSyses,e.coordinateSystem)>=0},t.prototype.findTargetInfo=function(t,e){for(var n=this._targetInfoList,i=Uz(e,t),r=0;rt[1]&&t.reverse(),t}function Uz(t,e){return Fo(t,e,{includeMainTypes:Hz})}var Zz={grid:function(t,e){var n=t.xAxisModels,i=t.yAxisModels,r=t.gridModels,o=yt(),a={},s={};(n||i||r)&&(E(n,(function(t){var e=t.axis.grid.model;o.set(e.id,e),a[e.id]=!0})),E(i,(function(t){var e=t.axis.grid.model;o.set(e.id,e),s[e.id]=!0})),E(r,(function(t){o.set(t.id,t),a[t.id]=!0,s[t.id]=!0})),o.each((function(t){var r=t.coordinateSystem,o=[];E(r.getCartesians(),(function(t,e){(P(n,t.getAxis("x").model)>=0||P(i,t.getAxis("y").model)>=0)&&o.push(t)})),e.push({panelId:"grid--"+t.id,gridModel:t,coordSysModel:t,coordSys:o[0],coordSyses:o,getPanelRect:qz.grid,xAxisDeclared:a[t.id],yAxisDeclared:s[t.id]})})))},geo:function(t,e){E(t.geoModels,(function(t){var n=t.coordinateSystem;e.push({panelId:"geo--"+t.id,geoModel:t,coordSysModel:t,coordSys:n,coordSyses:[n],getPanelRect:qz.geo})}))}},jz=[function(t,e){var n=t.xAxisModel,i=t.yAxisModel,r=t.gridModel;return!r&&n&&(r=n.axis.grid.model),!r&&i&&(r=i.axis.grid.model),r&&r===e.gridModel},function(t,e){var n=t.geoModel;return n&&n===e.geoModel}],qz={grid:function(){return this.coordSys.master.getRect().clone()},geo:function(){var t=this.coordSys,e=t.getBoundingRect().clone();return e.applyTransform(Bh(t)),e}},Kz={lineX:H($z,0),lineY:H($z,1),rect:function(t,e,n,i){var r=t?e.pointToData([n[0][0],n[1][0]],i):e.dataToPoint([n[0][0],n[1][0]],i),o=t?e.pointToData([n[0][1],n[1][1]],i):e.dataToPoint([n[0][1],n[1][1]],i),a=[Xz([r[0],o[0]]),Xz([r[1],o[1]])];return{values:a,xyMinMax:a}},polygon:function(t,e,n,i){var r=[[1/0,-1/0],[1/0,-1/0]];return{values:z(n,(function(n){var o=t?e.pointToData(n,i):e.dataToPoint(n,i);return r[0][0]=Math.min(r[0][0],o[0]),r[1][0]=Math.min(r[1][0],o[1]),r[0][1]=Math.max(r[0][1],o[0]),r[1][1]=Math.max(r[1][1],o[1]),o})),xyMinMax:r}}};function $z(t,e,n,i){var r=n.getAxis(["x","y"][t]),o=Xz(z([0,1],(function(t){return e?r.coordToData(r.toLocalCoord(i[t]),!0):r.toGlobalCoord(r.dataToCoord(i[t]))}))),a=[];return a[t]=o,a[1-t]=[NaN,NaN],{values:o,xyMinMax:a}}var Jz={lineX:H(Qz,0),lineY:H(Qz,1),rect:function(t,e,n){return[[t[0][0]-n[0]*e[0][0],t[0][1]-n[0]*e[0][1]],[t[1][0]-n[1]*e[1][0],t[1][1]-n[1]*e[1][1]]]},polygon:function(t,e,n){return z(t,(function(t,i){return[t[0]-n[0]*e[i][0],t[1]-n[1]*e[i][1]]}))}};function Qz(t,e,n,i){return[e[0]-i[t]*n[0],e[1]-i[t]*n[1]]}function tV(t){return t?[t[0][1]-t[0][0],t[1][1]-t[1][0]]:[NaN,NaN]}var eV,nV,iV=E,rV=Io+"toolbox-dataZoom_",oV=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){this._brushController||(this._brushController=new lL(n.getZr()),this._brushController.on("brush",W(this._onBrush,this)).mount()),function(t,e,n,i,r){var o=n._isZoomActive;i&&"takeGlobalCursor"===i.type&&(o="dataZoomSelect"===i.key&&i.dataZoomSelectActive);n._isZoomActive=o,t.setIconStatus("zoom",o?"emphasis":"normal");var a=new Yz(sV(t),e,{include:["grid"]}),s=a.makePanelOpts(r,(function(t){return t.xAxisDeclared&&!t.yAxisDeclared?"lineX":!t.xAxisDeclared&&t.yAxisDeclared?"lineY":"rect"}));n._brushController.setPanels(s).enableBrush(!(!o||!s.length)&&{brushType:"auto",brushStyle:t.getModel("brushStyle").getItemStyle()})}(t,e,this,i,n),function(t,e){t.setIconStatus("back",function(t){return Gz(t).length}(e)>1?"emphasis":"normal")}(t,e)},e.prototype.onclick=function(t,e,n){aV[n].call(this)},e.prototype.remove=function(t,e){this._brushController&&this._brushController.unmount()},e.prototype.dispose=function(t,e){this._brushController&&this._brushController.dispose()},e.prototype._onBrush=function(t){var e=t.areas;if(t.isEnd&&e.length){var n={},i=this.ecModel;this._brushController.updateCovers([]),new Yz(sV(this.model),i,{include:["grid"]}).matchOutputRanges(e,i,(function(t,e,n){if("cartesian2d"===n.type){var i=t.brushType;"rect"===i?(r("x",n,e[0]),r("y",n,e[1])):r({lineX:"x",lineY:"y"}[i],n,e)}})),function(t,e){var n=Gz(t);Bz(e,(function(e,i){for(var r=n.length-1;r>=0&&!n[r][i];r--);if(r<0){var o=t.queryComponents({mainType:"dataZoom",subType:"select",id:i})[0];if(o){var a=o.getPercentRange();n[0][i]={dataZoomId:i,start:a[0],end:a[1]}}}})),n.push(e)}(i,n),this._dispatchZoomAction(n)}function r(t,e,r){var o=e.getAxis(t),a=o.model,s=function(t,e,n){var i;return n.eachComponent({mainType:"dataZoom",subType:"select"},(function(n){n.getAxisModel(t,e.componentIndex)&&(i=n)})),i}(t,a,i),l=s.findRepresentativeAxisProxy(a).getMinMaxSpan();null==l.minValueSpan&&null==l.maxValueSpan||(r=zk(0,r.slice(),o.scale.getExtent(),0,l.minValueSpan,l.maxValueSpan)),s&&(n[s.id]={dataZoomId:s.id,startValue:r[0],endValue:r[1]})}},e.prototype._dispatchZoomAction=function(t){var e=[];iV(t,(function(t,n){e.push(T(t))})),e.length&&this.api.dispatchAction({type:"dataZoom",from:this.uid,batch:e})},e.getDefaultOption=function(t){return{show:!0,filterMode:"filter",icon:{zoom:"M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1",back:"M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26"},title:t.getLocaleModel().get(["toolbox","dataZoom","title"]),brushStyle:{borderWidth:0,color:"rgba(210,219,238,0.2)"}}},e}(_z),aV={zoom:function(){var t=!this._isZoomActive;this.api.dispatchAction({type:"takeGlobalCursor",key:"dataZoomSelect",dataZoomSelectActive:t})},back:function(){this._dispatchZoomAction(function(t){var e=Gz(t),n=e[e.length-1];e.length>1&&e.pop();var i={};return Bz(n,(function(t,n){for(var r=e.length-1;r>=0;r--)if(t=e[r][n]){i[n]=t;break}})),i}(this.ecModel))}};function sV(t){var e={xAxisIndex:t.get("xAxisIndex",!0),yAxisIndex:t.get("yAxisIndex",!0),xAxisId:t.get("xAxisId",!0),yAxisId:t.get("yAxisId",!0)};return null==e.xAxisIndex&&null==e.xAxisId&&(e.xAxisIndex="all"),null==e.yAxisIndex&&null==e.yAxisId&&(e.yAxisIndex="all"),e}eV="dataZoom",nV=function(t){var e=t.getComponent("toolbox",0),n=["feature","dataZoom"];if(e&&null!=e.get(n)){var i=e.getModel(n),r=[],o=Fo(t,sV(i));return iV(o.xAxisModels,(function(t){return a(t,"xAxis","xAxisIndex")})),iV(o.yAxisModels,(function(t){return a(t,"yAxis","yAxisIndex")})),r}function a(t,e,n){var o=t.componentIndex,a={type:"select",$fromToolbox:!0,filterMode:i.get("filterMode",!0)||"filter",id:rV+e+o};a[n]=o,r.push(a)}},lt(null==od.get(eV)&&nV),od.set(eV,nV);var lV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="tooltip",e.dependencies=["axisPointer"],e.defaultOption={z:60,show:!0,showContent:!0,trigger:"item",triggerOn:"mousemove|click",alwaysShowContent:!1,displayMode:"single",renderMode:"auto",confine:null,showDelay:0,hideDelay:100,transitionDuration:.4,enterable:!1,backgroundColor:"#fff",shadowBlur:10,shadowColor:"rgba(0, 0, 0, .2)",shadowOffsetX:1,shadowOffsetY:2,borderRadius:4,borderWidth:1,padding:null,extraCssText:"",axisPointer:{type:"line",axis:"auto",animation:"auto",animationDurationUpdate:200,animationEasingUpdate:"exponentialOut",crossStyle:{color:"#999",width:1,type:"dashed",textStyle:{}}},textStyle:{color:"#666",fontSize:14}},e}(zp);function uV(t){var e=t.get("confine");return null!=e?!!e:"richText"===t.get("renderMode")}function hV(t){if(r.domSupported)for(var e=document.documentElement.style,n=0,i=t.length;n-1?(u+="top:50%",h+="translateY(-50%) rotate("+(a="left"===s?-225:-45)+"deg)"):(u+="left:50%",h+="translateX(-50%) rotate("+(a="top"===s?225:45)+"deg)");var c=a*Math.PI/180,p=l+r,d=p*Math.abs(Math.cos(c))+p*Math.abs(Math.sin(c)),f=e+" solid "+r+"px;";return'
'}(n,i,r)),U(t))o.innerHTML=t+a;else if(t){o.innerHTML="",Y(t)||(t=[t]);for(var s=0;s=0?this._tryShow(n,i):"leave"===e&&this._hide(i))}),this))},e.prototype._keepShow=function(){var t=this._tooltipModel,e=this._ecModel,n=this._api,i=t.get("triggerOn");if(null!=this._lastX&&null!=this._lastY&&"none"!==i&&"click"!==i){var r=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout((function(){!n.isDisposed()&&r.manuallyShowTip(t,e,n,{x:r._lastX,y:r._lastY,dataByCoordSys:r._lastDataByCoordSys})}))}},e.prototype.manuallyShowTip=function(t,e,n,i){if(i.from!==this.uid&&!r.node&&n.getDom()){var o=CV(i,n);this._ticket="";var a=i.dataByCoordSys,s=function(t,e,n){var i=Go(t).queryOptionMap,r=i.keys()[0];if(!r||"series"===r)return;var o=Yo(e,r,i.get(r),{useDefault:!1,enableAll:!1,enableNone:!1}),a=o.models[0];if(!a)return;var s,l=n.getViewOfComponentModel(a);if(l.group.traverse((function(e){var n=rl(e).tooltipConfig;if(n&&n.name===t.name)return s=e,!0})),s)return{componentMainType:r,componentIndex:a.componentIndex,el:s}}(i,e,n);if(s){var l=s.el.getBoundingRect().clone();l.applyTransform(s.el.transform),this._tryShow({offsetX:l.x+l.width/2,offsetY:l.y+l.height/2,target:s.el,position:i.position,positionDefault:"bottom"},o)}else if(i.tooltip&&null!=i.x&&null!=i.y){var u=MV;u.x=i.x,u.y=i.y,u.update(),rl(u).tooltipConfig={name:null,option:i.tooltip},this._tryShow({offsetX:i.x,offsetY:i.y,target:u},o)}else if(a)this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,dataByCoordSys:a,tooltipOption:i.tooltipOption},o);else if(null!=i.seriesIndex){if(this._manuallyAxisShowTip(t,e,n,i))return;var h=RN(i,e),c=h.point[0],p=h.point[1];null!=c&&null!=p&&this._tryShow({offsetX:c,offsetY:p,target:h.el,position:i.position,positionDefault:"bottom"},o)}else null!=i.x&&null!=i.y&&(n.dispatchAction({type:"updateAxisPointer",x:i.x,y:i.y}),this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,target:n.getZr().findHover(i.x,i.y).target},o))}},e.prototype.manuallyHideTip=function(t,e,n,i){var r=this._tooltipContent;this._tooltipModel&&r.hideLater(this._tooltipModel.get("hideDelay")),this._lastX=this._lastY=this._lastDataByCoordSys=null,i.from!==this.uid&&this._hide(CV(i,n))},e.prototype._manuallyAxisShowTip=function(t,e,n,i){var r=i.seriesIndex,o=i.dataIndex,a=e.getComponent("axisPointer").coordSysAxesInfo;if(null!=r&&null!=o&&null!=a){var s=e.getSeriesByIndex(r);if(s)if("axis"===TV([s.getData().getItemModel(o),s,(s.coordinateSystem||{}).model],this._tooltipModel).get("trigger"))return n.dispatchAction({type:"updateAxisPointer",seriesIndex:r,dataIndex:o,position:i.position}),!0}},e.prototype._tryShow=function(t,e){var n=t.target;if(this._tooltipModel){this._lastX=t.offsetX,this._lastY=t.offsetY;var i=t.dataByCoordSys;if(i&&i.length)this._showAxisTooltip(i,t);else if(n){var r,o;if("legend"===rl(n).ssrType)return;this._lastDataByCoordSys=null,Oy(n,(function(t){return null!=rl(t).dataIndex?(r=t,!0):null!=rl(t).tooltipConfig?(o=t,!0):void 0}),!0),r?this._showSeriesItemTooltip(t,r,e):o?this._showComponentItemTooltip(t,o,e):this._hide(e)}else this._lastDataByCoordSys=null,this._hide(e)}},e.prototype._showOrMove=function(t,e){var n=t.get("showDelay");e=W(e,this),clearTimeout(this._showTimout),n>0?this._showTimout=setTimeout(e,n):e()},e.prototype._showAxisTooltip=function(t,e){var n=this._ecModel,i=this._tooltipModel,r=[e.offsetX,e.offsetY],o=TV([e.tooltipOption],i),a=this._renderMode,s=[],l=og("section",{blocks:[],noHeader:!0}),u=[],h=new yg;E(t,(function(t){E(t.dataByAxis,(function(t){var e=n.getComponent(t.axisDim+"Axis",t.axisIndex),r=t.value;if(e&&null!=r){var o=yN(r,e.axis,n,t.seriesDataIndices,t.valueLabelOpt),c=og("section",{header:o,noHeader:!ut(o),sortBlocks:!0,blocks:[]});l.blocks.push(c),E(t.seriesDataIndices,(function(l){var p=n.getSeriesByIndex(l.seriesIndex),d=l.dataIndexInside,f=p.getDataParams(d);if(!(f.dataIndex<0)){f.axisDim=t.axisDim,f.axisIndex=t.axisIndex,f.axisType=t.axisType,f.axisId=t.axisId,f.axisValue=S_(e.axis,{value:r}),f.axisValueLabel=o,f.marker=h.makeTooltipMarker("item",Sp(f.color),a);var g=bf(p.formatTooltip(d,!0,null)),y=g.frag;if(y){var v=TV([p],i).get("valueFormatter");c.blocks.push(v?A({valueFormatter:v},y):y)}g.text&&u.push(g.text),s.push(f)}}))}}))})),l.blocks.reverse(),u.reverse();var c=e.position,p=o.get("order"),d=cg(l,h,a,p,n.get("useUTC"),o.get("textStyle"));d&&u.unshift(d);var f="richText"===a?"\n\n":"
",g=u.join(f);this._showOrMove(o,(function(){this._updateContentNotChangedOnAxis(t,s)?this._updatePosition(o,c,r[0],r[1],this._tooltipContent,s):this._showTooltipContent(o,g,s,Math.random()+"",r[0],r[1],c,null,h)}))},e.prototype._showSeriesItemTooltip=function(t,e,n){var i=this._ecModel,r=rl(e),o=r.seriesIndex,a=i.getSeriesByIndex(o),s=r.dataModel||a,l=r.dataIndex,u=r.dataType,h=s.getData(u),c=this._renderMode,p=t.positionDefault,d=TV([h.getItemModel(l),s,a&&(a.coordinateSystem||{}).model],this._tooltipModel,p?{position:p}:null),f=d.get("trigger");if(null==f||"item"===f){var g=s.getDataParams(l,u),y=new yg;g.marker=y.makeTooltipMarker("item",Sp(g.color),c);var v=bf(s.formatTooltip(l,!1,u)),m=d.get("order"),x=d.get("valueFormatter"),_=v.frag,b=_?cg(x?A({valueFormatter:x},_):_,y,c,m,i.get("useUTC"),d.get("textStyle")):v.text,w="item_"+s.name+"_"+l;this._showOrMove(d,(function(){this._showTooltipContent(d,b,g,w,t.offsetX,t.offsetY,t.position,t.target,y)})),n({type:"showTip",dataIndexInside:l,dataIndex:h.getRawIndex(l),seriesIndex:o,from:this.uid})}},e.prototype._showComponentItemTooltip=function(t,e,n){var i="html"===this._renderMode,r=rl(e),o=r.tooltipConfig.option||{},a=o.encodeHTMLContent;if(U(o)){o={content:o,formatter:o},a=!0}a&&i&&o.content&&((o=T(o)).content=re(o.content));var s=[o],l=this._ecModel.getComponent(r.componentMainType,r.componentIndex);l&&s.push(l),s.push({formatter:o.content});var u=t.positionDefault,h=TV(s,this._tooltipModel,u?{position:u}:null),c=h.get("content"),p=Math.random()+"",d=new yg;this._showOrMove(h,(function(){var n=T(h.get("formatterParams")||{});this._showTooltipContent(h,c,n,p,t.offsetX,t.offsetY,t.position,e,d)})),n({type:"showTip",from:this.uid})},e.prototype._showTooltipContent=function(t,e,n,i,r,o,a,s,l){if(this._ticket="",t.get("showContent")&&t.get("show")){var u=this._tooltipContent;u.setEnterable(t.get("enterable"));var h=t.get("formatter");a=a||t.get("position");var c=e,p=this._getNearestPoint([r,o],n,t.get("trigger"),t.get("borderColor")).color;if(h)if(U(h)){var d=t.ecModel.get("useUTC"),f=Y(n)?n[0]:n;c=h,f&&f.axisType&&f.axisType.indexOf("time")>=0&&(c=Jc(f.axisValue,c,d)),c=bp(c,n,!0)}else if(X(h)){var g=W((function(e,i){e===this._ticket&&(u.setContent(i,l,t,p,a),this._updatePosition(t,a,r,o,u,n,s))}),this);this._ticket=i,c=h(n,i,g)}else c=h;u.setContent(c,l,t,p,a),u.show(t,p),this._updatePosition(t,a,r,o,u,n,s)}},e.prototype._getNearestPoint=function(t,e,n,i){return"axis"===n||Y(e)?{color:i||("html"===this._renderMode?"#fff":"none")}:Y(e)?void 0:{color:i||e.color||e.borderColor}},e.prototype._updatePosition=function(t,e,n,i,r,o,a){var s=this._api.getWidth(),l=this._api.getHeight();e=e||t.get("position");var u=r.getSize(),h=t.get("align"),c=t.get("verticalAlign"),p=a&&a.getBoundingRect().clone();if(a&&p.applyTransform(a.transform),X(e)&&(e=e([n,i],o,r.el,p,{viewSize:[s,l],contentSize:u.slice()})),Y(e))n=$r(e[0],s),i=$r(e[1],l);else if(q(e)){var d=e;d.width=u[0],d.height=u[1];var f=kp(d,{width:s,height:l});n=f.x,i=f.y,h=null,c=null}else if(U(e)&&a){var g=function(t,e,n,i){var r=n[0],o=n[1],a=Math.ceil(Math.SQRT2*i)+8,s=0,l=0,u=e.width,h=e.height;switch(t){case"inside":s=e.x+u/2-r/2,l=e.y+h/2-o/2;break;case"top":s=e.x+u/2-r/2,l=e.y-o-a;break;case"bottom":s=e.x+u/2-r/2,l=e.y+h+a;break;case"left":s=e.x-r-a,l=e.y+h/2-o/2;break;case"right":s=e.x+u+a,l=e.y+h/2-o/2}return[s,l]}(e,p,u,t.get("borderWidth"));n=g[0],i=g[1]}else{g=function(t,e,n,i,r,o,a){var s=n.getSize(),l=s[0],u=s[1];null!=o&&(t+l+o+2>i?t-=l+o:t+=o);null!=a&&(e+u+a>r?e-=u+a:e+=a);return[t,e]}(n,i,r,s,l,h?null:20,c?null:20);n=g[0],i=g[1]}if(h&&(n-=DV(h)?u[0]/2:"right"===h?u[0]:0),c&&(i-=DV(c)?u[1]/2:"bottom"===c?u[1]:0),uV(t)){g=function(t,e,n,i,r){var o=n.getSize(),a=o[0],s=o[1];return t=Math.min(t+a,i)-a,e=Math.min(e+s,r)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}(n,i,r,s,l);n=g[0],i=g[1]}r.moveTo(n,i)},e.prototype._updateContentNotChangedOnAxis=function(t,e){var n=this._lastDataByCoordSys,i=this._cbParamsList,r=!!n&&n.length===t.length;return r&&E(n,(function(n,o){var a=n.dataByAxis||[],s=(t[o]||{}).dataByAxis||[];(r=r&&a.length===s.length)&&E(a,(function(t,n){var o=s[n]||{},a=t.seriesDataIndices||[],l=o.seriesDataIndices||[];(r=r&&t.value===o.value&&t.axisType===o.axisType&&t.axisId===o.axisId&&a.length===l.length)&&E(a,(function(t,e){var n=l[e];r=r&&t.seriesIndex===n.seriesIndex&&t.dataIndex===n.dataIndex})),i&&E(t.seriesDataIndices,(function(t){var n=t.seriesIndex,o=e[n],a=i[n];o&&a&&a.data!==o.data&&(r=!1)}))}))})),this._lastDataByCoordSys=t,this._cbParamsList=e,!!r},e.prototype._hide=function(t){this._lastDataByCoordSys=null,t({type:"hideTip",from:this.uid})},e.prototype.dispose=function(t,e){!r.node&&e.getDom()&&(Yg(this,"_updatePosition"),this._tooltipContent.dispose(),PN("itemTooltip",e))},e.type="tooltip",e}(Ag);function TV(t,e,n){var i,r=e.ecModel;n?(i=new Cc(n,r,r),i=new Cc(e.option,i,r)):i=e;for(var o=t.length-1;o>=0;o--){var a=t[o];a&&(a instanceof Cc&&(a=a.get("tooltip",!0)),U(a)&&(a={formatter:a}),a&&(i=new Cc(a,i,r)))}return i}function CV(t,e){return t.dispatchAction||W(e.dispatchAction,e)}function DV(t){return"center"===t||"middle"===t}var AV=["rect","polygon","keep","clear"];function kV(t,e){var n=To(t?t.brush:[]);if(n.length){var i=[];E(n,(function(t){var e=t.hasOwnProperty("toolbox")?t.toolbox:[];e instanceof Array&&(i=i.concat(e))}));var r=t&&t.toolbox;Y(r)&&(r=r[0]),r||(r={feature:{}},t.toolbox=[r]);var o=r.feature||(r.feature={}),a=o.brush||(o.brush={}),s=a.type||(a.type=[]);s.push.apply(s,i),function(t){var e={};E(t,(function(t){e[t]=1})),t.length=0,E(e,(function(e,n){t.push(n)}))}(s),e&&!s.length&&s.push.apply(s,AV)}}var LV=E;function PV(t){if(t)for(var e in t)if(t.hasOwnProperty(e))return!0}function OV(t,e,n){var i={};return LV(e,(function(e){var r,o=i[e]=((r=function(){}).prototype.__hidden=r.prototype,new r);LV(t[e],(function(t,i){if(kD.isValidType(i)){var r={type:i,visual:t};n&&n(r,e),o[i]=new kD(r),"opacity"===i&&((r=T(r)).type="colorAlpha",o.__hidden.__alphaForOpacity=new kD(r))}}))})),i}function RV(t,e,n){var i;E(n,(function(t){e.hasOwnProperty(t)&&PV(e[t])&&(i=!0)})),i&&E(n,(function(n){e.hasOwnProperty(n)&&PV(e[n])?t[n]=T(e[n]):delete t[n]}))}var NV={lineX:EV(0),lineY:EV(1),rect:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])},rect:function(t,e,n){return t&&n.boundingRect.intersect(t)}},polygon:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])&&P_(n.range,t[0],t[1])},rect:function(t,e,n){var i=n.range;if(!t||i.length<=1)return!1;var r=t.x,o=t.y,a=t.width,s=t.height,l=i[0];return!!(P_(i,r,o)||P_(i,r+a,o)||P_(i,r,o+s)||P_(i,r+a,o+s)||ze.create(t).contain(l[0],l[1])||Zh(r,o,r+a,o,i)||Zh(r,o,r,o+s,i)||Zh(r+a,o,r+a,o+s,i)||Zh(r,o+s,r+a,o+s,i))||void 0}}};function EV(t){var e=["x","y"],n=["width","height"];return{point:function(e,n,i){if(e){var r=i.range;return zV(e[t],r)}},rect:function(i,r,o){if(i){var a=o.range,s=[i[e[t]],i[e[t]]+i[n[t]]];return s[1]e[0][1]&&(e[0][1]=o[0]),o[1]e[1][1]&&(e[1][1]=o[1])}return e&&UV(e)}};function UV(t){return new ze(t[0][0],t[1][0],t[0][1]-t[0][0],t[1][1]-t[1][0])}var ZV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.ecModel=t,this.api=e,this.model,(this._brushController=new lL(e.getZr())).on("brush",W(this._onBrush,this)).mount()},e.prototype.render=function(t,e,n,i){this.model=t,this._updateController(t,e,n,i)},e.prototype.updateTransform=function(t,e,n,i){GV(e),this._updateController(t,e,n,i)},e.prototype.updateVisual=function(t,e,n,i){this.updateTransform(t,e,n,i)},e.prototype.updateView=function(t,e,n,i){this._updateController(t,e,n,i)},e.prototype._updateController=function(t,e,n,i){(!i||i.$from!==t.id)&&this._brushController.setPanels(t.brushTargetManager.makePanelOpts(n)).enableBrush(t.brushOption).updateCovers(t.areas.slice())},e.prototype.dispose=function(){this._brushController.dispose()},e.prototype._onBrush=function(t){var e=this.model.id,n=this.model.brushTargetManager.setOutputRanges(t.areas,this.ecModel);(!t.isEnd||t.removeOnClick)&&this.api.dispatchAction({type:"brush",brushId:e,areas:T(n),$from:e}),t.isEnd&&this.api.dispatchAction({type:"brushEnd",brushId:e,areas:T(n),$from:e})},e.type="brush",e}(Ag),jV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.areas=[],n.brushOption={},n}return n(e,t),e.prototype.optionUpdated=function(t,e){var n=this.option;!e&&RV(n,t,["inBrush","outOfBrush"]);var i=n.inBrush=n.inBrush||{};n.outOfBrush=n.outOfBrush||{color:"#ddd"},i.hasOwnProperty("liftZ")||(i.liftZ=5)},e.prototype.setAreas=function(t){t&&(this.areas=z(t,(function(t){return qV(this.option,t)}),this))},e.prototype.setBrushOption=function(t){this.brushOption=qV(this.option,t),this.brushType=this.brushOption.brushType},e.type="brush",e.dependencies=["geo","grid","xAxis","yAxis","parallel","series"],e.defaultOption={seriesIndex:"all",brushType:"rect",brushMode:"single",transformable:!0,brushStyle:{borderWidth:1,color:"rgba(210,219,238,0.3)",borderColor:"#D2DBEE"},throttleType:"fixRate",throttleDelay:0,removeOnClick:!0,z:1e4},e}(zp);function qV(t,e){return C({brushType:t.brushType,brushMode:t.brushMode,transformable:t.transformable,brushStyle:new Cc(t.brushStyle).getItemStyle(),removeOnClick:t.removeOnClick,z:t.z},e,!0)}var KV=["rect","polygon","lineX","lineY","keep","clear"],$V=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n){var i,r,o;e.eachComponent({mainType:"brush"},(function(t){i=t.brushType,r=t.brushOption.brushMode||"single",o=o||!!t.areas.length})),this._brushType=i,this._brushMode=r,E(t.get("type",!0),(function(e){t.setIconStatus(e,("keep"===e?"multiple"===r:"clear"===e?o:e===i)?"emphasis":"normal")}))},e.prototype.updateView=function(t,e,n){this.render(t,e,n)},e.prototype.getIcons=function(){var t=this.model,e=t.get("icon",!0),n={};return E(t.get("type",!0),(function(t){e[t]&&(n[t]=e[t])})),n},e.prototype.onclick=function(t,e,n){var i=this._brushType,r=this._brushMode;"clear"===n?(e.dispatchAction({type:"axisAreaSelect",intervals:[]}),e.dispatchAction({type:"brush",command:"clear",areas:[]})):e.dispatchAction({type:"takeGlobalCursor",key:"brush",brushOption:{brushType:"keep"===n?i:i!==n&&n,brushMode:"keep"===n?"multiple"===r?"single":"multiple":r}})},e.getDefaultOption=function(t){return{show:!0,type:KV.slice(),icon:{rect:"M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13",polygon:"M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2",lineX:"M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4",lineY:"M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4",keep:"M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z",clear:"M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2"},title:t.getLocaleModel().get(["toolbox","brush","title"])}},e}(_z);var JV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode={type:"box",ignoreSize:!0},n}return n(e,t),e.type="title",e.defaultOption={z:6,show:!0,text:"",target:"blank",subtext:"",subtarget:"blank",left:0,top:0,backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,padding:5,itemGap:10,textStyle:{fontSize:18,fontWeight:"bold",color:"#464646"},subtextStyle:{fontSize:12,color:"#6E7079"}},e}(zp),QV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){if(this.group.removeAll(),t.get("show")){var i=this.group,r=t.getModel("textStyle"),o=t.getModel("subtextStyle"),a=t.get("textAlign"),s=rt(t.get("textBaseline"),t.get("textVerticalAlign")),l=new Xs({style:oc(r,{text:t.get("text"),fill:r.getTextColor()},{disableBox:!0}),z2:10}),u=l.getBoundingRect(),h=t.get("subtext"),c=new Xs({style:oc(o,{text:h,fill:o.getTextColor(),y:u.height+t.get("itemGap"),verticalAlign:"top"},{disableBox:!0}),z2:10}),p=t.get("link"),d=t.get("sublink"),f=t.get("triggerEvent",!0);l.silent=!p&&!f,c.silent=!d&&!f,p&&l.on("click",(function(){Mp(p,"_"+t.get("target"))})),d&&c.on("click",(function(){Mp(d,"_"+t.get("subtarget"))})),rl(l).eventData=rl(c).eventData=f?{componentType:"title",componentIndex:t.componentIndex}:null,i.add(l),h&&i.add(c);var g=i.getBoundingRect(),y=t.getBoxLayoutParams();y.width=g.width,y.height=g.height;var v=kp(y,{width:n.getWidth(),height:n.getHeight()},t.get("padding"));a||("middle"===(a=t.get("left")||t.get("right"))&&(a="center"),"right"===a?v.x+=v.width:"center"===a&&(v.x+=v.width/2)),s||("center"===(s=t.get("top")||t.get("bottom"))&&(s="middle"),"bottom"===s?v.y+=v.height:"middle"===s&&(v.y+=v.height/2),s=s||"top"),i.x=v.x,i.y=v.y,i.markRedraw();var m={align:a,verticalAlign:s};l.setStyle(m),c.setStyle(m),g=i.getBoundingRect();var x=v.margin,_=t.getItemStyle(["color","opacity"]);_.fill=t.get("backgroundColor");var b=new Ws({shape:{x:g.x-x[3],y:g.y-x[0],width:g.width+x[1]+x[3],height:g.height+x[0]+x[2],r:t.get("borderRadius")},style:_,subPixelOptimize:!0,silent:!0});i.add(b)}},e.type="title",e}(Ag);var tB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode="box",n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n),this._initData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this._initData()},e.prototype.setCurrentIndex=function(t){null==t&&(t=this.option.currentIndex);var e=this._data.count();this.option.loop?t=(t%e+e)%e:(t>=e&&(t=e-1),t<0&&(t=0)),this.option.currentIndex=t},e.prototype.getCurrentIndex=function(){return this.option.currentIndex},e.prototype.isIndexMax=function(){return this.getCurrentIndex()>=this._data.count()-1},e.prototype.setPlayState=function(t){this.option.autoPlay=!!t},e.prototype.getPlayState=function(){return!!this.option.autoPlay},e.prototype._initData=function(){var t,e=this.option,n=e.data||[],i=e.axisType,r=this._names=[];"category"===i?(t=[],E(n,(function(e,n){var i,o=Ro(Ao(e),"");q(e)?(i=T(e)).value=n:i=n,t.push(i),r.push(o)}))):t=n;var o={category:"ordinal",time:"time",value:"number"}[i]||"number";(this._data=new cx([{name:"value",type:o}],this)).initData(t,r)},e.prototype.getData=function(){return this._data},e.prototype.getCategories=function(){if("category"===this.get("axisType"))return this._names.slice()},e.type="timeline",e.defaultOption={z:4,show:!0,axisType:"time",realtime:!0,left:"20%",top:null,right:"20%",bottom:0,width:null,height:40,padding:5,controlPosition:"left",autoPlay:!1,rewind:!1,loop:!0,playInterval:2e3,currentIndex:0,itemStyle:{},label:{color:"#000"},data:[]},e}(zp),eB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="timeline.slider",e.defaultOption=kc(tB.defaultOption,{backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,orient:"horizontal",inverse:!1,tooltip:{trigger:"item"},symbol:"circle",symbolSize:12,lineStyle:{show:!0,width:2,color:"#DAE1F5"},label:{position:"auto",show:!0,interval:"auto",rotate:0,color:"#A4B1D7"},itemStyle:{color:"#A4B1D7",borderWidth:1},checkpointStyle:{symbol:"circle",symbolSize:15,color:"#316bf3",borderColor:"#fff",borderWidth:2,shadowBlur:2,shadowOffsetX:1,shadowOffsetY:1,shadowColor:"rgba(0, 0, 0, 0.3)",animation:!0,animationDuration:300,animationEasing:"quinticInOut"},controlStyle:{show:!0,showPlayBtn:!0,showPrevBtn:!0,showNextBtn:!0,itemSize:24,itemGap:12,position:"left",playIcon:"path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z",stopIcon:"path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z",nextIcon:"M2,18.5A1.52,1.52,0,0,1,.92,18a1.49,1.49,0,0,1,0-2.12L7.81,9.36,1,3.11A1.5,1.5,0,1,1,3,.89l8,7.34a1.48,1.48,0,0,1,.49,1.09,1.51,1.51,0,0,1-.46,1.1L3,18.08A1.5,1.5,0,0,1,2,18.5Z",prevIcon:"M10,.5A1.52,1.52,0,0,1,11.08,1a1.49,1.49,0,0,1,0,2.12L4.19,9.64,11,15.89a1.5,1.5,0,1,1-2,2.22L1,10.77A1.48,1.48,0,0,1,.5,9.68,1.51,1.51,0,0,1,1,8.58L9,.92A1.5,1.5,0,0,1,10,.5Z",prevBtnSize:18,nextBtnSize:18,color:"#A4B1D7",borderColor:"#A4B1D7",borderWidth:1},emphasis:{label:{show:!0,color:"#6f778d"},itemStyle:{color:"#316BF3"},controlStyle:{color:"#316BF3",borderColor:"#316BF3",borderWidth:2}},progress:{lineStyle:{color:"#316BF3"},itemStyle:{color:"#316BF3"},label:{color:"#6f778d"}},data:[]}),e}(tB);R(eB,_f.prototype);var nB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="timeline",e}(Ag),iB=function(t){function e(e,n,i,r){var o=t.call(this,e,n,i)||this;return o.type=r||"value",o}return n(e,t),e.prototype.getLabelModel=function(){return this.model.getModel("label")},e.prototype.isHorizontal=function(){return"horizontal"===this.model.get("orient")},e}(ab),rB=Math.PI,oB=Vo(),aB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.api=e},e.prototype.render=function(t,e,n){if(this.model=t,this.api=n,this.ecModel=e,this.group.removeAll(),t.get("show",!0)){var i=this._layout(t,n),r=this._createGroup("_mainGroup"),o=this._createGroup("_labelGroup"),a=this._axis=this._createAxis(i,t);t.formatTooltip=function(t){return og("nameValue",{noName:!0,value:a.scale.getLabel({value:t})})},E(["AxisLine","AxisTick","Control","CurrentPointer"],(function(e){this["_render"+e](i,r,a,t)}),this),this._renderAxisLabel(i,o,a,t),this._position(i,t)}this._doPlayStop(),this._updateTicksStatus()},e.prototype.remove=function(){this._clearTimer(),this.group.removeAll()},e.prototype.dispose=function(){this._clearTimer()},e.prototype._layout=function(t,e){var n,i,r,o,a=t.get(["label","position"]),s=t.get("orient"),l=function(t,e){return kp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()},t.get("padding"))}(t,e),u={horizontal:"center",vertical:(n=null==a||"auto"===a?"horizontal"===s?l.y+l.height/2=0||"+"===n?"left":"right"},h={horizontal:n>=0||"+"===n?"top":"bottom",vertical:"middle"},c={horizontal:0,vertical:rB/2},p="vertical"===s?l.height:l.width,d=t.getModel("controlStyle"),f=d.get("show",!0),g=f?d.get("itemSize"):0,y=f?d.get("itemGap"):0,v=g+y,m=t.get(["label","rotate"])||0;m=m*rB/180;var x=d.get("position",!0),_=f&&d.get("showPlayBtn",!0),b=f&&d.get("showPrevBtn",!0),w=f&&d.get("showNextBtn",!0),S=0,M=p;"left"===x||"bottom"===x?(_&&(i=[0,0],S+=v),b&&(r=[S,0],S+=v),w&&(o=[M-g,0],M-=v)):(_&&(i=[M-g,0],M-=v),b&&(r=[0,0],S+=v),w&&(o=[M-g,0],M-=v));var I=[S,M];return t.get("inverse")&&I.reverse(),{viewRect:l,mainLength:p,orient:s,rotation:c[s],labelRotation:m,labelPosOpt:n,labelAlign:t.get(["label","align"])||u[s],labelBaseline:t.get(["label","verticalAlign"])||t.get(["label","baseline"])||h[s],playPosition:i,prevBtnPosition:r,nextBtnPosition:o,axisExtent:I,controlSize:g,controlGap:y}},e.prototype._position=function(t,e){var n=this._mainGroup,i=this._labelGroup,r=t.viewRect;if("vertical"===t.orient){var o=[1,0,0,1,0,0],a=r.x,s=r.y+r.height;we(o,o,[-a,-s]),Se(o,o,-rB/2),we(o,o,[a,s]),(r=r.clone()).applyTransform(o)}var l=y(r),u=y(n.getBoundingRect()),h=y(i.getBoundingRect()),c=[n.x,n.y],p=[i.x,i.y];p[0]=c[0]=l[0][0];var d,f=t.labelPosOpt;null==f||U(f)?(v(c,u,l,1,d="+"===f?0:1),v(p,h,l,1,1-d)):(v(c,u,l,1,d=f>=0?0:1),p[1]=c[1]+f);function g(t){t.originX=l[0][0]-t.x,t.originY=l[1][0]-t.y}function y(t){return[[t.x,t.x+t.width],[t.y,t.y+t.height]]}function v(t,e,n,i,r){t[i]+=n[i][r]-e[i][r]}n.setPosition(c),i.setPosition(p),n.rotation=i.rotation=t.rotation,g(n),g(i)},e.prototype._createAxis=function(t,e){var n=e.getData(),i=e.get("axisType"),r=function(t,e){if(e=e||t.get("type"),e)switch(e){case"category":return new Rx({ordinalMeta:t.getCategories(),extent:[1/0,-1/0]});case"time":return new Kx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get("useUTC")});default:return new Ex}}(e,i);r.getTicks=function(){return n.mapArray(["value"],(function(t){return{value:t}}))};var o=n.getDataExtent("value");r.setExtent(o[0],o[1]),r.calcNiceTicks();var a=new iB("value",r,t.axisExtent,i);return a.model=e,a},e.prototype._createGroup=function(t){var e=this[t]=new Br;return this.group.add(e),e},e.prototype._renderAxisLine=function(t,e,n,i){var r=n.getExtent();if(i.get(["lineStyle","show"])){var o=new Ku({shape:{x1:r[0],y1:0,x2:r[1],y2:0},style:A({lineCap:"round"},i.getModel("lineStyle").getLineStyle()),silent:!0,z2:1});e.add(o);var a=this._progressLine=new Ku({shape:{x1:r[0],x2:this._currentPointer?this._currentPointer.x:r[0],y1:0,y2:0},style:k({lineCap:"round",lineWidth:o.style.lineWidth},i.getModel(["progress","lineStyle"]).getLineStyle()),silent:!0,z2:1});e.add(a)}},e.prototype._renderAxisTick=function(t,e,n,i){var r=this,o=i.getData(),a=n.scale.getTicks();this._tickSymbols=[],E(a,(function(t){var a=n.dataToCoord(t.value),s=o.getItemModel(t.value),l=s.getModel("itemStyle"),u=s.getModel(["emphasis","itemStyle"]),h=s.getModel(["progress","itemStyle"]),c={x:a,y:0,onclick:W(r._changeTimeline,r,t.value)},p=sB(s,l,e,c);p.ensureState("emphasis").style=u.getItemStyle(),p.ensureState("progress").style=h.getItemStyle(),Ul(p);var d=rl(p);s.get("tooltip")?(d.dataIndex=t.value,d.dataModel=i):d.dataIndex=d.dataModel=null,r._tickSymbols.push(p)}))},e.prototype._renderAxisLabel=function(t,e,n,i){var r=this;if(n.getLabelModel().get("show")){var o=i.getData(),a=n.getViewLabels();this._tickLabels=[],E(a,(function(i){var a=i.tickValue,s=o.getItemModel(a),l=s.getModel("label"),u=s.getModel(["emphasis","label"]),h=s.getModel(["progress","label"]),c=n.dataToCoord(i.tickValue),p=new Xs({x:c,y:0,rotation:t.labelRotation-t.rotation,onclick:W(r._changeTimeline,r,a),silent:!1,style:oc(l,{text:i.formattedLabel,align:t.labelAlign,verticalAlign:t.labelBaseline})});p.ensureState("emphasis").style=oc(u),p.ensureState("progress").style=oc(h),e.add(p),Ul(p),oB(p).dataIndex=a,r._tickLabels.push(p)}))}},e.prototype._renderControl=function(t,e,n,i){var r=t.controlSize,o=t.rotation,a=i.getModel("controlStyle").getItemStyle(),s=i.getModel(["emphasis","controlStyle"]).getItemStyle(),l=i.getPlayState(),u=i.get("inverse",!0);function h(t,n,l,u){if(t){var h=Cr(rt(i.get(["controlStyle",n+"BtnSize"]),r),r),c=function(t,e,n,i){var r=i.style,o=Uh(t.get(["controlStyle",e]),i||{},new ze(n[0],n[1],n[2],n[3]));r&&o.setStyle(r);return o}(i,n+"Icon",[0,-h/2,h,h],{x:t[0],y:t[1],originX:r/2,originY:0,rotation:u?-o:0,rectHover:!0,style:a,onclick:l});c.ensureState("emphasis").style=s,e.add(c),Ul(c)}}h(t.nextBtnPosition,"next",W(this._changeTimeline,this,u?"-":"+")),h(t.prevBtnPosition,"prev",W(this._changeTimeline,this,u?"+":"-")),h(t.playPosition,l?"stop":"play",W(this._handlePlayClick,this,!l),!0)},e.prototype._renderCurrentPointer=function(t,e,n,i){var r=i.getData(),o=i.getCurrentIndex(),a=r.getItemModel(o).getModel("checkpointStyle"),s=this,l={onCreate:function(t){t.draggable=!0,t.drift=W(s._handlePointerDrag,s),t.ondragend=W(s._handlePointerDragend,s),lB(t,s._progressLine,o,n,i,!0)},onUpdate:function(t){lB(t,s._progressLine,o,n,i)}};this._currentPointer=sB(a,a,this._mainGroup,{},this._currentPointer,l)},e.prototype._handlePlayClick=function(t){this._clearTimer(),this.api.dispatchAction({type:"timelinePlayChange",playState:t,from:this.uid})},e.prototype._handlePointerDrag=function(t,e,n){this._clearTimer(),this._pointerChangeTimeline([n.offsetX,n.offsetY])},e.prototype._handlePointerDragend=function(t){this._pointerChangeTimeline([t.offsetX,t.offsetY],!0)},e.prototype._pointerChangeTimeline=function(t,e){var n=this._toAxisCoord(t)[0],i=Qr(this._axis.getExtent().slice());n>i[1]&&(n=i[1]),n=0&&(a[o]=+a[o].toFixed(c)),[a,h]}var xB={min:H(mB,"min"),max:H(mB,"max"),average:H(mB,"average"),median:H(mB,"median")};function _B(t,e){if(e){var n=t.getData(),i=t.coordinateSystem,r=i&&i.dimensions;if(!function(t){return!isNaN(parseFloat(t.x))&&!isNaN(parseFloat(t.y))}(e)&&!Y(e.coord)&&Y(r)){var o=bB(e,n,i,t);if((e=T(e)).type&&xB[e.type]&&o.baseAxis&&o.valueAxis){var a=P(r,o.baseAxis.dim),s=P(r,o.valueAxis.dim),l=xB[e.type](n,o.baseDataDim,o.valueDataDim,a,s);e.coord=l[0],e.value=l[1]}else e.coord=[null!=e.xAxis?e.xAxis:e.radiusAxis,null!=e.yAxis?e.yAxis:e.angleAxis]}if(null!=e.coord&&Y(r))for(var u=e.coord,h=0;h<2;h++)xB[u[h]]&&(u[h]=MB(n,n.mapDimension(r[h]),u[h]));else e.coord=[];return e}}function bB(t,e,n,i){var r={};return null!=t.valueIndex||null!=t.valueDim?(r.valueDataDim=null!=t.valueIndex?e.getDimension(t.valueIndex):t.valueDim,r.valueAxis=n.getAxis(function(t,e){var n=t.getData().getDimensionInfo(e);return n&&n.coordDim}(i,r.valueDataDim)),r.baseAxis=n.getOtherAxis(r.valueAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim)):(r.baseAxis=i.getBaseAxis(),r.valueAxis=n.getOtherAxis(r.baseAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim),r.valueDataDim=e.mapDimension(r.valueAxis.dim)),r}function wB(t,e){return!(t&&t.containData&&e.coord&&!vB(e))||t.containData(e.coord)}function SB(t,e){return t?function(t,n,i,r){return If(r<2?t.coord&&t.coord[r]:t.value,e[r])}:function(t,n,i,r){return If(t.value,e[r])}}function MB(t,e,n){if("average"===n){var i=0,r=0;return t.each(e,(function(t,e){isNaN(t)||(i+=t,r++)})),i/r}return"median"===n?t.getMedian(e):t.getDataExtent(e)["max"===n?1:0]}var IB=Vo(),TB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){this.markerGroupMap=yt()},e.prototype.render=function(t,e,n){var i=this,r=this.markerGroupMap;r.each((function(t){IB(t).keep=!1})),e.eachSeries((function(t){var r=gB.getMarkerModelFromSeries(t,i.type);r&&i.renderSeries(t,r,e,n)})),r.each((function(t){!IB(t).keep&&i.group.remove(t.group)}))},e.prototype.markKeep=function(t){IB(t).keep=!0},e.prototype.toggleBlurSeries=function(t,e){var n=this;E(t,(function(t){var i=gB.getMarkerModelFromSeries(t,n.type);i&&i.getData().eachItemGraphicEl((function(t){t&&(e?Nl(t):El(t))}))}))},e.type="marker",e}(Ag);function CB(t,e,n){var i=e.coordinateSystem;t.each((function(r){var o,a=t.getItemModel(r),s=$r(a.get("x"),n.getWidth()),l=$r(a.get("y"),n.getHeight());if(isNaN(s)||isNaN(l)){if(e.getMarkerPosition)o=e.getMarkerPosition(t.getValues(t.dimensions,r));else if(i){var u=t.get(i.dimensions[0],r),h=t.get(i.dimensions[1],r);o=i.dataToPoint([u,h])}}else o=[s,l];isNaN(s)||(o[0]=s),isNaN(l)||(o[1]=l),t.setItemLayout(r,o)}))}var DB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=gB.getMarkerModelFromSeries(t,"markPoint");e&&(CB(e.getData(),t,n),this.markerGroupMap.get(t.id).updateLayout())}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new xS),u=function(t,e,n){var i;i=t?z(t&&t.dimensions,(function(t){return A(A({},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{}),{name:t,ordinalMeta:null})})):[{name:"value",type:"float"}];var r=new cx(i,n),o=z(n.get("data"),H(_B,e));t&&(o=B(o,H(wB,t)));var a=SB(!!t,i);return r.initData(o,null,a),r}(r,t,e);e.setData(u),CB(e.getData(),t,i),u.each((function(t){var n=u.getItemModel(t),i=n.getShallow("symbol"),r=n.getShallow("symbolSize"),o=n.getShallow("symbolRotate"),s=n.getShallow("symbolOffset"),l=n.getShallow("symbolKeepAspect");if(X(i)||X(r)||X(o)||X(s)){var h=e.getRawValue(t),c=e.getDataParams(t);X(i)&&(i=i(h,c)),X(r)&&(r=r(h,c)),X(o)&&(o=o(h,c)),X(s)&&(s=s(h,c))}var p=n.getModel("itemStyle").getItemStyle(),d=Ay(a,"color");p.fill||(p.fill=d),u.setItemVisual(t,{symbol:i,symbolSize:r,symbolRotate:o,symbolOffset:s,symbolKeepAspect:l,style:p})})),l.updateData(u),this.group.add(l.group),u.eachItemGraphicEl((function(t){t.traverse((function(t){rl(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get("silent")||t.get("silent")},e.type="markPoint",e}(TB);var AB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type="markLine",e.defaultOption={z:5,symbol:["circle","arrow"],symbolSize:[8,16],symbolOffset:0,precision:2,tooltip:{trigger:"item"},label:{show:!0,position:"end",distance:5},lineStyle:{type:"dashed"},emphasis:{label:{show:!0},lineStyle:{width:3}},animationEasing:"linear"},e}(gB),kB=Vo(),LB=function(t,e,n,i){var r,o=t.getData();if(Y(i))r=i;else{var a=i.type;if("min"===a||"max"===a||"average"===a||"median"===a||null!=i.xAxis||null!=i.yAxis){var s=void 0,l=void 0;if(null!=i.yAxis||null!=i.xAxis)s=e.getAxis(null!=i.yAxis?"y":"x"),l=it(i.yAxis,i.xAxis);else{var u=bB(i,o,e,t);s=u.valueAxis,l=MB(o,xx(o,u.valueDataDim),a)}var h="x"===s.dim?0:1,c=1-h,p=T(i),d={coord:[]};p.type=null,p.coord=[],p.coord[c]=-1/0,d.coord[c]=1/0;var f=n.get("precision");f>=0&&j(l)&&(l=+l.toFixed(Math.min(f,20))),p.coord[h]=d.coord[h]=l,r=[p,d,{type:a,valueIndex:i.valueIndex,value:l}]}else r=[]}var g=[_B(t,r[0]),_B(t,r[1]),A({},r[2])];return g[2].type=g[2].type||null,C(g[2],g[0]),C(g[2],g[1]),g};function PB(t){return!isNaN(t)&&!isFinite(t)}function OB(t,e,n,i){var r=1-t,o=i.dimensions[t];return PB(e[r])&&PB(n[r])&&e[t]===n[t]&&i.getAxis(o).containData(e[t])}function RB(t,e){if("cartesian2d"===t.type){var n=e[0].coord,i=e[1].coord;if(n&&i&&(OB(1,n,i,t)||OB(0,n,i,t)))return!0}return wB(t,e[0])&&wB(t,e[1])}function NB(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=$r(s.get("x"),r.getWidth()),u=$r(s.get("y"),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition)o=i.getMarkerPosition(t.getValues(t.dimensions,e));else{var h=a.dimensions,c=t.get(h[0],e),p=t.get(h[1],e);o=a.dataToPoint([c,p])}if(OS(a,"cartesian2d")){var d=a.getAxis("x"),f=a.getAxis("y");h=a.dimensions;PB(t.get(h[0],e))?o[0]=d.toGlobalCoord(d.getExtent()[n?0:1]):PB(t.get(h[1],e))&&(o[1]=f.toGlobalCoord(f.getExtent()[n?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];t.setItemLayout(e,o)}var EB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=gB.getMarkerModelFromSeries(t,"markLine");if(e){var i=e.getData(),r=kB(e).from,o=kB(e).to;r.each((function(e){NB(r,e,!0,t,n),NB(o,e,!1,t,n)})),i.each((function(t){i.setItemLayout(t,[r.getItemLayout(t),o.getItemLayout(t)])})),this.markerGroupMap.get(t.id).updateLayout()}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new YA);this.group.add(l.group);var u=function(t,e,n){var i;i=t?z(t&&t.dimensions,(function(t){return A(A({},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{}),{name:t,ordinalMeta:null})})):[{name:"value",type:"float"}];var r=new cx(i,n),o=new cx(i,n),a=new cx([],n),s=z(n.get("data"),H(LB,e,t,n));t&&(s=B(s,H(RB,t)));var l=SB(!!t,i);return r.initData(z(s,(function(t){return t[0]})),null,l),o.initData(z(s,(function(t){return t[1]})),null,l),a.initData(z(s,(function(t){return t[2]}))),a.hasItemOption=!0,{from:r,to:o,line:a}}(r,t,e),h=u.from,c=u.to,p=u.line;kB(e).from=h,kB(e).to=c,e.setData(p);var d=e.get("symbol"),f=e.get("symbolSize"),g=e.get("symbolRotate"),y=e.get("symbolOffset");function v(e,n,r){var o=e.getItemModel(n);NB(e,n,r,t,i);var s=o.getModel("itemStyle").getItemStyle();null==s.fill&&(s.fill=Ay(a,"color")),e.setItemVisual(n,{symbolKeepAspect:o.get("symbolKeepAspect"),symbolOffset:rt(o.get("symbolOffset",!0),y[r?0:1]),symbolRotate:rt(o.get("symbolRotate",!0),g[r?0:1]),symbolSize:rt(o.get("symbolSize"),f[r?0:1]),symbol:rt(o.get("symbol",!0),d[r?0:1]),style:s})}Y(d)||(d=[d,d]),Y(f)||(f=[f,f]),Y(g)||(g=[g,g]),Y(y)||(y=[y,y]),u.from.each((function(t){v(h,t,!0),v(c,t,!1)})),p.each((function(t){var e=p.getItemModel(t).getModel("lineStyle").getLineStyle();p.setItemLayout(t,[h.getItemLayout(t),c.getItemLayout(t)]),null==e.stroke&&(e.stroke=h.getItemVisual(t,"style").fill),p.setItemVisual(t,{fromSymbolKeepAspect:h.getItemVisual(t,"symbolKeepAspect"),fromSymbolOffset:h.getItemVisual(t,"symbolOffset"),fromSymbolRotate:h.getItemVisual(t,"symbolRotate"),fromSymbolSize:h.getItemVisual(t,"symbolSize"),fromSymbol:h.getItemVisual(t,"symbol"),toSymbolKeepAspect:c.getItemVisual(t,"symbolKeepAspect"),toSymbolOffset:c.getItemVisual(t,"symbolOffset"),toSymbolRotate:c.getItemVisual(t,"symbolRotate"),toSymbolSize:c.getItemVisual(t,"symbolSize"),toSymbol:c.getItemVisual(t,"symbol"),style:e})})),l.updateData(p),u.line.eachItemGraphicEl((function(t){rl(t).dataModel=e,t.traverse((function(t){rl(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get("silent")||t.get("silent")},e.type="markLine",e}(TB);var zB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type="markArea",e.defaultOption={z:1,tooltip:{trigger:"item"},animation:!1,label:{show:!0,position:"top"},itemStyle:{borderWidth:0},emphasis:{label:{show:!0,position:"top"}}},e}(gB),VB=Vo(),BB=function(t,e,n,i){var r=i[0],o=i[1];if(r&&o){var a=_B(t,r),s=_B(t,o),l=a.coord,u=s.coord;l[0]=it(l[0],-1/0),l[1]=it(l[1],-1/0),u[0]=it(u[0],1/0),u[1]=it(u[1],1/0);var h=D([{},a,s]);return h.coord=[a.coord,s.coord],h.x0=a.x,h.y0=a.y,h.x1=s.x,h.y1=s.y,h}};function FB(t){return!isNaN(t)&&!isFinite(t)}function GB(t,e,n,i){var r=1-t;return FB(e[r])&&FB(n[r])}function WB(t,e){var n=e.coord[0],i=e.coord[1],r={coord:n,x:e.x0,y:e.y0},o={coord:i,x:e.x1,y:e.y1};return OS(t,"cartesian2d")?!(!n||!i||!GB(1,n,i)&&!GB(0,n,i))||function(t,e,n){return!(t&&t.containZone&&e.coord&&n.coord&&!vB(e)&&!vB(n))||t.containZone(e.coord,n.coord)}(t,r,o):wB(t,r)||wB(t,o)}function HB(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=$r(s.get(n[0]),r.getWidth()),u=$r(s.get(n[1]),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition){var h=t.getValues(["x0","y0"],e),c=t.getValues(["x1","y1"],e),p=a.clampData(h),d=a.clampData(c),f=[];"x0"===n[0]?f[0]=p[0]>d[0]?c[0]:h[0]:f[0]=p[0]>d[0]?h[0]:c[0],"y0"===n[1]?f[1]=p[1]>d[1]?c[1]:h[1]:f[1]=p[1]>d[1]?h[1]:c[1],o=i.getMarkerPosition(f,n,!0)}else{var g=[m=t.get(n[0],e),x=t.get(n[1],e)];a.clampData&&a.clampData(g,g),o=a.dataToPoint(g,!0)}if(OS(a,"cartesian2d")){var y=a.getAxis("x"),v=a.getAxis("y"),m=t.get(n[0],e),x=t.get(n[1],e);FB(m)?o[0]=y.toGlobalCoord(y.getExtent()["x0"===n[0]?0:1]):FB(x)&&(o[1]=v.toGlobalCoord(v.getExtent()["y0"===n[1]?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];return o}var YB=[["x0","y0"],["x1","y0"],["x1","y1"],["x0","y1"]],XB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=gB.getMarkerModelFromSeries(t,"markArea");if(e){var i=e.getData();i.each((function(e){var r=z(YB,(function(r){return HB(i,e,r,t,n)}));i.setItemLayout(e,r),i.getItemGraphicEl(e).setShape("points",r)}))}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,{group:new Br});this.group.add(l.group),this.markKeep(l);var u=function(t,e,n){var i,r,o=["x0","y0","x1","y1"];if(t){var a=z(t&&t.dimensions,(function(t){var n=e.getData();return A(A({},n.getDimensionInfo(n.mapDimension(t))||{}),{name:t,ordinalMeta:null})}));r=z(o,(function(t,e){return{name:t,type:a[e%2].type}})),i=new cx(r,n)}else i=new cx(r=[{name:"value",type:"float"}],n);var s=z(n.get("data"),H(BB,e,t,n));t&&(s=B(s,H(WB,t)));var l=t?function(t,e,n,i){return If(t.coord[Math.floor(i/2)][i%2],r[i])}:function(t,e,n,i){return If(t.value,r[i])};return i.initData(s,null,l),i.hasItemOption=!0,i}(r,t,e);e.setData(u),u.each((function(e){var n=z(YB,(function(n){return HB(u,e,n,t,i)})),o=r.getAxis("x").scale,s=r.getAxis("y").scale,l=o.getExtent(),h=s.getExtent(),c=[o.parse(u.get("x0",e)),o.parse(u.get("x1",e))],p=[s.parse(u.get("y0",e)),s.parse(u.get("y1",e))];Qr(c),Qr(p);var d=!!(l[0]>c[1]||l[1]p[1]||h[1]=0},e.prototype.getOrient=function(){return"vertical"===this.get("orient")?{index:1,name:"vertical"}:{index:0,name:"horizontal"}},e.type="legend.plain",e.dependencies=["series"],e.defaultOption={z:4,show:!0,orient:"horizontal",left:"center",top:0,align:"auto",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,symbolRotate:"inherit",symbolKeepAspect:!0,inactiveColor:"#ccc",inactiveBorderColor:"#ccc",inactiveBorderWidth:"auto",itemStyle:{color:"inherit",opacity:"inherit",borderColor:"inherit",borderWidth:"auto",borderCap:"inherit",borderJoin:"inherit",borderDashOffset:"inherit",borderMiterLimit:"inherit"},lineStyle:{width:"auto",color:"inherit",inactiveColor:"#ccc",inactiveWidth:2,opacity:"inherit",type:"inherit",cap:"inherit",join:"inherit",dashOffset:"inherit",miterLimit:"inherit"},textStyle:{color:"#333"},selectedMode:!0,selector:!1,selectorLabel:{show:!0,borderRadius:10,padding:[3,5,3,5],fontSize:12,fontFamily:"sans-serif",color:"#666",borderWidth:1,borderColor:"#666"},emphasis:{selectorLabel:{show:!0,color:"#eee",backgroundColor:"#666"}},selectorPosition:"auto",selectorItemGap:7,selectorButtonGap:10,tooltip:{show:!1}},e}(zp),ZB=H,jB=E,qB=Br,KB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.newlineDisabled=!1,n}return n(e,t),e.prototype.init=function(){this.group.add(this._contentGroup=new qB),this.group.add(this._selectorGroup=new qB),this._isFirstRender=!0},e.prototype.getContentGroup=function(){return this._contentGroup},e.prototype.getSelectorGroup=function(){return this._selectorGroup},e.prototype.render=function(t,e,n){var i=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),t.get("show",!0)){var r=t.get("align"),o=t.get("orient");r&&"auto"!==r||(r="right"===t.get("left")&&"vertical"===o?"right":"left");var a=t.get("selector",!0),s=t.get("selectorPosition",!0);!a||s&&"auto"!==s||(s="horizontal"===o?"end":"start"),this.renderInner(r,t,e,n,a,o,s);var l=t.getBoxLayoutParams(),u={width:n.getWidth(),height:n.getHeight()},h=t.get("padding"),c=kp(l,u,h),p=this.layoutInner(t,r,c,i,a,s),d=kp(k({width:p.width,height:p.height},l),u,h);this.group.x=d.x-p.x,this.group.y=d.y-p.y,this.group.markRedraw(),this.group.add(this._backgroundEl=Iz(p,t))}},e.prototype.resetInner=function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl),this.getSelectorGroup().removeAll()},e.prototype.renderInner=function(t,e,n,i,r,o,a){var s=this.getContentGroup(),l=yt(),u=e.get("selectedMode"),h=[];n.eachRawSeries((function(t){!t.get("legendHoverLink")&&h.push(t.id)})),jB(e.getData(),(function(r,o){var a=r.get("name");if(!this.newlineDisabled&&(""===a||"\n"===a)){var c=new qB;return c.newline=!0,void s.add(c)}var p=n.getSeriesByName(a)[0];if(!l.get(a)){if(p){var d=p.getData(),f=d.getVisual("legendLineStyle")||{},g=d.getVisual("legendIcon"),y=d.getVisual("style"),v=this._createItem(p,a,o,r,e,t,f,y,g,u,i);v.on("click",ZB($B,a,null,i,h)).on("mouseover",ZB(QB,p.name,null,i,h)).on("mouseout",ZB(tF,p.name,null,i,h)),n.ssr&&v.eachChild((function(t){var e=rl(t);e.seriesIndex=p.seriesIndex,e.dataIndex=o,e.ssrType="legend"})),l.set(a,!0)}else n.eachRawSeries((function(s){if(!l.get(a)&&s.legendVisualProvider){var c=s.legendVisualProvider;if(!c.containName(a))return;var p=c.indexOfName(a),d=c.getItemVisual(p,"style"),f=c.getItemVisual(p,"legendIcon"),g=qn(d.fill);g&&0===g[3]&&(g[3]=.2,d=A(A({},d),{fill:ri(g,"rgba")}));var y=this._createItem(s,a,o,r,e,t,{},d,f,u,i);y.on("click",ZB($B,null,a,i,h)).on("mouseover",ZB(QB,null,a,i,h)).on("mouseout",ZB(tF,null,a,i,h)),n.ssr&&y.eachChild((function(t){var e=rl(t);e.seriesIndex=s.seriesIndex,e.dataIndex=o,e.ssrType="legend"})),l.set(a,!0)}}),this);0}}),this),r&&this._createSelector(r,e,i,o,a)},e.prototype._createSelector=function(t,e,n,i,r){var o=this.getSelectorGroup();jB(t,(function(t){var i=t.type,r=new Xs({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){n.dispatchAction({type:"all"===i?"legendAllSelect":"legendInverseSelect",legendId:e.id})}});o.add(r),ic(r,{normal:e.getModel("selectorLabel"),emphasis:e.getModel(["emphasis","selectorLabel"])},{defaultText:t.title}),Ul(r)}))},e.prototype._createItem=function(t,e,n,i,r,o,a,s,l,u,h){var c=t.visualDrawType,p=r.get("itemWidth"),d=r.get("itemHeight"),f=r.isSelected(e),g=i.get("symbolRotate"),y=i.get("symbolKeepAspect"),v=i.get("icon"),m=function(t,e,n,i,r,o,a){function s(t,e){"auto"===t.lineWidth&&(t.lineWidth=e.lineWidth>0?2:0),jB(t,(function(n,i){"inherit"===t[i]&&(t[i]=e[i])}))}var l=e.getModel("itemStyle"),u=l.getItemStyle(),h=0===t.lastIndexOf("empty",0)?"fill":"stroke",c=l.getShallow("decal");u.decal=c&&"inherit"!==c?mv(c,a):i.decal,"inherit"===u.fill&&(u.fill=i[r]);"inherit"===u.stroke&&(u.stroke=i[h]);"inherit"===u.opacity&&(u.opacity=("fill"===r?i:n).opacity);s(u,i);var p=e.getModel("lineStyle"),d=p.getLineStyle();if(s(d,n),"auto"===u.fill&&(u.fill=i.fill),"auto"===u.stroke&&(u.stroke=i.fill),"auto"===d.stroke&&(d.stroke=i.fill),!o){var f=e.get("inactiveBorderWidth"),g=u[h];u.lineWidth="auto"===f?i.lineWidth>0&&g?2:0:u.lineWidth,u.fill=e.get("inactiveColor"),u.stroke=e.get("inactiveBorderColor"),d.stroke=p.get("inactiveColor"),d.lineWidth=p.get("inactiveWidth")}return{itemStyle:u,lineStyle:d}}(l=v||l||"roundRect",i,a,s,c,f,h),x=new qB,_=i.getModel("textStyle");if(!X(t.getLegendIcon)||v&&"inherit"!==v){var b="inherit"===v&&t.getData().getVisual("symbol")?"inherit"===g?t.getData().getVisual("symbolRotate"):g:0;x.add(function(t){var e=t.icon||"roundRect",n=Xy(e,0,0,t.itemWidth,t.itemHeight,t.itemStyle.fill,t.symbolKeepAspect);n.setStyle(t.itemStyle),n.rotation=(t.iconRotate||0)*Math.PI/180,n.setOrigin([t.itemWidth/2,t.itemHeight/2]),e.indexOf("empty")>-1&&(n.style.stroke=n.style.fill,n.style.fill="#fff",n.style.lineWidth=2);return n}({itemWidth:p,itemHeight:d,icon:l,iconRotate:b,itemStyle:m.itemStyle,lineStyle:m.lineStyle,symbolKeepAspect:y}))}else x.add(t.getLegendIcon({itemWidth:p,itemHeight:d,icon:l,iconRotate:g,itemStyle:m.itemStyle,lineStyle:m.lineStyle,symbolKeepAspect:y}));var w="left"===o?p+5:-5,S=o,M=r.get("formatter"),I=e;U(M)&&M?I=M.replace("{name}",null!=e?e:""):X(M)&&(I=M(e));var T=f?_.getTextColor():i.get("inactiveColor");x.add(new Xs({style:oc(_,{text:I,x:w,y:d/2,fill:T,align:S,verticalAlign:"middle"},{inheritColor:T})}));var C=new Ws({shape:x.getBoundingRect(),style:{fill:"transparent"}}),D=i.getModel("tooltip");return D.get("show")&&Kh({el:C,componentModel:r,itemName:e,itemTooltipOption:D.option}),x.add(C),x.eachChild((function(t){t.silent=!0})),C.silent=!u,this.getContentGroup().add(x),Ul(x),x.__legendDataIndex=n,x},e.prototype.layoutInner=function(t,e,n,i,r,o){var a=this.getContentGroup(),s=this.getSelectorGroup();Ap(t.get("orient"),a,t.get("itemGap"),n.width,n.height);var l=a.getBoundingRect(),u=[-l.x,-l.y];if(s.markRedraw(),a.markRedraw(),r){Ap("horizontal",s,t.get("selectorItemGap",!0));var h=s.getBoundingRect(),c=[-h.x,-h.y],p=t.get("selectorButtonGap",!0),d=t.getOrient().index,f=0===d?"width":"height",g=0===d?"height":"width",y=0===d?"y":"x";"end"===o?c[d]+=l[f]+p:u[d]+=h[f]+p,c[1-d]+=l[g]/2-h[g]/2,s.x=c[0],s.y=c[1],a.x=u[0],a.y=u[1];var v={x:0,y:0};return v[f]=l[f]+p+h[f],v[g]=Math.max(l[g],h[g]),v[y]=Math.min(0,h[y]+c[1-d]),v}return a.x=u[0],a.y=u[1],this.group.getBoundingRect()},e.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},e.type="legend.plain",e}(Ag);function $B(t,e,n,i){tF(t,e,n,i),n.dispatchAction({type:"legendToggleSelect",name:null!=t?t:e}),QB(t,e,n,i)}function JB(t){for(var e,n=t.getZr().storage.getDisplayList(),i=0,r=n.length;in[r],f=[-c.x,-c.y];e||(f[i]=l[s]);var g=[0,0],y=[-p.x,-p.y],v=rt(t.get("pageButtonGap",!0),t.get("itemGap",!0));d&&("end"===t.get("pageButtonPosition",!0)?y[i]+=n[r]-p[r]:g[i]+=p[r]+v);y[1-i]+=c[o]/2-p[o]/2,l.setPosition(f),u.setPosition(g),h.setPosition(y);var m={x:0,y:0};if(m[r]=d?n[r]:c[r],m[o]=Math.max(c[o],p[o]),m[a]=Math.min(0,p[a]+y[1-i]),u.__rectSize=n[r],d){var x={x:0,y:0};x[r]=Math.max(n[r]-p[r]-v,0),x[o]=m[o],u.setClipPath(new Ws({shape:x})),u.__rectSize=x[r]}else h.eachChild((function(t){t.attr({invisible:!0,silent:!0})}));var _=this._getPageInfo(t);return null!=_.pageIndex&&vh(l,{x:_.contentPosition[0],y:_.contentPosition[1]},d?t:null),this._updatePageInfoView(t,_),m},e.prototype._pageGo=function(t,e,n){var i=this._getPageInfo(e)[t];null!=i&&n.dispatchAction({type:"legendScroll",scrollDataIndex:i,legendId:e.id})},e.prototype._updatePageInfoView=function(t,e){var n=this._controllerGroup;E(["pagePrev","pageNext"],(function(i){var r=null!=e[i+"DataIndex"],o=n.childOfName(i);o&&(o.setStyle("fill",r?t.get("pageIconColor",!0):t.get("pageIconInactiveColor",!0)),o.cursor=r?"pointer":"default")}));var i=n.childOfName("pageText"),r=t.get("pageFormatter"),o=e.pageIndex,a=null!=o?o+1:0,s=e.pageCount;i&&r&&i.setStyle("text",U(r)?r.replace("{current}",null==a?"":a+"").replace("{total}",null==s?"":s+""):r({current:a,total:s}))},e.prototype._getPageInfo=function(t){var e=t.get("scrollDataIndex",!0),n=this.getContentGroup(),i=this._containerGroup.__rectSize,r=t.getOrient().index,o=lF[r],a=uF[r],s=this._findTargetItemIndex(e),l=n.children(),u=l[s],h=l.length,c=h?1:0,p={contentPosition:[n.x,n.y],pageCount:c,pageIndex:c-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!u)return p;var d=m(u);p.contentPosition[r]=-d.s;for(var f=s+1,g=d,y=d,v=null;f<=h;++f)(!(v=m(l[f]))&&y.e>g.s+i||v&&!x(v,g.s))&&(g=y.i>g.i?y:v)&&(null==p.pageNextDataIndex&&(p.pageNextDataIndex=g.i),++p.pageCount),y=v;for(f=s-1,g=d,y=d,v=null;f>=-1;--f)(v=m(l[f]))&&x(y,v.s)||!(g.i=e&&t.s<=e+i}},e.prototype._findTargetItemIndex=function(t){return this._showController?(this.getContentGroup().eachChild((function(i,r){var o=i.__legendDataIndex;null==n&&null!=o&&(n=r),o===t&&(e=r)})),null!=e?e:n):0;var e,n},e.type="legend.scroll",e}(KB);function cF(t){Vm(rF),t.registerComponentModel(oF),t.registerComponentView(hF),function(t){t.registerAction("legendScroll","legendscroll",(function(t,e){var n=t.scrollDataIndex;null!=n&&e.eachComponent({mainType:"legend",subType:"scroll",query:t},(function(t){t.setScrollDataIndex(n)}))}))}(t)}var pF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="dataZoom.inside",e.defaultOption=kc(lz.defaultOption,{disabled:!1,zoomLock:!1,zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}),e}(lz),dF=Vo();function fF(t,e,n){dF(t).coordSysRecordMap.each((function(t){var i=t.dataZoomInfoMap.get(e.uid);i&&(i.getRange=n)}))}function gF(t,e){if(e){t.removeKey(e.model.uid);var n=e.controller;n&&n.dispose()}}function yF(t,e){t.isDisposed()||t.dispatchAction({type:"dataZoom",animation:{easing:"cubicOut",duration:100},batch:e})}function vF(t,e,n,i){return t.coordinateSystem.containPoint([n,i])}function mF(t){t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,(function(t,e){var n=dF(e),i=n.coordSysRecordMap||(n.coordSysRecordMap=yt());i.each((function(t){t.dataZoomInfoMap=null})),t.eachComponent({mainType:"dataZoom",subType:"inside"},(function(t){E(az(t).infoList,(function(n){var r=n.model.uid,o=i.get(r)||i.set(r,function(t,e){var n={model:e,containsPoint:H(vF,e),dispatchAction:H(yF,t),dataZoomInfoMap:null,controller:null},i=n.controller=new nT(t.getZr());return E(["pan","zoom","scrollMove"],(function(t){i.on(t,(function(e){var i=[];n.dataZoomInfoMap.each((function(r){if(e.isAvailableBehavior(r.model.option)){var o=(r.getRange||{})[t],a=o&&o(r.dzReferCoordSysInfo,n.model.mainType,n.controller,e);!r.model.get("disabled",!0)&&a&&i.push({dataZoomId:r.model.id,start:a[0],end:a[1]})}})),i.length&&n.dispatchAction(i)}))})),n}(e,n.model));(o.dataZoomInfoMap||(o.dataZoomInfoMap=yt())).set(t.uid,{dzReferCoordSysInfo:n,model:t,getRange:null})}))})),i.each((function(t){var e,n=t.controller,r=t.dataZoomInfoMap;if(r){var o=r.keys()[0];null!=o&&(e=r.get(o))}if(e){var a=function(t){var e,n="type_",i={type_true:2,type_move:1,type_false:0,type_undefined:-1},r=!0;return t.each((function(t){var o=t.model,a=!o.get("disabled",!0)&&(!o.get("zoomLock",!0)||"move");i[n+a]>i[n+e]&&(e=a),r=r&&o.get("preventDefaultMouseMove",!0)})),{controlType:e,opt:{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!0,preventDefaultMouseMove:!!r}}}(r);n.enable(a.controlType,a.opt),n.setPointerChecker(t.containsPoint),Hg(t,"dispatchAction",e.model.get("throttle",!0),"fixRate")}else gF(i,t)}))}))}var xF=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="dataZoom.inside",e}return n(e,t),e.prototype.render=function(e,n,i){t.prototype.render.apply(this,arguments),e.noTarget()?this._clear():(this.range=e.getPercentRange(),fF(i,e,{pan:W(_F.pan,this),zoom:W(_F.zoom,this),scrollMove:W(_F.scrollMove,this)}))},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){!function(t,e){for(var n=dF(t).coordSysRecordMap,i=n.keys(),r=0;r0?s.pixelStart+s.pixelLength-s.pixel:s.pixel-s.pixelStart)/s.pixelLength*(o[1]-o[0])+o[0],u=Math.max(1/i.scale,0);o[0]=(o[0]-l)*u+l,o[1]=(o[1]-l)*u+l;var h=this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();return zk(0,o,[0,100],0,h.minSpan,h.maxSpan),this.range=o,r[0]!==o[0]||r[1]!==o[1]?o:void 0}},pan:bF((function(t,e,n,i,r,o){var a=wF[i]([o.oldX,o.oldY],[o.newX,o.newY],e,r,n);return a.signal*(t[1]-t[0])*a.pixel/a.pixelLength})),scrollMove:bF((function(t,e,n,i,r,o){return wF[i]([0,0],[o.scrollDelta,o.scrollDelta],e,r,n).signal*(t[1]-t[0])*o.scrollDelta}))};function bF(t){return function(e,n,i,r){var o=this.range,a=o.slice(),s=e.axisModels[0];if(s)return zk(t(a,s,e,n,i,r),a,[0,100],"all"),this.range=a,o[0]!==a[0]||o[1]!==a[1]?a:void 0}}var wF={grid:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem.getRect();return t=t||[0,0],"x"===o.dim?(a.pixel=e[0]-t[0],a.pixelLength=s.width,a.pixelStart=s.x,a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=s.height,a.pixelStart=s.y,a.signal=o.inverse?-1:1),a},polar:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem,l=s.getRadiusAxis().getExtent(),u=s.getAngleAxis().getExtent();return t=t?s.pointToCoord(t):[0,0],e=s.pointToCoord(e),"radiusAxis"===n.mainType?(a.pixel=e[0]-t[0],a.pixelLength=l[1]-l[0],a.pixelStart=l[0],a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=u[1]-u[0],a.pixelStart=u[0],a.signal=o.inverse?-1:1),a},singleAxis:function(t,e,n,i,r){var o=n.axis,a=r.model.coordinateSystem.getRect(),s={};return t=t||[0,0],"horizontal"===o.orient?(s.pixel=e[0]-t[0],s.pixelLength=a.width,s.pixelStart=a.x,s.signal=o.inverse?1:-1):(s.pixel=e[1]-t[1],s.pixelLength=a.height,s.pixelStart=a.y,s.signal=o.inverse?-1:1),s}};function SF(t){mz(t),t.registerComponentModel(pF),t.registerComponentView(xF),mF(t)}var MF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="dataZoom.slider",e.layoutMode="box",e.defaultOption=kc(lz.defaultOption,{show:!0,right:"ph",top:"ph",width:"ph",height:"ph",left:null,bottom:null,borderColor:"#d2dbee",borderRadius:3,backgroundColor:"rgba(47,69,84,0)",dataBackground:{lineStyle:{color:"#d2dbee",width:.5},areaStyle:{color:"#d2dbee",opacity:.2}},selectedDataBackground:{lineStyle:{color:"#8fb0f7",width:.5},areaStyle:{color:"#8fb0f7",opacity:.2}},fillerColor:"rgba(135,175,274,0.2)",handleIcon:"path://M-9.35,34.56V42m0-40V9.5m-2,0h4a2,2,0,0,1,2,2v21a2,2,0,0,1-2,2h-4a2,2,0,0,1-2-2v-21A2,2,0,0,1-11.35,9.5Z",handleSize:"100%",handleStyle:{color:"#fff",borderColor:"#ACB8D1"},moveHandleSize:7,moveHandleIcon:"path://M-320.9-50L-320.9-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-348-41-339-50-320.9-50z M-212.3-50L-212.3-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-239.4-41-230.4-50-212.3-50z M-103.7-50L-103.7-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-130.9-41-121.8-50-103.7-50z",moveHandleStyle:{color:"#D2DBEE",opacity:.7},showDetail:!0,showDataShadow:"auto",realtime:!0,zoomLock:!1,textStyle:{color:"#6E7079"},brushSelect:!0,brushStyle:{color:"rgba(135,175,274,0.15)"},emphasis:{handleLabel:{show:!0},handleStyle:{borderColor:"#8FB0F7"},moveHandleStyle:{color:"#8FB0F7"}}}),e}(lz),IF=Ws,TF="horizontal",CF="vertical",DF=["line","bar","candlestick","scatter"],AF={easing:"cubicOut",duration:100,delay:0},kF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._displayables={},n}return n(e,t),e.prototype.init=function(t,e){this.api=e,this._onBrush=W(this._onBrush,this),this._onBrushEnd=W(this._onBrushEnd,this)},e.prototype.render=function(e,n,i,r){if(t.prototype.render.apply(this,arguments),Hg(this,"_dispatchZoomAction",e.get("throttle"),"fixRate"),this._orient=e.getOrient(),!1!==e.get("show")){if(e.noTarget())return this._clear(),void this.group.removeAll();r&&"dataZoom"===r.type&&r.from===this.uid||this._buildView(),this._updateView()}else this.group.removeAll()},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){Yg(this,"_dispatchZoomAction");var t=this.api.getZr();t.off("mousemove",this._onBrush),t.off("mouseup",this._onBrushEnd)},e.prototype._buildView=function(){var t=this.group;t.removeAll(),this._brushing=!1,this._displayables.brushRect=null,this._resetLocation(),this._resetInterval();var e=this._displayables.sliderGroup=new Br;this._renderBackground(),this._renderHandle(),this._renderDataShadow(),t.add(e),this._positionGroup()},e.prototype._resetLocation=function(){var t=this.dataZoomModel,e=this.api,n=t.get("brushSelect")?7:0,i=this._findCoordRect(),r={width:e.getWidth(),height:e.getHeight()},o=this._orient===TF?{right:r.width-i.x-i.width,top:r.height-30-7-n,width:i.width,height:30}:{right:7,top:i.y,width:30,height:i.height},a=Rp(t.option);E(["right","top","width","height"],(function(t){"ph"===a[t]&&(a[t]=o[t])}));var s=kp(a,r);this._location={x:s.x,y:s.y},this._size=[s.width,s.height],this._orient===CF&&this._size.reverse()},e.prototype._positionGroup=function(){var t=this.group,e=this._location,n=this._orient,i=this.dataZoomModel.getFirstTargetAxisModel(),r=i&&i.get("inverse"),o=this._displayables.sliderGroup,a=(this._dataShadowInfo||{}).otherAxisInverse;o.attr(n!==TF||r?n===TF&&r?{scaleY:a?1:-1,scaleX:-1}:n!==CF||r?{scaleY:a?-1:1,scaleX:-1,rotation:Math.PI/2}:{scaleY:a?-1:1,scaleX:1,rotation:Math.PI/2}:{scaleY:a?1:-1,scaleX:1});var s=t.getBoundingRect([o]);t.x=e.x-s.x,t.y=e.y-s.y,t.markRedraw()},e.prototype._getViewExtent=function(){return[0,this._size[0]]},e.prototype._renderBackground=function(){var t=this.dataZoomModel,e=this._size,n=this._displayables.sliderGroup,i=t.get("brushSelect");n.add(new IF({silent:!0,shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:t.get("backgroundColor")},z2:-40}));var r=new IF({shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:"transparent"},z2:0,onclick:W(this._onClickPanel,this)}),o=this.api.getZr();i?(r.on("mousedown",this._onBrushStart,this),r.cursor="crosshair",o.on("mousemove",this._onBrush),o.on("mouseup",this._onBrushEnd)):(o.off("mousemove",this._onBrush),o.off("mouseup",this._onBrushEnd)),n.add(r)},e.prototype._renderDataShadow=function(){var t=this._dataShadowInfo=this._prepareDataShadowInfo();if(this._displayables.dataShadowSegs=[],t){var e=this._size,n=this._shadowSize||[],i=t.series,r=i.getRawData(),o=i.getShadowDim&&i.getShadowDim(),a=o&&r.getDimensionInfo(o)?i.getShadowDim():t.otherDim;if(null!=a){var s=this._shadowPolygonPts,l=this._shadowPolylinePts;if(r!==this._shadowData||a!==this._shadowDim||e[0]!==n[0]||e[1]!==n[1]){var u=r.getDataExtent(a),h=.3*(u[1]-u[0]);u=[u[0]-h,u[1]+h];var c,p=[0,e[1]],d=[0,e[0]],f=[[e[0],0],[0,0]],g=[],y=d[1]/(r.count()-1),v=0,m=Math.round(r.count()/e[0]);r.each([a],(function(t,e){if(m>0&&e%m)v+=y;else{var n=null==t||isNaN(t)||""===t,i=n?0:Kr(t,u,p,!0);n&&!c&&e?(f.push([f[f.length-1][0],0]),g.push([g[g.length-1][0],0])):!n&&c&&(f.push([v,0]),g.push([v,0])),f.push([v,i]),g.push([v,i]),v+=y,c=n}})),s=this._shadowPolygonPts=f,l=this._shadowPolylinePts=g}this._shadowData=r,this._shadowDim=a,this._shadowSize=[e[0],e[1]];for(var x=this.dataZoomModel,_=0;_<3;_++){var b=w(1===_);this._displayables.sliderGroup.add(b),this._displayables.dataShadowSegs.push(b)}}}function w(t){var e=x.getModel(t?"selectedDataBackground":"dataBackground"),n=new Br,i=new Xu({shape:{points:s},segmentIgnoreThreshold:1,style:e.getModel("areaStyle").getAreaStyle(),silent:!0,z2:-20}),r=new Zu({shape:{points:l},segmentIgnoreThreshold:1,style:e.getModel("lineStyle").getLineStyle(),silent:!0,z2:-19});return n.add(i),n.add(r),n}},e.prototype._prepareDataShadowInfo=function(){var t=this.dataZoomModel,e=t.get("showDataShadow");if(!1!==e){var n,i=this.ecModel;return t.eachTargetAxis((function(r,o){E(t.getAxisProxy(r,o).getTargetSeriesModels(),(function(t){if(!(n||!0!==e&&P(DF,t.get("type"))<0)){var a,s=i.getComponent(rz(r),o).axis,l=function(t){var e={x:"y",y:"x",radius:"angle",angle:"radius"};return e[t]}(r),u=t.coordinateSystem;null!=l&&u.getOtherAxis&&(a=u.getOtherAxis(s).inverse),l=t.getData().mapDimension(l),n={thisAxis:s,series:t,thisDim:r,otherDim:l,otherAxisInverse:a}}}),this)}),this),n}},e.prototype._renderHandle=function(){var t=this.group,e=this._displayables,n=e.handles=[null,null],i=e.handleLabels=[null,null],r=this._displayables.sliderGroup,o=this._size,a=this.dataZoomModel,s=this.api,l=a.get("borderRadius")||0,u=a.get("brushSelect"),h=e.filler=new IF({silent:u,style:{fill:a.get("fillerColor")},textConfig:{position:"inside"}});r.add(h),r.add(new IF({silent:!0,subPixelOptimize:!0,shape:{x:0,y:0,width:o[0],height:o[1],r:l},style:{stroke:a.get("dataBackgroundColor")||a.get("borderColor"),lineWidth:1,fill:"rgba(0,0,0,0)"}})),E([0,1],(function(e){var o=a.get("handleIcon");!Wy[o]&&o.indexOf("path://")<0&&o.indexOf("image://")<0&&(o="path://"+o);var s=Xy(o,-1,0,2,2,null,!0);s.attr({cursor:LF(this._orient),draggable:!0,drift:W(this._onDragMove,this,e),ondragend:W(this._onDragEnd,this),onmouseover:W(this._showDataInfo,this,!0),onmouseout:W(this._showDataInfo,this,!1),z2:5});var l=s.getBoundingRect(),u=a.get("handleSize");this._handleHeight=$r(u,this._size[1]),this._handleWidth=l.width/l.height*this._handleHeight,s.setStyle(a.getModel("handleStyle").getItemStyle()),s.style.strokeNoScale=!0,s.rectHover=!0,s.ensureState("emphasis").style=a.getModel(["emphasis","handleStyle"]).getItemStyle(),Ul(s);var h=a.get("handleColor");null!=h&&(s.style.fill=h),r.add(n[e]=s);var c=a.getModel("textStyle"),p=(a.get("handleLabel")||{}).show||!1;t.add(i[e]=new Xs({silent:!0,invisible:!p,style:oc(c,{x:0,y:0,text:"",verticalAlign:"middle",align:"center",fill:c.getTextColor(),font:c.getFont()}),z2:10}))}),this);var c=h;if(u){var p=$r(a.get("moveHandleSize"),o[1]),d=e.moveHandle=new Ws({style:a.getModel("moveHandleStyle").getItemStyle(),silent:!0,shape:{r:[0,0,2,2],y:o[1]-.5,height:p}}),f=.8*p,g=e.moveHandleIcon=Xy(a.get("moveHandleIcon"),-f/2,-f/2,f,f,"#fff",!0);g.silent=!0,g.y=o[1]+p/2-.5,d.ensureState("emphasis").style=a.getModel(["emphasis","moveHandleStyle"]).getItemStyle();var y=Math.min(o[1]/2,Math.max(p,10));(c=e.moveZone=new Ws({invisible:!0,shape:{y:o[1]-y,height:p+y}})).on("mouseover",(function(){s.enterEmphasis(d)})).on("mouseout",(function(){s.leaveEmphasis(d)})),r.add(d),r.add(g),r.add(c)}c.attr({draggable:!0,cursor:LF(this._orient),drift:W(this._onDragMove,this,"all"),ondragstart:W(this._showDataInfo,this,!0),ondragend:W(this._onDragEnd,this),onmouseover:W(this._showDataInfo,this,!0),onmouseout:W(this._showDataInfo,this,!1)})},e.prototype._resetInterval=function(){var t=this._range=this.dataZoomModel.getPercentRange(),e=this._getViewExtent();this._handleEnds=[Kr(t[0],[0,100],e,!0),Kr(t[1],[0,100],e,!0)]},e.prototype._updateInterval=function(t,e){var n=this.dataZoomModel,i=this._handleEnds,r=this._getViewExtent(),o=n.findRepresentativeAxisProxy().getMinMaxSpan(),a=[0,100];zk(e,i,r,n.get("zoomLock")?"all":t,null!=o.minSpan?Kr(o.minSpan,a,r,!0):null,null!=o.maxSpan?Kr(o.maxSpan,a,r,!0):null);var s=this._range,l=this._range=Qr([Kr(i[0],r,a,!0),Kr(i[1],r,a,!0)]);return!s||s[0]!==l[0]||s[1]!==l[1]},e.prototype._updateView=function(t){var e=this._displayables,n=this._handleEnds,i=Qr(n.slice()),r=this._size;E([0,1],(function(t){var i=e.handles[t],o=this._handleHeight;i.attr({scaleX:o/2,scaleY:o/2,x:n[t]+(t?-1:1),y:r[1]/2-o/2})}),this),e.filler.setShape({x:i[0],y:0,width:i[1]-i[0],height:r[1]});var o={x:i[0],width:i[1]-i[0]};e.moveHandle&&(e.moveHandle.setShape(o),e.moveZone.setShape(o),e.moveZone.getBoundingRect(),e.moveHandleIcon&&e.moveHandleIcon.attr("x",o.x+o.width/2));for(var a=e.dataShadowSegs,s=[0,i[0],i[1],r[0]],l=0;le[0]||n[1]<0||n[1]>e[1])){var i=this._handleEnds,r=(i[0]+i[1])/2,o=this._updateInterval("all",n[0]-r);this._updateView(),o&&this._dispatchZoomAction(!1)}},e.prototype._onBrushStart=function(t){var e=t.offsetX,n=t.offsetY;this._brushStart=new De(e,n),this._brushing=!0,this._brushStartTime=+new Date},e.prototype._onBrushEnd=function(t){if(this._brushing){var e=this._displayables.brushRect;if(this._brushing=!1,e){e.attr("ignore",!0);var n=e.shape;if(!(+new Date-this._brushStartTime<200&&Math.abs(n.width)<5)){var i=this._getViewExtent(),r=[0,100];this._range=Qr([Kr(n.x,i,r,!0),Kr(n.x+n.width,i,r,!0)]),this._handleEnds=[n.x,n.x+n.width],this._updateView(),this._dispatchZoomAction(!1)}}}},e.prototype._onBrush=function(t){this._brushing&&(de(t.event),this._updateBrushRect(t.offsetX,t.offsetY))},e.prototype._updateBrushRect=function(t,e){var n=this._displayables,i=this.dataZoomModel,r=n.brushRect;r||(r=n.brushRect=new IF({silent:!0,style:i.getModel("brushStyle").getItemStyle()}),n.sliderGroup.add(r)),r.attr("ignore",!1);var o=this._brushStart,a=this._displayables.sliderGroup,s=a.transformCoordToLocal(t,e),l=a.transformCoordToLocal(o.x,o.y),u=this._size;s[0]=Math.max(Math.min(u[0],s[0]),0),r.setShape({x:l[0],y:0,width:s[0]-l[0],height:u[1]})},e.prototype._dispatchZoomAction=function(t){var e=this._range;this.api.dispatchAction({type:"dataZoom",from:this.uid,dataZoomId:this.dataZoomModel.id,animation:t?AF:null,start:e[0],end:e[1]})},e.prototype._findCoordRect=function(){var t,e=az(this.dataZoomModel).infoList;if(!t&&e.length){var n=e[0].model.coordinateSystem;t=n.getRect&&n.getRect()}if(!t){var i=this.api.getWidth(),r=this.api.getHeight();t={x:.2*i,y:.2*r,width:.6*i,height:.6*r}}return t},e.type="dataZoom.slider",e}(cz);function LF(t){return"vertical"===t?"ns-resize":"ew-resize"}function PF(t){t.registerComponentModel(MF),t.registerComponentView(kF),mz(t)}var OF=function(t,e,n){var i=T((RF[t]||{})[e]);return n&&Y(i)?i[i.length-1]:i},RF={color:{active:["#006edd","#e0ffff"],inactive:["rgba(0,0,0,0)"]},colorHue:{active:[0,360],inactive:[0,0]},colorSaturation:{active:[.3,1],inactive:[0,0]},colorLightness:{active:[.9,.5],inactive:[0,0]},colorAlpha:{active:[.3,1],inactive:[0,0]},opacity:{active:[.3,1],inactive:[0,0]},symbol:{active:["circle","roundRect","diamond"],inactive:["none"]},symbolSize:{active:[10,50],inactive:[0,0]}},NF=kD.mapVisual,EF=kD.eachVisual,zF=Y,VF=E,BF=Qr,FF=Kr,GF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.stateList=["inRange","outOfRange"],n.replacableOptionKeys=["inRange","outOfRange","target","controller","color"],n.layoutMode={type:"box",ignoreSize:!0},n.dataBound=[-1/0,1/0],n.targetVisuals={},n.controllerVisuals={},n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n)},e.prototype.optionUpdated=function(t,e){var n=this.option;!e&&RV(n,t,this.replacableOptionKeys),this.textStyleModel=this.getModel("textStyle"),this.resetItemSize(),this.completeVisualOption()},e.prototype.resetVisual=function(t){var e=this.stateList;t=W(t,this),this.controllerVisuals=OV(this.option.controller,e,t),this.targetVisuals=OV(this.option.target,e,t)},e.prototype.getItemSymbol=function(){return null},e.prototype.getTargetSeriesIndices=function(){var t=this.option.seriesIndex,e=[];return null==t||"all"===t?this.ecModel.eachSeries((function(t,n){e.push(n)})):e=To(t),e},e.prototype.eachTargetSeries=function(t,e){E(this.getTargetSeriesIndices(),(function(n){var i=this.ecModel.getSeriesByIndex(n);i&&t.call(e,i)}),this)},e.prototype.isTargetSeries=function(t){var e=!1;return this.eachTargetSeries((function(n){n===t&&(e=!0)})),e},e.prototype.formatValueText=function(t,e,n){var i,r=this.option,o=r.precision,a=this.dataBound,s=r.formatter;n=n||["<",">"],Y(t)&&(t=t.slice(),i=!0);var l=e?t:i?[u(t[0]),u(t[1])]:u(t);return U(s)?s.replace("{value}",i?l[0]:l).replace("{value2}",i?l[1]:l):X(s)?i?s(t[0],t[1]):s(t):i?t[0]===a[0]?n[0]+" "+l[1]:t[1]===a[1]?n[1]+" "+l[0]:l[0]+" - "+l[1]:l;function u(t){return t===a[0]?"min":t===a[1]?"max":(+t).toFixed(Math.min(o,20))}},e.prototype.resetExtent=function(){var t=this.option,e=BF([t.min,t.max]);this._dataExtent=e},e.prototype.getDataDimensionIndex=function(t){var e=this.option.dimension;if(null!=e)return t.getDimensionIndex(e);for(var n=t.dimensions,i=n.length-1;i>=0;i--){var r=n[i],o=t.getDimensionInfo(r);if(!o.isCalculationCoord)return o.storeDimIndex}},e.prototype.getExtent=function(){return this._dataExtent.slice()},e.prototype.completeVisualOption=function(){var t=this.ecModel,e=this.option,n={inRange:e.inRange,outOfRange:e.outOfRange},i=e.target||(e.target={}),r=e.controller||(e.controller={});C(i,n),C(r,n);var o=this.isCategory();function a(n){zF(e.color)&&!n.inRange&&(n.inRange={color:e.color.slice().reverse()}),n.inRange=n.inRange||{color:t.get("gradientColor")}}a.call(this,i),a.call(this,r),function(t,e,n){var i=t[e],r=t[n];i&&!r&&(r=t[n]={},VF(i,(function(t,e){if(kD.isValidType(e)){var n=OF(e,"inactive",o);null!=n&&(r[e]=n,"color"!==e||r.hasOwnProperty("opacity")||r.hasOwnProperty("colorAlpha")||(r.opacity=[0,0]))}})))}.call(this,i,"inRange","outOfRange"),function(t){var e=(t.inRange||{}).symbol||(t.outOfRange||{}).symbol,n=(t.inRange||{}).symbolSize||(t.outOfRange||{}).symbolSize,i=this.get("inactiveColor"),r=this.getItemSymbol()||"roundRect";VF(this.stateList,(function(a){var s=this.itemSize,l=t[a];l||(l=t[a]={color:o?i:[i]}),null==l.symbol&&(l.symbol=e&&T(e)||(o?r:[r])),null==l.symbolSize&&(l.symbolSize=n&&T(n)||(o?s[0]:[s[0],s[0]])),l.symbol=NF(l.symbol,(function(t){return"none"===t?r:t}));var u=l.symbolSize;if(null!=u){var h=-1/0;EF(u,(function(t){t>h&&(h=t)})),l.symbolSize=NF(u,(function(t){return FF(t,[0,h],[0,s[0]],!0)}))}}),this)}.call(this,r)},e.prototype.resetItemSize=function(){this.itemSize=[parseFloat(this.get("itemWidth")),parseFloat(this.get("itemHeight"))]},e.prototype.isCategory=function(){return!!this.option.categories},e.prototype.setSelected=function(t){},e.prototype.getSelected=function(){return null},e.prototype.getValueState=function(t){return null},e.prototype.getVisualMeta=function(t){return null},e.type="visualMap",e.dependencies=["series"],e.defaultOption={show:!0,z:4,seriesIndex:"all",min:0,max:200,left:0,right:null,top:null,bottom:0,itemWidth:null,itemHeight:null,inverse:!1,orient:"vertical",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",contentColor:"#5793f3",inactiveColor:"#aaa",borderWidth:0,padding:5,textGap:10,precision:0,textStyle:{color:"#333"}},e}(zp),WF=[20,140],HF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent(),this.resetVisual((function(t){t.mappingMethod="linear",t.dataExtent=this.getExtent()})),this._resetRange()},e.prototype.resetItemSize=function(){t.prototype.resetItemSize.apply(this,arguments);var e=this.itemSize;(null==e[0]||isNaN(e[0]))&&(e[0]=WF[0]),(null==e[1]||isNaN(e[1]))&&(e[1]=WF[1])},e.prototype._resetRange=function(){var t=this.getExtent(),e=this.option.range;!e||e.auto?(t.auto=1,this.option.range=t):Y(e)&&(e[0]>e[1]&&e.reverse(),e[0]=Math.max(e[0],t[0]),e[1]=Math.min(e[1],t[1]))},e.prototype.completeVisualOption=function(){t.prototype.completeVisualOption.apply(this,arguments),E(this.stateList,(function(t){var e=this.option.controller[t].symbolSize;e&&e[0]!==e[1]&&(e[0]=e[1]/3)}),this)},e.prototype.setSelected=function(t){this.option.range=t.slice(),this._resetRange()},e.prototype.getSelected=function(){var t=this.getExtent(),e=Qr((this.get("range")||[]).slice());return e[0]>t[1]&&(e[0]=t[1]),e[1]>t[1]&&(e[1]=t[1]),e[0]=n[1]||t<=e[1])?"inRange":"outOfRange"},e.prototype.findTargetDataIndices=function(t){var e=[];return this.eachTargetSeries((function(n){var i=[],r=n.getData();r.each(this.getDataDimensionIndex(r),(function(e,n){t[0]<=e&&e<=t[1]&&i.push(n)}),this),e.push({seriesId:n.id,dataIndex:i})}),this),e},e.prototype.getVisualMeta=function(t){var e=YF(this,"outOfRange",this.getExtent()),n=YF(this,"inRange",this.option.range.slice()),i=[];function r(e,n){i.push({value:e,color:t(e,n)})}for(var o=0,a=0,s=n.length,l=e.length;at[1])break;n.push({color:this.getControllerVisual(o,"color",e),offset:r/100})}return n.push({color:this.getControllerVisual(t[1],"color",e),offset:1}),n},e.prototype._createBarPoints=function(t,e){var n=this.visualMapModel.itemSize;return[[n[0]-e[0],t[0]],[n[0],t[0]],[n[0],t[1]],[n[0]-e[1],t[1]]]},e.prototype._createBarGroup=function(t){var e=this._orient,n=this.visualMapModel.get("inverse");return new Br("horizontal"!==e||n?"horizontal"===e&&n?{scaleX:"bottom"===t?-1:1,rotation:-Math.PI/2}:"vertical"!==e||n?{scaleX:"left"===t?1:-1}:{scaleX:"left"===t?1:-1,scaleY:-1}:{scaleX:"bottom"===t?1:-1,rotation:Math.PI/2})},e.prototype._updateHandle=function(t,e){if(this._useHandle){var n=this._shapes,i=this.visualMapModel,r=n.handleThumbs,o=n.handleLabels,a=i.itemSize,s=i.getExtent(),l=this._applyTransform("left",n.mainGroup);KF([0,1],(function(u){var h=r[u];h.setStyle("fill",e.handlesColor[u]),h.y=t[u];var c=qF(t[u],[0,a[1]],s,!0),p=this.getControllerVisual(c,"symbolSize");h.scaleX=h.scaleY=p/a[0],h.x=a[0]-p/2;var d=Fh(n.handleLabelPoints[u],Bh(h,this.group));if("horizontal"===this._orient){var f="left"===l||"top"===l?(a[0]-p)/2:(a[0]-p)/-2;d[1]+=f}o[u].setStyle({x:d[0],y:d[1],text:i.formatValueText(this._dataInterval[u]),verticalAlign:"middle",align:"vertical"===this._orient?this._applyTransform("left",n.mainGroup):"center"})}),this)}},e.prototype._showIndicator=function(t,e,n,i){var r=this.visualMapModel,o=r.getExtent(),a=r.itemSize,s=[0,a[1]],l=this._shapes,u=l.indicator;if(u){u.attr("invisible",!1);var h=this.getControllerVisual(t,"color",{convertOpacityToAlpha:!0}),c=this.getControllerVisual(t,"symbolSize"),p=qF(t,o,s,!0),d=a[0]-c/2,f={x:u.x,y:u.y};u.y=p,u.x=d;var g=Fh(l.indicatorLabelPoint,Bh(u,this.group)),y=l.indicatorLabel;y.attr("invisible",!1);var v=this._applyTransform("left",l.mainGroup),m="horizontal"===this._orient;y.setStyle({text:(n||"")+r.formatValueText(e),verticalAlign:m?v:"middle",align:m?"center":v});var x={x:d,y:p,style:{fill:h}},_={style:{x:g[0],y:g[1]}};if(r.ecModel.isAnimationEnabled()&&!this._firstShowIndicator){var b={duration:100,easing:"cubicInOut",additive:!0};u.x=f.x,u.y=f.y,u.animateTo(x,b),y.animateTo(_,b)}else u.attr(x),y.attr(_);this._firstShowIndicator=!1;var w=this._shapes.handleLabels;if(w)for(var S=0;Sr[1]&&(u[1]=1/0),e&&(u[0]===-1/0?this._showIndicator(l,u[1],"< ",a):u[1]===1/0?this._showIndicator(l,u[0],"> ",a):this._showIndicator(l,l,"≈ ",a));var h=this._hoverLinkDataIndices,c=[];(e||eG(n))&&(c=this._hoverLinkDataIndices=n.findTargetDataIndices(u));var p=function(t,e){var n={},i={};return r(t||[],n),r(e||[],i,n),[o(n),o(i)];function r(t,e,n){for(var i=0,r=t.length;i=0&&(r.dimension=o,i.push(r))}})),t.getData().setVisual("visualMeta",i)}}];function aG(t,e,n,i){for(var r=e.targetVisuals[i],o=kD.prepareVisualTypes(r),a={color:Ay(t.getData(),"color")},s=0,l=o.length;s0:t.splitNumber>0)&&!t.calculable?"piecewise":"continuous"})),t.registerAction(iG,rG),E(oG,(function(e){t.registerVisual(t.PRIORITY.VISUAL.COMPONENT,e)})),t.registerPreprocessor(lG))}function pG(t){t.registerComponentModel(HF),t.registerComponentView(QF),cG(t)}var dG=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._pieceList=[],n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent();var i=this._mode=this._determineMode();this._pieceList=[],fG[this._mode].call(this,this._pieceList),this._resetSelected(e,n);var r=this.option.categories;this.resetVisual((function(t,e){"categories"===i?(t.mappingMethod="category",t.categories=T(r)):(t.dataExtent=this.getExtent(),t.mappingMethod="piecewise",t.pieceList=z(this._pieceList,(function(t){return t=T(t),"inRange"!==e&&(t.visual=null),t})))}))},e.prototype.completeVisualOption=function(){var e=this.option,n={},i=kD.listVisualTypes(),r=this.isCategory();function o(t,e,n){return t&&t[e]&&t[e].hasOwnProperty(n)}E(e.pieces,(function(t){E(i,(function(e){t.hasOwnProperty(e)&&(n[e]=1)}))})),E(n,(function(t,n){var i=!1;E(this.stateList,(function(t){i=i||o(e,t,n)||o(e.target,t,n)}),this),!i&&E(this.stateList,(function(t){(e[t]||(e[t]={}))[n]=OF(n,"inRange"===t?"active":"inactive",r)}))}),this),t.prototype.completeVisualOption.apply(this,arguments)},e.prototype._resetSelected=function(t,e){var n=this.option,i=this._pieceList,r=(e?n:t).selected||{};if(n.selected=r,E(i,(function(t,e){var n=this.getSelectedMapKey(t);r.hasOwnProperty(n)||(r[n]=!0)}),this),"single"===n.selectedMode){var o=!1;E(i,(function(t,e){var n=this.getSelectedMapKey(t);r[n]&&(o?r[n]=!1:o=!0)}),this)}},e.prototype.getItemSymbol=function(){return this.get("itemSymbol")},e.prototype.getSelectedMapKey=function(t){return"categories"===this._mode?t.value+"":t.index+""},e.prototype.getPieceList=function(){return this._pieceList},e.prototype._determineMode=function(){var t=this.option;return t.pieces&&t.pieces.length>0?"pieces":this.option.categories?"categories":"splitNumber"},e.prototype.setSelected=function(t){this.option.selected=T(t)},e.prototype.getValueState=function(t){var e=kD.findPieceIndex(t,this._pieceList);return null!=e&&this.option.selected[this.getSelectedMapKey(this._pieceList[e])]?"inRange":"outOfRange"},e.prototype.findTargetDataIndices=function(t){var e=[],n=this._pieceList;return this.eachTargetSeries((function(i){var r=[],o=i.getData();o.each(this.getDataDimensionIndex(o),(function(e,i){kD.findPieceIndex(e,n)===t&&r.push(i)}),this),e.push({seriesId:i.id,dataIndex:r})}),this),e},e.prototype.getRepresentValue=function(t){var e;if(this.isCategory())e=t.value;else if(null!=t.value)e=t.value;else{var n=t.interval||[];e=n[0]===-1/0&&n[1]===1/0?0:(n[0]+n[1])/2}return e},e.prototype.getVisualMeta=function(t){if(!this.isCategory()){var e=[],n=["",""],i=this,r=this._pieceList.slice();if(r.length){var o=r[0].interval[0];o!==-1/0&&r.unshift({interval:[-1/0,o]}),(o=r[r.length-1].interval[1])!==1/0&&r.push({interval:[o,1/0]})}else r.push({interval:[-1/0,1/0]});var a=-1/0;return E(r,(function(t){var e=t.interval;e&&(e[0]>a&&s([a,e[0]],"outOfRange"),s(e.slice()),a=e[1])}),this),{stops:e,outerColors:n}}function s(r,o){var a=i.getRepresentValue({interval:r});o||(o=i.getValueState(a));var s=t(a,o);r[0]===-1/0?n[0]=s:r[1]===1/0?n[1]=s:e.push({value:r[0],color:s},{value:r[1],color:s})}},e.type="visualMap.piecewise",e.defaultOption=kc(GF.defaultOption,{selected:null,minOpen:!1,maxOpen:!1,align:"auto",itemWidth:20,itemHeight:14,itemSymbol:"roundRect",pieces:null,categories:null,splitNumber:5,selectedMode:"multiple",itemGap:10,hoverLink:!0}),e}(GF),fG={splitNumber:function(t){var e=this.option,n=Math.min(e.precision,20),i=this.getExtent(),r=e.splitNumber;r=Math.max(parseInt(r,10),1),e.splitNumber=r;for(var o=(i[1]-i[0])/r;+o.toFixed(n)!==o&&n<5;)n++;e.precision=n,o=+o.toFixed(n),e.minOpen&&t.push({interval:[-1/0,i[0]],close:[0,0]});for(var a=0,s=i[0];a","≥"][e[0]]];t.text=t.text||this.formatValueText(null!=t.value?t.value:t.interval,!1,n)}),this)}};function gG(t,e){var n=t.inverse;("vertical"===t.orient?!n:n)&&e.reverse()}var yG=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.doRender=function(){var t=this.group;t.removeAll();var e=this.visualMapModel,n=e.get("textGap"),i=e.textStyleModel,r=i.getFont(),o=i.getTextColor(),a=this._getItemAlign(),s=e.itemSize,l=this._getViewData(),u=l.endsText,h=it(e.get("showLabel",!0),!u),c=!e.get("selectedMode");u&&this._renderEndsText(t,u[0],s,h,a),E(l.viewPieceList,(function(i){var l=i.piece,u=new Br;u.onclick=W(this._onItemClick,this,l),this._enableHoverLink(u,i.indexInModelPieceList);var p=e.getRepresentValue(l);if(this._createItemSymbol(u,p,[0,0,s[0],s[1]],c),h){var d=this.visualMapModel.getValueState(p);u.add(new Xs({style:{x:"right"===a?-n:s[0]+n,y:s[1]/2,text:l.text,verticalAlign:"middle",align:a,font:r,fill:o,opacity:"outOfRange"===d?.5:1},silent:c}))}t.add(u)}),this),u&&this._renderEndsText(t,u[1],s,h,a),Ap(e.get("orient"),t,e.get("itemGap")),this.renderBackground(t),this.positionGroup(t)},e.prototype._enableHoverLink=function(t,e){var n=this;t.on("mouseover",(function(){return i("highlight")})).on("mouseout",(function(){return i("downplay")}));var i=function(t){var i=n.visualMapModel;i.option.hoverLink&&n.api.dispatchAction({type:t,batch:jF(i.findTargetDataIndices(e),i)})}},e.prototype._getItemAlign=function(){var t=this.visualMapModel,e=t.option;if("vertical"===e.orient)return ZF(t,this.api,t.itemSize);var n=e.align;return n&&"auto"!==n||(n="left"),n},e.prototype._renderEndsText=function(t,e,n,i,r){if(e){var o=new Br,a=this.visualMapModel.textStyleModel;o.add(new Xs({style:oc(a,{x:i?"right"===r?n[0]:0:n[0]/2,y:n[1]/2,verticalAlign:"middle",align:i?r:"center",text:e})})),t.add(o)}},e.prototype._getViewData=function(){var t=this.visualMapModel,e=z(t.getPieceList(),(function(t,e){return{piece:t,indexInModelPieceList:e}})),n=t.get("text"),i=t.get("orient"),r=t.get("inverse");return("horizontal"===i?r:!r)?e.reverse():n&&(n=n.slice().reverse()),{viewPieceList:e,endsText:n}},e.prototype._createItemSymbol=function(t,e,n,i){var r=Xy(this.getControllerVisual(e,"symbol"),n[0],n[1],n[2],n[3],this.getControllerVisual(e,"color"));r.silent=i,t.add(r)},e.prototype._onItemClick=function(t){var e=this.visualMapModel,n=e.option,i=n.selectedMode;if(i){var r=T(n.selected),o=e.getSelectedMapKey(t);"single"===i||!0===i?(r[o]=!0,E(r,(function(t,e){r[e]=e===o}))):r[o]=!r[o],this.api.dispatchAction({type:"selectDataRange",from:this.uid,visualMapId:this.visualMapModel.id,selected:r})}},e.type="visualMap.piecewise",e}(XF);function vG(t){t.registerComponentModel(dG),t.registerComponentView(yG),cG(t)}var mG={label:{enabled:!0},decal:{show:!1}},xG=Vo(),_G={};function bG(t,e){var n=t.getModel("aria");if(n.get("enabled")){var i=T(mG);C(i.label,t.getLocaleModel().get("aria"),!1),C(n.option,i,!1),function(){if(n.getModel("decal").get("show")){var e=yt();t.eachSeries((function(t){if(!t.isColorBySeries()){var n=e.get(t.type);n||(n={},e.set(t.type,n)),xG(t).scope=n}})),t.eachRawSeries((function(e){if(!t.isSeriesFiltered(e))if(X(e.enableAriaDecal))e.enableAriaDecal();else{var n=e.getData();if(e.isColorBySeries()){var i=pd(e.ecModel,e.name,_G,t.getSeriesCount()),r=n.getVisual("decal");n.setVisual("decal",u(r,i))}else{var o=e.getRawData(),a={},s=xG(e).scope;n.each((function(t){var e=n.getRawIndex(t);a[e]=t}));var l=o.count();o.each((function(t){var i=a[t],r=o.getName(t)||t+"",h=pd(e.ecModel,r,s,l),c=n.getItemVisual(i,"decal");n.setItemVisual(i,"decal",u(c,h))}))}}function u(t,e){var n=t?A(A({},e),t):e;return n.dirty=!0,n}}))}}(),function(){var i=e.getZr().dom;if(!i)return;var o=t.getLocaleModel().get("aria"),a=n.getModel("label");if(a.option=k(a.option,o),!a.get("enabled"))return;if(i.setAttribute("role","img"),a.get("description"))return void i.setAttribute("aria-label",a.get("description"));var s,l=t.getSeriesCount(),u=a.get(["data","maxCount"])||10,h=a.get(["series","maxCount"])||10,c=Math.min(l,h);if(l<1)return;var p=function(){var e=t.get("title");e&&e.length&&(e=e[0]);return e&&e.text}();s=p?r(a.get(["general","withTitle"]),{title:p}):a.get(["general","withoutTitle"]);var d=[];s+=r(l>1?a.get(["series","multiple","prefix"]):a.get(["series","single","prefix"]),{seriesCount:l}),t.eachSeries((function(e,n){if(n1?a.get(["series","multiple",o]):a.get(["series","single",o]),{seriesId:e.seriesIndex,seriesName:e.get("name"),seriesType:(_=e.subType,b=t.getLocaleModel().get(["series","typeNames"]),b[_]||b.chart)});var s=e.getData();if(s.count()>u)i+=r(a.get(["data","partialData"]),{displayCnt:u});else i+=a.get(["data","allData"]);for(var h=a.get(["data","separator","middle"]),p=a.get(["data","separator","end"]),f=a.get(["data","excludeDimensionId"]),g=[],y=0;y":"gt",">=":"gte","=":"eq","!=":"ne","<>":"ne"},MG=function(){function t(t){if(null==(this._condVal=U(t)?new RegExp(t):et(t)?t:null)){var e="";0,wo(e)}}return t.prototype.evaluate=function(t){var e=typeof t;return U(e)?this._condVal.test(t):!!j(e)&&this._condVal.test(t+"")},t}(),IG=function(){function t(){}return t.prototype.evaluate=function(){return this.value},t}(),TG=function(){function t(){}return t.prototype.evaluate=function(){for(var t=this.children,e=0;e2&&l.push(e),e=[t,n]}function f(t,n,i,r){BG(t,i)&&BG(n,r)||e.push(t,n,i,r,i,r)}function g(t,n,i,r,o,a){var s=Math.abs(n-t),l=4*Math.tan(s/4)/3,u=nM:C2&&l.push(e),l}function GG(t,e,n,i,r,o,a,s,l,u){if(BG(t,n)&&BG(e,i)&&BG(r,a)&&BG(o,s))l.push(a,s);else{var h=2/u,c=h*h,p=a-t,d=s-e,f=Math.sqrt(p*p+d*d);p/=f,d/=f;var g=n-t,y=i-e,v=r-a,m=o-s,x=g*g+y*y,_=v*v+m*m;if(x=0&&_-w*w=0)l.push(a,s);else{var S=[],M=[];wn(t,n,r,a,.5,S),wn(e,i,o,s,.5,M),GG(S[0],M[0],S[1],M[1],S[2],M[2],S[3],M[3],l,u),GG(S[4],M[4],S[5],M[5],S[6],M[6],S[7],M[7],l,u)}}}}function WG(t,e,n){var i=t[e],r=t[1-e],o=Math.abs(i/r),a=Math.ceil(Math.sqrt(o*n)),s=Math.floor(n/a);0===s&&(s=1,a=n);for(var l=[],u=0;u0)for(u=0;uMath.abs(u),c=WG([l,u],h?0:1,e),p=(h?s:u)/c.length,d=0;d1?null:new De(d*l+t,d*u+e)}function UG(t,e,n){var i=new De;De.sub(i,n,e),i.normalize();var r=new De;return De.sub(r,t,e),r.dot(i)}function ZG(t,e){var n=t[t.length-1];n&&n[0]===e[0]&&n[1]===e[1]||t.push(e)}function jG(t){var e=t.points,n=[],i=[];Ba(e,n,i);var r=new ze(n[0],n[1],i[0]-n[0],i[1]-n[1]),o=r.width,a=r.height,s=r.x,l=r.y,u=new De,h=new De;return o>a?(u.x=h.x=s+o/2,u.y=l,h.y=l+a):(u.y=h.y=l+a/2,u.x=s,h.x=s+o),function(t,e,n){for(var i=t.length,r=[],o=0;or,a=WG([i,r],o?0:1,e),s=o?"width":"height",l=o?"height":"width",u=o?"x":"y",h=o?"y":"x",c=t[s]/a.length,p=0;p0)for(var b=i/n,w=-i/2;w<=i/2;w+=b){var S=Math.sin(w),M=Math.cos(w),I=0;for(x=0;x0;l/=2){var u=0,h=0;(t&l)>0&&(u=1),(e&l)>0&&(h=1),s+=l*l*(3*u^h),0===h&&(1===u&&(t=l-1-t,e=l-1-e),a=t,t=e,e=a)}return s}function cW(t){var e=1/0,n=1/0,i=-1/0,r=-1/0,o=z(t,(function(t){var o=t.getBoundingRect(),a=t.getComputedTransform(),s=o.x+o.width/2+(a?a[4]:0),l=o.y+o.height/2+(a?a[5]:0);return e=Math.min(s,e),n=Math.min(l,n),i=Math.max(s,i),r=Math.max(l,r),[s,l]}));return z(o,(function(o,a){return{cp:o,z:hW(o[0],o[1],e,n,i,r),path:t[a]}})).sort((function(t,e){return t.z-e.z})).map((function(t){return t.path}))}function pW(t){return $G(t.path,t.count)}function dW(t){return Y(t[0])}function fW(t,e){for(var n=[],i=t.length,r=0;r=0;r--)if(!n[r].many.length){var l=n[s].many;if(l.length<=1){if(!s)return n;s=0}o=l.length;var u=Math.ceil(o/2);n[r].many=l.slice(u,o),n[s].many=l.slice(0,u),s++}return n}var gW={clone:function(t){for(var e=[],n=1-Math.pow(1-t.path.style.opacity,1/t.count),i=0;i0){var s,l,u=i.getModel("universalTransition").get("delay"),h=Object.assign({setToFinal:!0},a);dW(t)&&(s=t,l=e),dW(e)&&(s=e,l=t);for(var c=s?s===t:t.length>e.length,p=s?fW(l,s):fW(c?e:t,[c?t:e]),d=0,f=0;f1e4))for(var r=n.getIndices(),o=0;o0&&i.group.traverse((function(t){t instanceof ks&&!t.animators.length&&t.animateFrom({style:{opacity:0}},r)}))}))}function TW(t){var e=t.getModel("universalTransition").get("seriesKey");return e||t.id}function CW(t){return Y(t)?t.sort().join(","):t}function DW(t){if(t.hostModel)return t.hostModel.getModel("universalTransition").get("divideShape")}function AW(t,e){for(var n=0;n=0&&r.push({dataGroupId:e.oldDataGroupIds[n],data:e.oldData[n],divide:DW(e.oldData[n]),groupIdDim:t.dimension})})),E(To(t.to),(function(t){var i=AW(n.updatedSeries,t);if(i>=0){var r=n.updatedSeries[i].getData();o.push({dataGroupId:e.oldDataGroupIds[i],data:r,divide:DW(r),groupIdDim:t.dimension})}})),r.length>0&&o.length>0&&IW(r,o,i)}(t,i,n,e)}));else{var o=function(t,e){var n=yt(),i=yt(),r=yt();return E(t.oldSeries,(function(e,n){var o=t.oldDataGroupIds[n],a=t.oldData[n],s=TW(e),l=CW(s);i.set(l,{dataGroupId:o,data:a}),Y(s)&&E(s,(function(t){r.set(t,{key:l,dataGroupId:o,data:a})}))})),E(e.updatedSeries,(function(t){if(t.isUniversalTransitionEnabled()&&t.isAnimationEnabled()){var e=t.get("dataGroupId"),o=t.getData(),a=TW(t),s=CW(a),l=i.get(s);if(l)n.set(s,{oldSeries:[{dataGroupId:l.dataGroupId,divide:DW(l.data),data:l.data}],newSeries:[{dataGroupId:e,divide:DW(o),data:o}]});else if(Y(a)){var u=[];E(a,(function(t){var e=i.get(t);e.data&&u.push({dataGroupId:e.dataGroupId,divide:DW(e.data),data:e.data})})),u.length&&n.set(s,{oldSeries:u,newSeries:[{dataGroupId:e,data:o,divide:DW(o)}]})}else{var h=r.get(a);if(h){var c=n.get(h.key);c||(c={oldSeries:[{dataGroupId:h.dataGroupId,data:h.data,divide:DW(h.data)}],newSeries:[]},n.set(h.key,c)),c.newSeries.push({dataGroupId:e,data:o,divide:DW(o)})}}}})),n}(i,n);E(o.keys(),(function(t){var n=o.get(t);IW(n.oldSeries,n.newSeries,e)}))}E(n.updatedSeries,(function(t){t[_g]&&(t[_g]=!1)}))}for(var a=t.getSeries(),s=i.oldSeries=[],l=i.oldDataGroupIds=[],u=i.oldData=[],h=0;h + + + + + diff --git a/frontend/src/api/accounts.ts b/frontend/src/api/accounts.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5acef01680009ca375ec2cccdc33f382c90f48a --- /dev/null +++ b/frontend/src/api/accounts.ts @@ -0,0 +1,93 @@ +import apiClient from './client' +import type { + AccountsConfigResponse, + AccountsListResponse, + AccountConfigItem, + RegisterTask, + LoginTask, +} from '@/types/api' + +export const accountsApi = { + // 获取账户列表 + list: () => + apiClient.get('/admin/accounts'), + + // 获取账户配置 + getConfig: () => + apiClient.get('/admin/accounts-config'), + + // 更新账户配置 + updateConfig: (accounts: AccountConfigItem[]) => + apiClient.put('/admin/accounts-config', accounts), + + // 删除账户 + delete: (accountId: string) => + apiClient.delete(`/admin/accounts/${accountId}`), + + // 禁用账户 + disable: (accountId: string) => + apiClient.put(`/admin/accounts/${accountId}/disable`), + + // 启用账户 + enable: (accountId: string) => + apiClient.put(`/admin/accounts/${accountId}/enable`), + + // 批量启用账户(最多50个) + bulkEnable: (accountIds: string[]) => + apiClient.put( + '/admin/accounts/bulk-enable', + accountIds + ), + + // 批量禁用账户(最多50个) + bulkDisable: (accountIds: string[]) => + apiClient.put( + '/admin/accounts/bulk-disable', + accountIds + ), + // 批量删除账户(最多50个) + bulkDelete: (accountIds: string[]) => + apiClient.put( + '/admin/accounts/bulk-delete', + accountIds + ), + + startRegister: (count?: number, domain?: string, mail_provider?: string) => + apiClient.post('/admin/register/start', { count, domain, mail_provider }), + + getRegisterTask: (taskId: string) => + apiClient.get(`/admin/register/task/${taskId}`), + + getRegisterCurrent: () => + apiClient.get('/admin/register/current'), + + cancelRegisterTask: (taskId: string, reason?: string) => + apiClient.post<{ reason?: string }, RegisterTask>(`/admin/register/cancel/${taskId}`, reason ? { reason } : {}), + + startLogin: (accountIds: string[]) => + apiClient.post('/admin/login/start', accountIds), + + getLoginTask: (taskId: string) => + apiClient.get(`/admin/login/task/${taskId}`), + + getLoginCurrent: () => + apiClient.get('/admin/login/current'), + + cancelLoginTask: (taskId: string, reason?: string) => + apiClient.post<{ reason?: string }, LoginTask>(`/admin/login/cancel/${taskId}`, reason ? { reason } : {}), + + checkLogin: () => + apiClient.post('/admin/login/check'), + + // 暂停自动刷新 + pauseAutoRefresh: () => + apiClient.post('/admin/auto-refresh/pause'), + + // 恢复自动刷新 + resumeAutoRefresh: () => + apiClient.post('/admin/auto-refresh/resume'), + + // 获取自动刷新状态 + getAutoRefreshStatus: () => + apiClient.get('/admin/auto-refresh/status'), +} diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..9fe3a52e4771ac4965b295e53fac4a11825cc619 --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,20 @@ +import apiClient from './client' +import type { LoginRequest, LoginResponse } from '@/types/api' + +export const authApi = { + login: (data: LoginRequest) => { + const payload = new URLSearchParams() + payload.append('admin_key', data.password) + return apiClient.post('/login', payload, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }) + }, + + logout: () => + apiClient.post('/logout'), + + checkAuth: () => + apiClient.get('/health'), +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dd662017ceb2106c2855f4a20f08971720e9fb6 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,57 @@ +import axios, { type AxiosInstance, type AxiosError } from 'axios' +import { useAuthStore } from '@/stores/auth' +import router from '@/router' + +// 创建 axios 实例 +export const apiClient: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_URL || '', + timeout: 30000, + withCredentials: true, // 支持 cookie 认证 +}) + +// 请求拦截器 +apiClient.interceptors.request.use( + (config) => { + // 可以在这里添加 token 等认证信息 + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 防止多个 401 响应触发重复跳转 +let isRedirecting = false + +// 响应拦截器 +apiClient.interceptors.response.use( + (response) => { + return response.data + }, + async (error: AxiosError) => { + // 统一错误处理 + if (error.response?.status === 401 && !isRedirecting) { + isRedirecting = true + const authStore = useAuthStore() + authStore.isLoggedIn = false + await router.push('/login') + isRedirecting = false + } + + const errorMessage = error.response?.data + ? (error.response.data as any).detail || (error.response.data as any).message + : error.message + + // 保留 HTTP 状态码,便于调用方做精细化处理(例如任务不存在时的 404) + const wrapped = new Error(errorMessage || '请求失败') as Error & { + status?: number + data?: unknown + } + wrapped.status = error.response?.status + wrapped.data = error.response?.data + + return Promise.reject(wrapped) + } +) + +export default apiClient diff --git a/frontend/src/api/gallery.ts b/frontend/src/api/gallery.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb5f764d9e050514a975d066209e9020cda3421c --- /dev/null +++ b/frontend/src/api/gallery.ts @@ -0,0 +1,33 @@ +import apiClient from './client' + +export interface GalleryFile { + filename: string + url: string + size: number + created_at: string + mtime: number + type: 'image' | 'video' + expired: boolean + expires_in_seconds: number | null +} + +export interface GalleryResponse { + files: GalleryFile[] + total: number + total_size: number + expire_hours: number +} + +export const galleryApi = { + // 获取画廊文件列表 + getFiles: () => + apiClient.get('/admin/gallery'), + + // 删除单个文件 + deleteFile: (filename: string) => + apiClient.delete(`/admin/gallery/${filename}`), + + // 立即清理过期文件 + cleanupExpired: () => + apiClient.post('/admin/gallery/cleanup'), +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fef3e55ad47aa3efa24e0cdf4f66dce557e8109f --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,11 @@ +// 统一导出所有 API +export { authApi } from './auth' +export { accountsApi } from './accounts' +export { settingsApi } from './settings' +export { logsApi } from './logs' +export { monitorApi } from './monitor' +export { statsApi } from './stats' +export { publicLogsApi } from './publicLogs' +export { publicStatsApi } from './publicStats' +export { publicDisplayApi } from './publicDisplay' +export { apiClient } from './client' diff --git a/frontend/src/api/logs.ts b/frontend/src/api/logs.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ab66029215351b22695d7fcfe1ccada50588008 --- /dev/null +++ b/frontend/src/api/logs.ts @@ -0,0 +1,12 @@ +import apiClient from './client' +import type { AdminLogsResponse } from '@/types/api' + +export const logsApi = { + // 获取日志 + list: (params?: { limit?: number; level?: string; search?: string }) => + apiClient.get('/admin/log', { params }), + + // 清空日志 + clear: () => + apiClient.delete('/admin/log?confirm=yes'), +} diff --git a/frontend/src/api/monitor.ts b/frontend/src/api/monitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..65aa3d271ada491fb2b2fd4a3a34bb609660583b --- /dev/null +++ b/frontend/src/api/monitor.ts @@ -0,0 +1,8 @@ +import apiClient from './client' +import type { UptimeResponse } from '@/types/api' + +export const monitorApi = { + uptime(days = 90) { + return apiClient.get('/public/uptime', { params: { days } }) + }, +} diff --git a/frontend/src/api/publicDisplay.ts b/frontend/src/api/publicDisplay.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ca5690348cdcb09655c03949339620daefadd7f --- /dev/null +++ b/frontend/src/api/publicDisplay.ts @@ -0,0 +1,8 @@ +import apiClient from './client' +import type { PublicDisplay } from '@/types/api' + +export const publicDisplayApi = { + overview() { + return apiClient.get('/public/display') + }, +} diff --git a/frontend/src/api/publicLogs.ts b/frontend/src/api/publicLogs.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a60c6d91ecf13ec040510391365a3c6382a5a40 --- /dev/null +++ b/frontend/src/api/publicLogs.ts @@ -0,0 +1,7 @@ +import apiClient from './client' +import type { PublicLogsResponse } from '@/types/api' + +export const publicLogsApi = { + list: (params?: { limit?: number }) => + apiClient.get('/public/log', { params }), +} diff --git a/frontend/src/api/publicStats.ts b/frontend/src/api/publicStats.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd10aca43862c56f59b6aa3145eca1fde11de36f --- /dev/null +++ b/frontend/src/api/publicStats.ts @@ -0,0 +1,8 @@ +import apiClient from './client' +import type { PublicStats } from '@/types/api' + +export const publicStatsApi = { + overview() { + return apiClient.get('/public/stats') + }, +} diff --git a/frontend/src/api/settings.ts b/frontend/src/api/settings.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7e45035c7f7f6668c5ffb0d2185fef215ef7e6a --- /dev/null +++ b/frontend/src/api/settings.ts @@ -0,0 +1,12 @@ +import apiClient from './client' +import type { Settings } from '@/types/api' + +export const settingsApi = { + // 获取设置 + get: () => + apiClient.get('/admin/settings'), + + // 更新设置 + update: (settings: Settings) => + apiClient.put('/admin/settings', settings), +} diff --git a/frontend/src/api/stats.ts b/frontend/src/api/stats.ts new file mode 100644 index 0000000000000000000000000000000000000000..7790a3d1d1a92ff991a11313137b1ff9d14f0263 --- /dev/null +++ b/frontend/src/api/stats.ts @@ -0,0 +1,10 @@ +import apiClient from './client' +import type { AdminStats } from '@/types/api' + +export const statsApi = { + overview(timeRange: string = '24h') { + return apiClient.get('/admin/stats', { + params: { time_range: timeRange } + }) + }, +} diff --git a/frontend/src/components/QuotaBadge.vue b/frontend/src/components/QuotaBadge.vue new file mode 100644 index 0000000000000000000000000000000000000000..22927caa07db4abdc983ddeffc1f2e3e72d3c82f --- /dev/null +++ b/frontend/src/components/QuotaBadge.vue @@ -0,0 +1,255 @@ + + + diff --git a/frontend/src/components/ui/Checkbox.vue b/frontend/src/components/ui/Checkbox.vue new file mode 100644 index 0000000000000000000000000000000000000000..36fd03e14527ea2349c233803f0ba3126a3a2834 --- /dev/null +++ b/frontend/src/components/ui/Checkbox.vue @@ -0,0 +1,43 @@ + + + diff --git a/frontend/src/components/ui/ConfirmDialog.vue b/frontend/src/components/ui/ConfirmDialog.vue new file mode 100644 index 0000000000000000000000000000000000000000..a90664a6bf0e6801785dd426317204c1ce468795 --- /dev/null +++ b/frontend/src/components/ui/ConfirmDialog.vue @@ -0,0 +1,47 @@ + + + diff --git a/frontend/src/components/ui/HelpTip.vue b/frontend/src/components/ui/HelpTip.vue new file mode 100644 index 0000000000000000000000000000000000000000..a5abf8aac97e7d5b2611fadc26c0273905cf0333 --- /dev/null +++ b/frontend/src/components/ui/HelpTip.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontend/src/components/ui/SelectMenu.vue b/frontend/src/components/ui/SelectMenu.vue new file mode 100644 index 0000000000000000000000000000000000000000..2677539d15b46b56af1ad6db46d8628a04dd665f --- /dev/null +++ b/frontend/src/components/ui/SelectMenu.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/components/ui/Toast.vue b/frontend/src/components/ui/Toast.vue new file mode 100644 index 0000000000000000000000000000000000000000..7176ae36f070562cac9877261b3f675552025a2b --- /dev/null +++ b/frontend/src/components/ui/Toast.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/frontend/src/components/ui/Tooltip.vue b/frontend/src/components/ui/Tooltip.vue new file mode 100644 index 0000000000000000000000000000000000000000..4c68d0d0e0fb178f3cd8a179126bd9b75bc2fa83 --- /dev/null +++ b/frontend/src/components/ui/Tooltip.vue @@ -0,0 +1,56 @@ + + + diff --git a/frontend/src/composables/useConfirmDialog.ts b/frontend/src/composables/useConfirmDialog.ts new file mode 100644 index 0000000000000000000000000000000000000000..71029083f28e6572106fe489f0990c1056c5af40 --- /dev/null +++ b/frontend/src/composables/useConfirmDialog.ts @@ -0,0 +1,51 @@ +import { ref } from 'vue' + +type ConfirmOptions = { + title?: string + message: string + confirmText?: string + cancelText?: string +} + +// Global singleton state: all callers share the same dialog instance. +const open = ref(false) +const title = ref('确认操作') +const message = ref('') +const confirmText = ref('确定') +const cancelText = ref('取消') +let resolver: ((value: boolean) => void) | null = null + +export function useConfirmDialog() { + const ask = (options: ConfirmOptions) => + new Promise((resolve) => { + title.value = options.title || '确认操作' + message.value = options.message + confirmText.value = options.confirmText || '确定' + cancelText.value = options.cancelText || '取消' + open.value = true + resolver = resolve + }) + + const confirm = () => { + open.value = false + resolver?.(true) + resolver = null + } + + const cancel = () => { + open.value = false + resolver?.(false) + resolver = null + } + + return { + open, + title, + message, + confirmText, + cancelText, + ask, + confirm, + cancel, + } +} diff --git a/frontend/src/composables/useToast.ts b/frontend/src/composables/useToast.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a96c91894f681cfcd43ec3903b2d392a2f2fcbf --- /dev/null +++ b/frontend/src/composables/useToast.ts @@ -0,0 +1,58 @@ +import { reactive } from 'vue' + +export interface Toast { + id: string + type: 'success' | 'error' | 'warning' | 'info' + title?: string + message: string + duration?: number +} + +export const toastState = reactive<{ toasts: Toast[] }>({ + toasts: [], +}) + +let toastId = 0 + +export const showToast = (options: Omit) => { + const id = `toast-${++toastId}` + const duration = options.duration ?? 3000 + + const toast: Toast = { + id, + type: options.type, + title: options.title, + message: options.message, + duration, + } + + toastState.toasts.push(toast) + + if (duration > 0) { + setTimeout(() => { + removeToast(id) + }, duration) + } + + return id +} + +export const removeToast = (id: string) => { + const index = toastState.toasts.findIndex((t) => t.id === id) + if (index > -1) { + toastState.toasts.splice(index, 1) + } +} + +export const useToast = () => { + return { + success: (message: string, title?: string, duration?: number) => + showToast({ type: 'success', message, title, duration }), + error: (message: string, title?: string, duration?: number) => + showToast({ type: 'error', message, title, duration }), + warning: (message: string, title?: string, duration?: number) => + showToast({ type: 'warning', message, title, duration }), + info: (message: string, title?: string, duration?: number) => + showToast({ type: 'info', message, title, duration }), + } +} diff --git a/frontend/src/composables/useUptimeStatus.ts b/frontend/src/composables/useUptimeStatus.ts new file mode 100644 index 0000000000000000000000000000000000000000..80f9449c823ec100b4c03a6c85d0a663da7ef1d4 --- /dev/null +++ b/frontend/src/composables/useUptimeStatus.ts @@ -0,0 +1,104 @@ +import { computed, ref } from 'vue' +import { monitorApi } from '@/api' +import type { UptimeHeartbeat, UptimeResponse, UptimeService } from '@/types/api' + +type ServiceView = { + key: string + name: string + statusLabel: string + statusClass: string + uptime: number + total: number + success: number + beats: Array<{ className: string; tooltip: string | null }> +} + +const slowThresholdMs = 40000 +const maxBeats = 60 + +const mapStatusLabel = (statusValue: UptimeService['status']) => { + if (statusValue === 'up') return '正常' + if (statusValue === 'warn') return '注意' + if (statusValue === 'down') return '异常' + return '未知' +} + +const mapStatusClass = (statusValue: UptimeService['status']) => { + if (statusValue === 'up') return 'monitor-badge--up' + if (statusValue === 'warn') return 'monitor-badge--warn' + if (statusValue === 'down') return 'monitor-badge--down' + return 'monitor-badge--unknown' +} + +const buildBeats = (heartbeats: UptimeHeartbeat[] = []) => { + const beats: Array<{ className: string; tooltip: string | null }> = [] + for (let i = 0; i < maxBeats; i += 1) { + if (i < heartbeats.length) { + const beat = heartbeats[i] + const latencyMs = beat.latency_ms ?? null + const isSlow = beat.success && latencyMs !== null && latencyMs > slowThresholdMs + const level = beat.level ?? (isSlow ? 'warn' : (beat.success ? 'up' : 'down')) + const className = level === 'warn' + ? 'monitor-beat--warn' + : (level === 'up' ? 'monitor-beat--up' : 'monitor-beat--down') + const latencyText = latencyMs !== null + ? ` · 首响 ${(Math.max(latencyMs, 0) / 1000).toFixed(1)}s` + : '' + const statusCodeText = beat.status_code ? ` · HTTP ${beat.status_code}` : '' + const statusText = level === 'warn' ? '警告' : (beat.success ? '成功' : '失败') + + beats.push({ + className, + tooltip: `${beat.time} · ${statusText}${statusCodeText}${latencyText}`, + }) + } else { + beats.push({ className: 'monitor-beat--empty', tooltip: null }) + } + } + return beats +} + +export function useUptimeStatus() { + const status = ref(null) + const errorMessage = ref('') + const isLoading = ref(false) + + const updatedAt = computed(() => status.value?.updated_at ?? '') + + const services = computed(() => { + if (!status.value) return [] + + return Object.entries(status.value.services).map(([key, service]) => ({ + key, + name: service.name, + statusLabel: mapStatusLabel(service.status), + statusClass: mapStatusClass(service.status), + uptime: service.uptime, + total: service.total, + success: service.success, + beats: buildBeats(service.heartbeats), + })) + }) + + const refreshStatus = async () => { + if (isLoading.value) return + isLoading.value = true + errorMessage.value = '' + + try { + status.value = await monitorApi.uptime() + } catch (error) { + errorMessage.value = (error as Error).message || '监控数据获取失败' + } finally { + isLoading.value = false + } + } + + return { + services, + updatedAt, + errorMessage, + isLoading, + refreshStatus, + } +} diff --git a/frontend/src/constants/mailProviders.ts b/frontend/src/constants/mailProviders.ts new file mode 100644 index 0000000000000000000000000000000000000000..93aaff61118f04323a7cd5e68ec47d1ab505ba93 --- /dev/null +++ b/frontend/src/constants/mailProviders.ts @@ -0,0 +1,11 @@ +export const mailProviderOptions = [ + { label: 'DuckMail', value: 'duckmail' }, + { label: 'Moemail', value: 'moemail' }, + { label: 'Freemail', value: 'freemail' }, + { label: 'GPTMail', value: 'gptmail' }, + { label: 'Cloudflare Mail', value: 'cfmail' }, +] as const + +export type TempMailProvider = typeof mailProviderOptions[number]['value'] + +export const defaultMailProvider: TempMailProvider = 'duckmail' diff --git a/frontend/src/counter.ts b/frontend/src/counter.ts new file mode 100644 index 0000000000000000000000000000000000000000..09e5afd2d8ad2b43f4c8073eef5ad57688caef7b --- /dev/null +++ b/frontend/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa5ed412fcb8eccb02dd16f8f16eb9edd9207aea --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent, Record, any> + export default component +} diff --git a/frontend/src/layouts/AppShell.vue b/frontend/src/layouts/AppShell.vue new file mode 100644 index 0000000000000000000000000000000000000000..8a4c511e6d37df15a3ed7efc1dadbd5971642e45 --- /dev/null +++ b/frontend/src/layouts/AppShell.vue @@ -0,0 +1,469 @@ + + + diff --git a/frontend/src/lib/chartTheme.ts b/frontend/src/lib/chartTheme.ts new file mode 100644 index 0000000000000000000000000000000000000000..e962f4614b66451ed1ee9cc310bc26cba107d588 --- /dev/null +++ b/frontend/src/lib/chartTheme.ts @@ -0,0 +1,301 @@ +/** + * ECharts 统一主题配置 + * 基于项目的设计系统,提供一致的图表样式 + */ + +// 主题色板 +export const chartColors = { + primary: '#0ea5e9', + success: '#10b981', + warning: '#f59e0b', + danger: '#ef4444', + info: '#3b82f6', + purple: '#a855f7', + pink: '#ec4899', + slate: '#64748b', + gray: '#94a3b8', + lightGreen: '#4ade80', + cyan: '#22d3ee', + emerald: '#34d399', +} + +// 模型专用色板 +export const modelColors: Record = { + 'gemini-3-pro-preview': chartColors.primary, + 'gemini-3.1-pro-preview': chartColors.primary, + 'gemini-2.5-pro': chartColors.cyan, + 'gemini-2.5-flash': chartColors.warning, + 'gemini-3-flash-preview': chartColors.pink, + 'gemini-imagen': chartColors.emerald, + 'gemini-veo': chartColors.success, + 'gemini-auto': chartColors.slate, +} + +// 有效模型列表 +export const validModels = [ + 'gemini-auto', + 'gemini-2.5-flash', + 'gemini-2.5-pro', + 'gemini-3-flash-preview', + 'gemini-3-pro-preview', + 'gemini-3.1-pro-preview', + 'gemini-imagen', + 'gemini-veo', +] + +// 获取模型颜色(带回退) +export function getModelColor(model: string): string { + return modelColors[model] || chartColors.gray +} + +// 过滤有效模型 +export function filterValidModels(modelRequests: Record): Record { + const filtered: Record = {} + validModels.forEach(model => { + if (modelRequests[model]) { + filtered[model] = modelRequests[model] + } + }) + return filtered +} + +// 文本样式 +const textStyle = { + fontFamily: 'Noto Sans SC, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif', + color: '#6b6b6b', // text-muted-foreground + fontSize: 11, +} + +// 网格配置 +const gridConfig = { + left: 24, + right: 16, + top: 44, + bottom: 24, + containLabel: true, +} + +// 工具提示配置 +const tooltipConfig = { + backgroundColor: 'rgba(255, 255, 255, 0.95)', + borderColor: '#e5e5e5', + borderWidth: 1, + textStyle: { + color: '#1a1a1a', + fontSize: 12, + }, + padding: [8, 12], + extraCssText: 'border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);', +} + +// 图例配置 +const legendConfig = { + textStyle: { + ...textStyle, + fontSize: 11, + }, + itemWidth: 14, + itemHeight: 14, + itemGap: 16, +} + +/** + * 折线图主题配置 + */ +export function getLineChartTheme() { + return { + tooltip: { + ...tooltipConfig, + trigger: 'axis', + axisPointer: { + type: 'line', + lineStyle: { + color: '#d4d4d4', + type: 'dashed', + }, + }, + }, + legend: { + ...legendConfig, + right: 0, + top: 0, + }, + grid: gridConfig, + xAxis: { + type: 'category', + boundaryGap: false, + axisLine: { + lineStyle: { + color: '#d4d4d4', + }, + }, + axisTick: { + show: false, + }, + axisLabel: { + ...textStyle, + fontSize: 10, + }, + }, + yAxis: { + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + ...textStyle, + fontSize: 10, + }, + splitLine: { + lineStyle: { + color: '#e5e5e5', + type: 'solid', + }, + }, + }, + } +} + +/** + * 饼图主题配置 + */ +export function getPieChartTheme(isMobile = false) { + const legendPosition = isMobile + ? { + left: 'center', + bottom: 0, + orient: 'horizontal' as const, + } + : { + left: 0, + top: 'middle', + orient: 'vertical' as const, + } + + const pieCenter = isMobile ? ['50%', '42%'] : ['60%', '50%'] + const pieRadius = isMobile ? ['35%', '55%'] : ['45%', '70%'] + + return { + animation: true, + animationDuration: 600, + animationEasing: 'cubicOut', + animationDurationUpdate: 300, + animationEasingUpdate: 'cubicOut', + tooltip: { + ...tooltipConfig, + trigger: 'item', + }, + legend: { + ...legendConfig, + ...legendPosition, + type: isMobile ? 'scroll' : 'plain', + pageIconSize: 10, + }, + series: { + type: 'pie', + radius: pieRadius, + center: pieCenter, + startAngle: 90, + animationType: 'scale', + animationEasing: 'cubicOut', + avoidLabelOverlap: true, + label: { + show: true, + fontSize: 11, + color: '#6b6b6b', + }, + labelLine: { + show: true, + length: 12, + length2: 10, + lineStyle: { + color: '#d4d4d4', + }, + }, + itemStyle: { + borderWidth: 2, + borderColor: '#fff', + borderRadius: 8, + }, + emphasis: { + label: { + show: true, + fontSize: 13, + fontWeight: 'bold', + }, + }, + }, + } +} + +/** + * 创建折线图系列配置 + */ +export function createLineSeries( + name: string, + data: number[], + color: string, + options?: { + smooth?: boolean + showSymbol?: boolean + areaOpacity?: number + lineWidth?: number + zIndex?: number + lineStyle?: { + type?: 'solid' | 'dashed' | 'dotted' + width?: number + } + } +) { + const { + smooth = true, + showSymbol = false, + areaOpacity = 0.25, + lineWidth = 2, + zIndex = 1, + lineStyle, + } = options || {} + + return { + name, + type: 'line', + data, + smooth, + showSymbol, + lineStyle: { + width: lineStyle?.width ?? lineWidth, + ...(lineStyle?.type && { type: lineStyle.type }), + }, + areaStyle: { + opacity: areaOpacity, + }, + itemStyle: { + color, + }, + emphasis: { + disabled: true, + }, + z: zIndex, + } +} + +/** + * 创建饼图数据项配置 + */ +export function createPieDataItem( + name: string, + value: number, + color: string +) { + return { + name, + value, + itemStyle: { + color, + borderRadius: 8, + }, + } +} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..d32b0fe652e3a7129dd9a4a2fb82bfbb6faad449 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7d2c2448e722ec77f373a0f79797f702ec3e9bf --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import router from './router' +import App from './App.vue' +import './style.css' +import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' + +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia) +app.use(router) + +app.mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..29601827cfab5a534a1589a0388e512aaff879e3 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,88 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import { useAuthStore } from '@/stores/auth' + +const router = createRouter({ + history: createWebHashHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/public/uptime', + name: 'public-uptime', + component: () => import('@/views/PublicUptime.vue'), + meta: { requiresAuth: false }, + }, + { + path: '/public/logs', + name: 'public-logs', + component: () => import('@/views/PublicLogs.vue'), + meta: { requiresAuth: false }, + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/Login.vue'), + meta: { requiresAuth: false }, + }, + { + path: '/', + component: () => import('@/layouts/AppShell.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'dashboard', + component: () => import('@/views/Dashboard.vue'), + }, + { + path: 'accounts', + name: 'accounts', + component: () => import('@/views/Accounts.vue'), + }, + { + path: 'settings', + name: 'settings', + component: () => import('@/views/Settings.vue'), + }, + { + path: 'logs', + name: 'logs', + component: () => import('@/views/Logs.vue'), + }, + { + path: 'monitor', + name: 'monitor', + component: () => import('@/views/Monitor.vue'), + }, + { + path: 'docs', + name: 'docs', + component: () => import('@/views/Docs.vue'), + }, + { + path: 'gallery', + name: 'gallery', + component: () => import('@/views/Gallery.vue'), + }, + ], + }, + ], +}) + +// 路由守卫 +router.beforeEach(async (to) => { + const authStore = useAuthStore() + + // 需要认证的路由 + if (to.meta.requiresAuth) { + const isAuthenticated = await authStore.checkAuth() + if (!isAuthenticated) { + return { name: 'login' } + } + } + + // 已登录用户访问登录页,重定向到首页 + if (to.name === 'login' && authStore.isLoggedIn) { + return { name: 'dashboard' } + } +}) + +export default router diff --git a/frontend/src/stores/accounts.ts b/frontend/src/stores/accounts.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b2061a5692054cd8afe79d88e984d79d1956d18 --- /dev/null +++ b/frontend/src/stores/accounts.ts @@ -0,0 +1,188 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { accountsApi } from '@/api' +import type { AdminAccount, AccountConfigItem } from '@/types/api' + +type AccountOpResult = { + ok: boolean + errors: string[] +} + +type RunOpOptions = { + accountIds?: string[] + lockKeys?: string[] + chunkSize?: number + request: (chunk: string[]) => Promise<{ errors?: string[] } | void> + refreshAfter?: boolean +} + +export const useAccountsStore = defineStore('accounts', () => { + const accounts = ref([]) + const isLoading = ref(false) + const operatingAccounts = ref>(new Set()) + const batchProgress = ref<{ current: number; total: number } | null>(null) + + const isOperating = computed(() => operatingAccounts.value.size > 0) + + const LOCK_TIMEOUT_MS = 60000 + + async function loadAccounts() { + isLoading.value = true + try { + const response = await accountsApi.list() + if (Array.isArray(response)) { + accounts.value = response + } else { + accounts.value = response.accounts || [] + } + } finally { + isLoading.value = false + } + } + + const addLocks = (locks: string[]) => { + locks.forEach(lock => operatingAccounts.value.add(lock)) + } + + const releaseLocks = (locks: string[]) => { + locks.forEach(lock => operatingAccounts.value.delete(lock)) + } + + const buildChunks = (ids: string[], chunkSize: number) => { + const chunks: string[][] = [] + for (let i = 0; i < ids.length; i += chunkSize) { + chunks.push(ids.slice(i, i + chunkSize)) + } + return chunks + } + + const startTimeoutGuard = (locks: string[]) => { + if (!locks.length) return null + return window.setTimeout(() => { + releaseLocks(locks) + batchProgress.value = null + }, LOCK_TIMEOUT_MS) + } + + async function runAccountOp(options: RunOpOptions): Promise { + const accountIds = options.accountIds ?? [] + const lockKeys = options.lockKeys ?? accountIds + const chunkSize = options.chunkSize ?? 10 + const refreshAfter = options.refreshAfter ?? true + + if (!lockKeys.length && !accountIds.length) { + return { ok: true, errors: [] } + } + + const conflict = lockKeys.filter(id => operatingAccounts.value.has(id)) + if (conflict.length > 0) { + throw new Error(`${conflict.length} 个账号正在操作中`) + } + + addLocks(lockKeys) + const timeoutGuard = startTimeoutGuard(lockKeys) + + if (accountIds.length > 1) { + batchProgress.value = { current: 0, total: accountIds.length } + } + + const errors: string[] = [] + try { + const chunks = accountIds.length ? buildChunks(accountIds, chunkSize) : [[]] + for (const chunk of chunks) { + const response = await options.request(chunk) + if (response && Array.isArray(response.errors) && response.errors.length) { + errors.push(...response.errors) + } + if (batchProgress.value) { + batchProgress.value.current += chunk.length + } + } + if (refreshAfter) { + await loadAccounts() + } + return { ok: errors.length === 0, errors } + } finally { + if (timeoutGuard !== null) { + window.clearTimeout(timeoutGuard) + } + releaseLocks(lockKeys) + batchProgress.value = null + } + } + + async function deleteAccount(accountId: string) { + return runAccountOp({ + accountIds: [accountId], + request: async (chunk) => { + await accountsApi.delete(chunk[0]) + }, + }) + } + + async function disableAccount(accountId: string) { + return runAccountOp({ + accountIds: [accountId], + request: async (chunk) => { + await accountsApi.disable(chunk[0]) + }, + }) + } + + async function enableAccount(accountId: string) { + return runAccountOp({ + accountIds: [accountId], + request: async (chunk) => { + await accountsApi.enable(chunk[0]) + }, + }) + } + + async function bulkEnable(accountIds: string[]) { + if (!accountIds.length) return { ok: true, errors: [] } + return runAccountOp({ + accountIds, + request: async (chunk) => accountsApi.bulkEnable(chunk), + }) + } + + async function bulkDisable(accountIds: string[]) { + if (!accountIds.length) return { ok: true, errors: [] } + return runAccountOp({ + accountIds, + request: async (chunk) => accountsApi.bulkDisable(chunk), + }) + } + + async function bulkDelete(accountIds: string[]) { + if (!accountIds.length) return { ok: true, errors: [] } + return runAccountOp({ + accountIds, + request: async (chunk) => accountsApi.bulkDelete(chunk), + }) + } + + async function updateConfig(newAccounts: AccountConfigItem[]) { + return runAccountOp({ + lockKeys: ['__config_update__'], + request: async () => { + await accountsApi.updateConfig(newAccounts) + }, + }) + } + + return { + accounts, + isLoading, + isOperating, + batchProgress, + loadAccounts, + deleteAccount, + disableAccount, + enableAccount, + bulkEnable, + bulkDisable, + bulkDelete, + updateConfig, + } +}) diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..a84685bd72c4551de5be4bad6cabe1b6a4ea6c69 --- /dev/null +++ b/frontend/src/stores/auth.ts @@ -0,0 +1,71 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { authApi } from '@/api' + +export const useAuthStore = defineStore('auth', () => { + const isLoggedIn = ref(false) + const isLoading = ref(false) + const lastCheckedAt = ref(0) + const AUTH_CACHE_MS = 10000 + let checkPromise: Promise | null = null + + // 登录 + async function login(password: string) { + isLoading.value = true + try { + await authApi.login({ password }) + await authApi.checkAuth() + isLoggedIn.value = true + lastCheckedAt.value = Date.now() + return true + } catch (error) { + isLoggedIn.value = false + throw error + } finally { + isLoading.value = false + } + } + + // 登出 + async function logout() { + try { + await authApi.logout() + } finally { + isLoggedIn.value = false + lastCheckedAt.value = 0 + } + } + + // 检查登录状态 + async function checkAuth() { + const now = Date.now() + if (isLoggedIn.value && now - lastCheckedAt.value < AUTH_CACHE_MS) { + return true + } + if (checkPromise) { + return checkPromise + } + try { + checkPromise = (async () => { + await authApi.checkAuth() + isLoggedIn.value = true + return true + })() + return await checkPromise + } catch (error) { + isLoggedIn.value = false + return false + } finally { + lastCheckedAt.value = Date.now() + checkPromise = null + } + } + + return { + isLoggedIn, + isLoading, + login, + logout, + checkAuth, + } +}) diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1750832aad16d6aae9af7826827e86feeb32be9b --- /dev/null +++ b/frontend/src/stores/index.ts @@ -0,0 +1,3 @@ +export { useAuthStore } from './auth' +export { useAccountsStore } from './accounts' +export { useSettingsStore } from './settings' diff --git a/frontend/src/stores/settings.ts b/frontend/src/stores/settings.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e704cb5628196321d920e302b8e49c0d2dfb7b8 --- /dev/null +++ b/frontend/src/stores/settings.ts @@ -0,0 +1,32 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { settingsApi } from '@/api' +import type { Settings } from '@/types/api' + +export const useSettingsStore = defineStore('settings', () => { + const settings = ref(null) + const isLoading = ref(false) + + // 加载设置 + async function loadSettings() { + isLoading.value = true + try { + settings.value = await settingsApi.get() + } finally { + isLoading.value = false + } + } + + // 更新设置 + async function updateSettings(newSettings: Settings) { + await settingsApi.update(newSettings) + settings.value = newSettings + } + + return { + settings, + isLoading, + loadSettings, + updateSettings, + } +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000000000000000000000000000000000000..d90c1c30f979619c4579145602a82cdde09f463f --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,342 @@ +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Serif+SC:wght@600;700&display=swap"); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 97%; + --foreground: 0 0% 10%; + + --card: 0 0% 100%; + --card-foreground: 0 0% 10%; + + --primary: 0 0% 10%; + --primary-foreground: 0 0% 100%; + + --secondary: 0 0% 92%; + --secondary-foreground: 0 0% 10%; + + --muted: 0 0% 92%; + --muted-foreground: 0 0% 40%; + + --accent: 0 0% 90%; + --accent-foreground: 0 0% 10%; + + --destructive: 0 70% 45%; + --destructive-foreground: 0 0% 100%; + + --border: 0 0% 86%; + --input: 0 0% 86%; + --ring: 0 0% 10%; + + --radius: 1rem; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-family: "Noto Sans SC", "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + margin: 0; + padding: 0; + overflow-x: hidden; + background-image: + radial-gradient(900px circle at 10% -20%, rgba(0, 0, 0, 0.04), transparent 45%), + radial-gradient(700px circle at 90% 0%, rgba(0, 0, 0, 0.05), transparent 40%), + linear-gradient(180deg, hsl(var(--background)) 0%, hsl(var(--background)) 100%); + } + + body::before { + content: ""; + position: fixed; + inset: 0; + background-image: linear-gradient(120deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0)); + opacity: 0.5; + pointer-events: none; + z-index: -1; + } + + h1, + h2, + h3 { + font-family: "Noto Serif SC", "Georgia", serif; + letter-spacing: -0.01em; + } + + #app { + min-height: 100vh; + } +} + +html { + scrollbar-gutter: stable; +} + +* { + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.35) transparent; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.35); + border-radius: 999px; + border: 2px solid transparent; + background-clip: content-box; +} + +.scrollbar-slim::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.scrollbar-slim::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.4); +} + +.cv-auto { + content-visibility: auto; + contain-intrinsic-size: 0 44px; +} + +.monitor-badge--up { + background: #d1fae5; + color: #065f46; +} + +.monitor-badge--warn { + background: #fef3c7; + color: #b45309; +} + +.monitor-badge--down { + background: #fee2e2; + color: #991b1b; +} + +.monitor-badge--unknown { + background: #f3f4f6; + color: #6b7280; +} + +.monitor-card { + border-radius: 16px; + padding: 0; + background: hsl(var(--card)); + box-shadow: none; +} + +.monitor-card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.monitor-card__name { + font-size: 14px; + font-weight: 600; + color: hsl(var(--foreground)); +} + +.monitor-card__badge { + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; +} + +.monitor-card__stats { + display: flex; + flex-wrap: wrap; + gap: 16px; + font-size: 12px; + color: hsl(var(--muted-foreground)); + margin-bottom: 12px; +} + +.monitor-card__value { + margin-left: 4px; + color: hsl(var(--foreground)); + font-weight: 600; +} + +.monitor-card__beats { + display: flex; + gap: 2px; + height: 24px; + align-items: flex-end; +} + +.monitor-beat { + flex: 1; + min-width: 4px; + max-width: 8px; + border-radius: 2px; + transition: all 0.2s; + position: relative; +} + +.monitor-beat:hover { + opacity: 0.8; + transform: scaleY(1.1); +} + +.monitor-beat--up { + background: #34c759; + height: 100%; +} + +.monitor-beat--warn { + background: #f5c15b; + height: 100%; +} + +.monitor-beat--slow { + background: #f5c15b; + height: 100%; +} + +.monitor-beat--down { + background: #ff3b30; + height: 100%; +} + +.monitor-beat--empty { + background: #e5e5ea; + height: 40%; +} + +.monitor-beat__tooltip { + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background: #1d1d1f; + color: #fff; + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s; + margin-bottom: 6px; + z-index: 10; +} + +.monitor-beat__tooltip::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: #1d1d1f; +} + +.monitor-beat:hover .monitor-beat__tooltip { + opacity: 1; +} + +.help-dot { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 999px; + border: 1px solid hsl(var(--border)); + font-size: 10px; + line-height: 1; + color: hsl(var(--muted-foreground)); + cursor: default; +} + +@media (max-width: 768px) { + .monitor-beat { + min-width: 3px; + max-width: 6px; + } +} + +@media (min-width: 1024px) { + .dashboard-split { + flex-direction: row; + flex-wrap: nowrap; + } + + .dashboard-split .dashboard-main { + flex: 0 0 66.6667%; + max-width: 66.6667%; + } + + .dashboard-split .dashboard-side { + flex: 0 0 33.3333%; + max-width: 33.3333%; + } +} + +@keyframes logo-float { + 0% { + transform: translateY(0); + } + 25% { + transform: translateY(6px); + } + 50% { + transform: translateY(0); + } + 75% { + transform: translateY(-6px); + } + 100% { + transform: translateY(0); + } +} + +@keyframes logo-blink { + 0%, + 45%, + 55%, + 100% { + transform: scaleY(1); + opacity: 1; + } + 50% { + transform: scaleY(0.1); + opacity: 0.6; + } +} + +.logo-mark .logo-cat-wrapper { + animation: logo-float 4s ease-in-out infinite; + transform-origin: center; +} + +.logo-mark { + transform: translateY(2px); +} + +.logo-mark .logo-eye { + fill: #00bcd4; + filter: drop-shadow(0 0 2px #00bcd4); + animation: logo-blink 5s infinite; + transform-box: fill-box; + transform-origin: center; +} diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts new file mode 100644 index 0000000000000000000000000000000000000000..448b92ab456303f59b6ad46d3a4b8a8835c7aed8 --- /dev/null +++ b/frontend/src/types/api.ts @@ -0,0 +1,306 @@ +// API 类型定义 + +export interface QuotaStatus { + available: boolean + remaining_seconds?: number + reason?: string // 受限原因(如"对话配额受限") + daily_used?: number + daily_limit?: number +} + +export interface AccountQuotaStatus { + quotas: { + text: QuotaStatus + images: QuotaStatus + videos: QuotaStatus + } + limited_count: number + total_count: number + is_expired: boolean +} + +export interface AdminAccount { + id: string + status: string + expires_at: string + remaining_hours: number | null + remaining_display: string + is_available: boolean + error_count: number + failure_count: number + disabled: boolean + disabled_reason: string | null + cooldown_seconds: number + cooldown_reason: string | null + conversation_count: number + quota_status: AccountQuotaStatus + trial_end?: string | null + trial_days_remaining?: number | null +} + +export interface AccountsListResponse { + total: number + accounts: AdminAccount[] +} + +export interface AccountConfigItem { + id: string + secure_c_ses: string + csesidx: string + config_id: string + host_c_oses?: string + expires_at?: string + mail_provider?: string + mail_address?: string + mail_password?: string + mail_client_id?: string + mail_refresh_token?: string + mail_tenant?: string + mail_base_url?: string + mail_api_key?: string + mail_jwt_token?: string + mail_verify_ssl?: boolean + mail_domain?: string +} + +export interface AccountsConfigResponse { + accounts: AccountConfigItem[] +} + +export interface Stats { + total_accounts: number + active_accounts: number + failed_accounts: number + rate_limited_accounts: number + expired_accounts: number + total_requests: number + total_visitors: number + requests_per_hour: number +} + +export type TempMailProvider = 'duckmail' | 'moemail' | 'freemail' | 'gptmail' | 'cfmail' + +export interface Settings { + basic: { + api_key?: string + base_url?: string + proxy_for_auth?: string + proxy_for_chat?: string + duckmail_base_url?: string + duckmail_api_key?: string + duckmail_verify_ssl?: boolean + temp_mail_provider?: TempMailProvider + moemail_base_url?: string + moemail_api_key?: string + moemail_domain?: string + freemail_base_url?: string + freemail_jwt_token?: string + freemail_verify_ssl?: boolean + freemail_domain?: string + mail_proxy_enabled?: boolean + gptmail_base_url?: string + gptmail_api_key?: string + gptmail_verify_ssl?: boolean + gptmail_domain?: string + cfmail_base_url?: string + cfmail_api_key?: string + cfmail_verify_ssl?: boolean + cfmail_domain?: string + browser_engine?: string + browser_headless?: boolean + refresh_window_hours?: number + register_default_count?: number + register_domain?: string + image_expire_hours?: number + } + retry: { + max_account_switch_tries: number + account_failure_threshold: number + text_rate_limit_cooldown_seconds: number + images_rate_limit_cooldown_seconds: number + videos_rate_limit_cooldown_seconds: number + session_cache_ttl_seconds: number + auto_refresh_accounts_seconds: number + scheduled_refresh_enabled?: boolean + scheduled_refresh_interval_minutes?: number + scheduled_refresh_cron?: string + refresh_batch_size?: number + refresh_batch_interval_minutes?: number + refresh_cooldown_hours?: number + } + public_display: { + logo_url?: string + chat_url?: string + } + image_generation: { + enabled: boolean + supported_models: string[] + output_format?: 'base64' | 'url' + } + session: { + expire_hours: number + } + quota_limits: { + enabled: boolean + text_daily_limit: number + images_daily_limit: number + videos_daily_limit: number + } +} + +export interface LogEntry { + time: string + level: 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' | 'DEBUG' + message: string +} + +export interface LogsResponse { + total: number + limit: number + logs: LogEntry[] +} + +export interface AdminLogStats { + memory: { + total: number + by_level: Record + capacity: number + } + errors: { + count: number + recent: LogEntry[] + } + chat_count: number +} + +export interface AdminLogsResponse extends LogsResponse { + filters?: { + level?: string | null + search?: string | null + start_time?: string | null + end_time?: string | null + } + stats: AdminLogStats +} + +export type PublicLogStatus = 'success' | 'error' | 'timeout' | 'in_progress' + +export interface PublicLogEvent { + time: string + type: 'start' | 'select' | 'retry' | 'switch' | 'complete' + status?: 'success' | 'error' | 'timeout' + content: string +} + +export interface PublicLogGroup { + request_id: string + start_time: string + status: PublicLogStatus + events: PublicLogEvent[] +} + +export interface PublicLogsResponse { + total: number + logs: PublicLogGroup[] + error?: string +} + +export interface AdminStatsTrend { + labels: string[] + total_requests: number[] + failed_requests: number[] + rate_limited_requests: number[] + model_requests?: Record + model_ttfb_times?: Record + model_total_times?: Record +} + +export interface AdminStats { + total_accounts: number + active_accounts: number + failed_accounts: number + rate_limited_accounts: number + idle_accounts: number + success_count?: number + failed_count?: number + trend: AdminStatsTrend +} + +export interface PublicStats { + total_visitors: number + total_requests: number + requests_per_minute: number + load_status: 'low' | 'medium' | 'high' + load_color: string +} + +export interface PublicDisplay { + logo_url?: string + chat_url?: string +} + +export interface UptimeHeartbeat { + time: string + success: boolean + latency_ms?: number | null + status_code?: number | null + level?: 'up' | 'down' | 'warn' +} + +export interface UptimeService { + name: string + status: 'up' | 'down' | 'warn' | 'unknown' + uptime: number + total: number + success: number + heartbeats: UptimeHeartbeat[] +} + +export interface UptimeResponse { + services: Record + updated_at: string +} + +export interface LoginRequest { + password: string +} + +export interface LoginResponse { + success: boolean + message?: string +} + +export type AutomationStatus = 'pending' | 'running' | 'success' | 'failed' | 'cancelled' + +export interface RegisterTask { + id: string + count: number + domain?: string | null + status: AutomationStatus + progress: number + success_count: number + fail_count: number + created_at: number + finished_at?: number | null + results: Array> + error?: string | null + logs?: Array<{ time: string; level: string; message: string }> + cancel_requested?: boolean + cancel_reason?: string | null +} + +export interface LoginTask { + id: string + account_ids: string[] + status: AutomationStatus + progress: number + success_count: number + fail_count: number + created_at: number + finished_at?: number | null + results: Array> + error?: string | null + logs?: Array<{ time: string; level: string; message: string }> + cancel_requested?: boolean + cancel_reason?: string | null +} diff --git a/frontend/src/typescript.svg b/frontend/src/typescript.svg new file mode 100644 index 0000000000000000000000000000000000000000..d91c910cc30bf4af39bb51adecdc71dd03d3832e --- /dev/null +++ b/frontend/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/views/Accounts.vue b/frontend/src/views/Accounts.vue new file mode 100644 index 0000000000000000000000000000000000000000..fdaa48302c66d000e86d9862e5442f984134de66 --- /dev/null +++ b/frontend/src/views/Accounts.vue @@ -0,0 +1,3042 @@ + + + diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue new file mode 100644 index 0000000000000000000000000000000000000000..d797ada6088f2f348fef6357477c20ca3634068b --- /dev/null +++ b/frontend/src/views/Dashboard.vue @@ -0,0 +1,894 @@ + + + diff --git a/frontend/src/views/Docs.vue b/frontend/src/views/Docs.vue new file mode 100644 index 0000000000000000000000000000000000000000..1c7f0718889faac2037a47ae0b9e4c707c86ca46 --- /dev/null +++ b/frontend/src/views/Docs.vue @@ -0,0 +1,305 @@ + + + diff --git a/frontend/src/views/Gallery.vue b/frontend/src/views/Gallery.vue new file mode 100644 index 0000000000000000000000000000000000000000..bafc5a4af2934b0c0b1999e4e87d208c8423e2da --- /dev/null +++ b/frontend/src/views/Gallery.vue @@ -0,0 +1,801 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 0000000000000000000000000000000000000000..a1e1d421569426f6c867f260a0c58ffc2850fa6a --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,93 @@ + + + diff --git a/frontend/src/views/Logs.vue b/frontend/src/views/Logs.vue new file mode 100644 index 0000000000000000000000000000000000000000..dd1df217ac1e2955d251b5c076684caf0e929586 --- /dev/null +++ b/frontend/src/views/Logs.vue @@ -0,0 +1,641 @@ + + + diff --git a/frontend/src/views/Monitor.vue b/frontend/src/views/Monitor.vue new file mode 100644 index 0000000000000000000000000000000000000000..286dae17fab6e665ce4f5434c2f5b2cfa07293e0 --- /dev/null +++ b/frontend/src/views/Monitor.vue @@ -0,0 +1,72 @@ + + + diff --git a/frontend/src/views/PublicLogs.vue b/frontend/src/views/PublicLogs.vue new file mode 100644 index 0000000000000000000000000000000000000000..7ca80761d6b1454926ace8b6d88d834b347dae2c --- /dev/null +++ b/frontend/src/views/PublicLogs.vue @@ -0,0 +1,312 @@ + + + diff --git a/frontend/src/views/PublicUptime.vue b/frontend/src/views/PublicUptime.vue new file mode 100644 index 0000000000000000000000000000000000000000..e808c06a612b412731178ff29b60f7b49e003724 --- /dev/null +++ b/frontend/src/views/PublicUptime.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue new file mode 100644 index 0000000000000000000000000000000000000000..b3f33280d4e329a554e1a28bf683858c6693d4b5 --- /dev/null +++ b/frontend/src/views/Settings.vue @@ -0,0 +1,592 @@ + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000000000000000000000000000000000000..1c78ca2a52b2d8c658e01fa0c7a9f9fd957cccc0 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,48 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, + }, + plugins: [], +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..80a0b320c384dee56b659982b2e0d0275dd89ff1 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + + /* Path Alias */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..47f7f8cde52bfeb8fa81638bff7ec6ada92e2251 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,31 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + build: { + outDir: path.resolve(__dirname, '../static'), + emptyOutDir: false, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:7860', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + '/login': 'http://localhost:7860', + '/logout': 'http://localhost:7860', + '/admin': 'http://localhost:7860', + '/public': 'http://localhost:7860', + }, + }, +}) diff --git a/main.py b/main.py new file mode 100644 index 0000000000000000000000000000000000000000..d288cba83dfd9eaee413ff8748606c99b6d720e5 --- /dev/null +++ b/main.py @@ -0,0 +1,3203 @@ +import json, time, os, asyncio, uuid, ssl, re, yaml, base64 +from datetime import datetime, timezone, timedelta +from typing import List, Optional, Union, Dict, Any +from pathlib import Path +import logging +from dotenv import load_dotenv + +import httpx +import aiofiles +from fastapi import FastAPI, HTTPException, Header, Request, Body, Form, UploadFile, File +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse, JSONResponse, FileResponse +from fastapi.staticfiles import StaticFiles +from pydantic import BaseModel +from util.streaming_parser import parse_json_array_stream_async +from collections import deque +from threading import Lock +from core.database import stats_db + +# ---------- 数据目录配置 ---------- +DATA_DIR = "./data" +logger_prefix = "[LOCAL]" + +# 确保数据目录存在 +os.makedirs(DATA_DIR, exist_ok=True) + +# 统一的数据文件路径 +TASK_HISTORY_MTIME: float = 0.0 +IMAGE_DIR = os.path.join(DATA_DIR, "images") +VIDEO_DIR = os.path.join(DATA_DIR, "videos") + +# 确保图片和视频目录存在 +os.makedirs(IMAGE_DIR, exist_ok=True) +os.makedirs(VIDEO_DIR, exist_ok=True) + +# 导入认证模块 +from core.auth import verify_api_key +from core.session_auth import is_logged_in, login_user, logout_user, require_login, generate_session_secret + +# 导入核心模块 +from core.message import ( + get_conversation_key, + parse_last_message, + build_full_context_text +) +from core.google_api import ( + get_common_headers, + create_google_session, + upload_context_file, + get_session_file_metadata, + download_image_with_jwt, + save_image_to_hf, +) +from core.account import ( + AccountManager, + MultiAccountManager, + RetryPolicy, + CooldownConfig, + format_account_expiration, + load_multi_account_config, + load_accounts_from_source, + reload_accounts as _reload_accounts, + update_accounts_config as _update_accounts_config, + delete_account as _delete_account, + update_account_disabled_status as _update_account_disabled_status, + bulk_update_account_disabled_status as _bulk_update_account_disabled_status, + bulk_delete_accounts as _bulk_delete_accounts +) +from core.proxy_utils import parse_proxy_setting + +# 导入 Uptime 追踪器 +from core import uptime as uptime_tracker + +# 导入配置管理和模板系统 +from core.config import config_manager, config + +# 数据库存储支持 +from core import storage, account + +# 模型到配额类型的映射 +MODEL_TO_QUOTA_TYPE = { + "gemini-imagen": "images", + "gemini-veo": "videos" +} + +# ---------- 日志配置 ---------- + +# 内存日志缓冲区 (保留最近 3000 条日志,重启后清空) +log_buffer = deque(maxlen=3000) +log_lock = Lock() + +# 统计数据持久化 +stats_lock = asyncio.Lock() # 改为异步锁 + +async def load_stats(): + """加载统计数据(异步)。数据库不可用时使用内存默认值。""" + data = None + if storage.is_database_enabled(): + try: + has_stats = await asyncio.to_thread(storage.has_stats_sync) + if has_stats: + data = await asyncio.to_thread(storage.load_stats_sync) + if not isinstance(data, dict): + data = None + except Exception as e: + logger.error(f"[STATS] 数据库加载失败: {str(e)[:50]}") + + if data is None: + data = { + "total_visitors": 0, + "total_requests": 0, + "success_count": 0, + "failed_count": 0, + "request_timestamps": [], + "model_request_timestamps": {}, + "failure_timestamps": [], + "rate_limit_timestamps": [], + "visitor_ips": {}, + "account_conversations": {}, + "account_failures": {}, + "recent_conversations": [] + } + + if isinstance(data.get("request_timestamps"), list): + data["request_timestamps"] = deque(data["request_timestamps"], maxlen=20000) + if isinstance(data.get("failure_timestamps"), list): + data["failure_timestamps"] = deque(data["failure_timestamps"], maxlen=10000) + if isinstance(data.get("rate_limit_timestamps"), list): + data["rate_limit_timestamps"] = deque(data["rate_limit_timestamps"], maxlen=10000) + + return data + +async def save_stats(stats): + """保存统计数据(异步)。数据库不可用时不落盘。""" + def convert_deques(obj): + """递归转换所有 deque 对象为 list""" + if isinstance(obj, deque): + return list(obj) + elif isinstance(obj, dict): + return {k: convert_deques(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [convert_deques(item) for item in obj] + return obj + + stats_to_save = convert_deques(stats) + + if storage.is_database_enabled(): + try: + saved = await asyncio.to_thread(storage.save_stats_sync, stats_to_save) + if saved: + return + except Exception as e: + logger.error(f"[STATS] 数据库保存失败: {str(e)[:50]}") + return + +# 初始化统计数据(需要在启动时异步加载) +global_stats = { + "total_visitors": 0, + "total_requests": 0, + "success_count": 0, + "failed_count": 0, + "request_timestamps": deque(maxlen=20000), + "model_request_timestamps": {}, + "failure_timestamps": deque(maxlen=10000), + "rate_limit_timestamps": deque(maxlen=10000), + "visitor_ips": {}, + "account_conversations": {}, + "account_failures": {}, + "recent_conversations": [] +} + +# 任务历史记录(内存存储,容器重启后清空) +task_history = deque(maxlen=100) # 最多保留100条历史记录 +task_history_lock = Lock() + + +def get_beijing_time_str(ts: Optional[float] = None) -> str: + tz = timezone(timedelta(hours=8)) + current = datetime.fromtimestamp(ts or time.time(), tz=tz) + return current.strftime("%Y-%m-%d %H:%M:%S") + + +def save_task_to_history(task_type: str, task_data: dict) -> None: + """保存任务历史记录(只存储简要信息)""" + with task_history_lock: + history_entry = _build_history_entry(task_type, task_data) + entry_id = history_entry.get("id") + if entry_id: + for i in range(len(task_history) - 1, -1, -1): + if task_history[i].get("id") == entry_id: + task_history.remove(task_history[i]) + break + task_history.append(history_entry) + _persist_task_history() + logger.info(f"[HISTORY] Saved {task_type} task to history: {history_entry['id']}") + + +def _build_history_entry(task_type: str, task_data: dict, is_live: bool = False) -> dict: + total_value = task_data.get("count") if task_type == "register" else len(task_data.get("account_ids", [])) + return { + "id": task_data.get("id", ""), + "type": task_type, # "register" or "login" + "status": task_data.get("status", ""), + "progress": task_data.get("progress", 0), + "total": total_value, + "success_count": task_data.get("success_count", 0), + "fail_count": task_data.get("fail_count", 0), + "created_at": task_data.get("created_at", time.time()), + "finished_at": task_data.get("finished_at"), + "is_live": is_live, + } + + +def _persist_task_history() -> None: + """持久化任务历史到数据库(仅数据库模式)。""" + if not storage.is_database_enabled(): + return + try: + if not task_history: + storage.clear_task_history_sync() + return + storage.save_task_history_entry_sync(task_history[-1]) + except Exception as exc: + logger.warning(f"[HISTORY] Persist task history failed: {exc}") + + +def _load_task_history() -> None: + """从数据库加载任务历史(仅数据库模式)。""" + if not storage.is_database_enabled(): + return + try: + history = storage.load_task_history_sync(limit=100) + if not isinstance(history, list): + return + with task_history_lock: + task_history.clear() + for entry in history: + if isinstance(entry, dict): + task_history.append(entry) + except Exception as exc: + logger.warning(f"[HISTORY] Load task history failed: {exc}") + + +def build_recent_conversation_entry( + request_id: str, + model: Optional[str], + message_count: Optional[int], + start_ts: float, + status: str, + duration_s: Optional[float] = None, + error_detail: Optional[str] = None, +) -> dict: + start_time = get_beijing_time_str(start_ts) + if model: + start_content = f"{model}" + if message_count: + start_content = f"{model} | {message_count}条消息" + else: + start_content = "请求处理中" + + events = [{ + "time": start_time, + "type": "start", + "content": start_content, + }] + + end_time = get_beijing_time_str(start_ts + duration_s) if duration_s is not None else get_beijing_time_str() + + if status == "success": + if duration_s is not None: + events.append({ + "time": end_time, + "type": "complete", + "status": "success", + "content": f"响应完成 | 耗时{duration_s:.2f}s", + }) + else: + events.append({ + "time": end_time, + "type": "complete", + "status": "success", + "content": "响应完成", + }) + elif status == "timeout": + events.append({ + "time": end_time, + "type": "complete", + "status": "timeout", + "content": "请求超时", + }) + else: + detail = error_detail or "请求失败" + events.append({ + "time": end_time, + "type": "complete", + "status": "error", + "content": detail[:120], + }) + + return { + "request_id": request_id, + "start_time": start_time, + "start_ts": start_ts, + "status": status, + "events": events, + } + +class MemoryLogHandler(logging.Handler): + """自定义日志处理器,将日志写入内存缓冲区""" + def emit(self, record): + log_entry = self.format(record) + # 转换为北京时间(UTC+8) + beijing_tz = timezone(timedelta(hours=8)) + beijing_time = datetime.fromtimestamp(record.created, tz=beijing_tz) + with log_lock: + log_buffer.append({ + "time": beijing_time.strftime("%Y-%m-%d %H:%M:%S"), + "level": record.levelname, + "message": record.getMessage() + }) + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s", + datefmt="%H:%M:%S", +) +logger = logging.getLogger("gemini") + +_load_task_history() + +# ---------- Linux zombie process reaper ---------- +# DrissionPage / Chromium may spawn subprocesses that exit without being waited on, +# which can accumulate as zombies () in long-running services. +try: + from core.child_reaper import install_child_reaper + + install_child_reaper(log=lambda m: logger.warning(m)) +except Exception: + # Never fail startup due to optional process reaper. + pass + +# 添加内存日志处理器 +memory_handler = MemoryLogHandler() +memory_handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(message)s", datefmt="%H:%M:%S")) +logger.addHandler(memory_handler) + +# ---------- 配置管理(使用统一配置系统)---------- +# 所有配置通过 config_manager 访问,优先级:环境变量 > YAML > 默认值 +TIMEOUT_SECONDS = 300 +API_KEY = config.basic.api_key +ADMIN_KEY = config.security.admin_key +_proxy_auth, _no_proxy_auth = parse_proxy_setting(config.basic.proxy_for_auth) +_proxy_chat, _no_proxy_chat = parse_proxy_setting(config.basic.proxy_for_chat) +PROXY_FOR_AUTH = _proxy_auth +PROXY_FOR_CHAT = _proxy_chat +_NO_PROXY = ",".join(filter(None, {_no_proxy_auth, _no_proxy_chat})) +if _NO_PROXY: + os.environ["NO_PROXY"] = _NO_PROXY +else: + os.environ.pop("NO_PROXY", None) +BASE_URL = config.basic.base_url +SESSION_SECRET_KEY = config.security.session_secret_key +SESSION_EXPIRE_HOURS = config.session.expire_hours + +# ---------- 公开展示配置 ---------- +LOGO_URL = config.public_display.logo_url +CHAT_URL = config.public_display.chat_url + +# ---------- 图片生成配置 ---------- +IMAGE_GENERATION_ENABLED = config.image_generation.enabled +IMAGE_GENERATION_MODELS = config.image_generation.supported_models + +def get_request_quota_type(model_name: str) -> str: + """根据模型名称返回本次请求的配额类型。""" + if model_name in MODEL_TO_QUOTA_TYPE: + return MODEL_TO_QUOTA_TYPE[model_name] + if IMAGE_GENERATION_ENABLED and model_name in IMAGE_GENERATION_MODELS: + return "images" + return "text" + +def get_required_quota_types(model_name: str) -> List[str]: + """所有请求都需要文本配额;图/视频请求还需要对应配额。""" + required = ["text"] + request_quota = get_request_quota_type(model_name) + if request_quota != "text": + required.append(request_quota) + return required + +# ---------- 虚拟模型映射 ---------- +VIRTUAL_MODELS = { + "gemini-imagen": {"imageGenerationSpec": {}}, + "gemini-veo": {"videoGenerationSpec": {}}, +} + +def get_tools_spec(model_name: str) -> dict: + """根据模型名称返回工具配置""" + # 虚拟模型 + if model_name in VIRTUAL_MODELS: + return VIRTUAL_MODELS[model_name] + + # 普通模型 + tools_spec = { + "webGroundingSpec": {}, + "toolRegistry": "default_tool_registry", + } + + if IMAGE_GENERATION_ENABLED and model_name in IMAGE_GENERATION_MODELS: + tools_spec["imageGenerationSpec"] = {} + + return tools_spec + + +# ---------- 重试配置 ---------- +MAX_ACCOUNT_SWITCH_TRIES = config.retry.max_account_switch_tries +SESSION_CACHE_TTL_SECONDS = config.retry.session_cache_ttl_seconds +AUTO_REFRESH_ACCOUNTS_SECONDS = config.retry.auto_refresh_accounts_seconds + +def build_retry_policy() -> RetryPolicy: + return RetryPolicy( + cooldowns=CooldownConfig( + text=config.retry.text_rate_limit_cooldown_seconds, + images=config.retry.images_rate_limit_cooldown_seconds, + videos=config.retry.videos_rate_limit_cooldown_seconds, + ), + ) + +RETRY_POLICY = build_retry_policy() + +# ---------- 模型映射配置 ---------- +MODEL_MAPPING = { + "gemini-auto": None, + "gemini-2.5-flash": "gemini-2.5-flash", + "gemini-2.5-pro": "gemini-2.5-pro", + "gemini-3-flash-preview": "gemini-3-flash-preview", + "gemini-3-pro-preview": "gemini-3-pro-preview", + "gemini-3.1-pro-preview": "gemini-3.1-pro-preview" +} + +# ---------- HTTP 客户端 ---------- +# 对话操作客户端(用于JWT获取、创建会话、发送消息) +http_client = httpx.AsyncClient( + proxy=(PROXY_FOR_CHAT or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) +) + +# 对话流式客户端(用于流式响应) +http_client_chat = httpx.AsyncClient( + proxy=(PROXY_FOR_CHAT or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) +) + +# 账户操作客户端(用于注册/登录/刷新) +http_client_auth = httpx.AsyncClient( + proxy=(PROXY_FOR_AUTH or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) +) + +# 打印代理配置日志 +logger.info(f"[PROXY] Account operations (register/login/refresh): {PROXY_FOR_AUTH if PROXY_FOR_AUTH else 'disabled'}") +logger.info(f"[PROXY] Chat operations (JWT/session/messages): {PROXY_FOR_CHAT if PROXY_FOR_CHAT else 'disabled'}") + +# ---------- 工具函数 ---------- +def get_base_url(request: Request) -> str: + """获取完整的base URL(优先环境变量,否则从请求自动获取)""" + # 优先使用环境变量 + if BASE_URL: + return BASE_URL.rstrip("/") + + # 自动从请求获取(兼容反向代理) + forwarded_proto = request.headers.get("x-forwarded-proto", request.url.scheme) + forwarded_host = request.headers.get("x-forwarded-host", request.headers.get("host")) + + return f"{forwarded_proto}://{forwarded_host}" + + + +# ---------- 常量定义 ---------- +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36" + +# ---------- 多账户支持 ---------- +# (AccountConfig, AccountManager, MultiAccountManager 已移至 core/account.py) + +# ---------- 配置文件管理 ---------- +# (配置管理函数已移至 core/account.py) + +# 初始化多账户管理器 +multi_account_mgr = load_multi_account_config( + http_client, + USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, + global_stats +) + +# ---------- 自动注册/刷新服务 ---------- +register_service = None +login_service = None + +def _set_multi_account_mgr(new_mgr): + global multi_account_mgr + multi_account_mgr = new_mgr + if register_service: + register_service.multi_account_mgr = new_mgr + if login_service: + login_service.multi_account_mgr = new_mgr + +def _get_global_stats(): + return global_stats + +try: + from core.register_service import RegisterService + from core.login_service import LoginService + register_service = RegisterService( + multi_account_mgr, + http_client_auth, + USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, + _get_global_stats, + _set_multi_account_mgr, + ) + login_service = LoginService( + multi_account_mgr, + http_client_auth, + USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, + _get_global_stats, + _set_multi_account_mgr, + ) +except Exception as e: + logger.warning("[SYSTEM] 自动注册/刷新服务不可用: %s", e) + register_service = None + login_service = None + +# 验证必需的环境变量 +if not ADMIN_KEY: + logger.error("[SYSTEM] 未配置 ADMIN_KEY 环境变量,请设置后重启") + import sys + sys.exit(1) + +# 启动日志 +logger.info("[SYSTEM] API端点: /v1/chat/completions") +logger.info("[SYSTEM] Admin API endpoints: /admin/*") +logger.info("[SYSTEM] Public endpoints: /public/log, /public/stats, /public/uptime") +logger.info(f"[SYSTEM] Session过期时间: {SESSION_EXPIRE_HOURS}小时") +logger.info("[SYSTEM] 系统初始化完成") + +# ---------- JWT 管理 ---------- +# (JWTManager已移至 core/jwt.py) + +# ---------- Session & File 管理 ---------- +# (Google API函数已移至 core/google_api.py) + +# ---------- 消息处理逻辑 ---------- +# (消息处理函数已移至 core/message.py) + +# ---------- 媒体处理函数 ---------- +def process_image(data: bytes, mime: str, chat_id: str, file_id: str, base_url: str, idx: int, request_id: str, account_id: str) -> str: + """处理图片:根据配置返回 base64 或 URL""" + output_format = config_manager.image_output_format + + if output_format == "base64": + b64 = base64.b64encode(data).decode() + logger.info(f"[IMAGE] [{account_id}] [req_{request_id}] 图片{idx}已编码为base64") + return f"\n\n![生成的图片](data:{mime};base64,{b64})\n\n" + else: + url = save_image_to_hf(data, chat_id, file_id, mime, base_url, IMAGE_DIR) + logger.info(f"[IMAGE] [{account_id}] [req_{request_id}] 图片{idx}已保存: {url}") + return f"\n\n![生成的图片]({url})\n\n" + +def process_video(data: bytes, mime: str, chat_id: str, file_id: str, base_url: str, idx: int, request_id: str, account_id: str) -> str: + """处理视频:根据配置返回不同格式""" + url = save_image_to_hf(data, chat_id, file_id, mime, base_url, VIDEO_DIR, "videos") + logger.info(f"[VIDEO] [{account_id}] [req_{request_id}] 视频{idx}已保存: {url}") + + output_format = config_manager.video_output_format + + if output_format == "html": + return f'\n\n\n\n' + elif output_format == "markdown": + return f"\n\n![生成的视频]({url})\n\n" + else: # url + return f"\n\n{url}\n\n" + +def process_media(data: bytes, mime: str, chat_id: str, file_id: str, base_url: str, idx: int, request_id: str, account_id: str) -> str: + """统一媒体处理入口:根据 MIME 类型分发到对应处理器""" + logger.info(f"[MEDIA] [{account_id}] [req_{request_id}] 处理媒体{idx}: MIME={mime}") + if mime.startswith("video/"): + return process_video(data, mime, chat_id, file_id, base_url, idx, request_id, account_id) + else: + return process_image(data, mime, chat_id, file_id, base_url, idx, request_id, account_id) + +# ---------- OpenAI 兼容接口 ---------- +app = FastAPI(title="Gemini-Business OpenAI Gateway") + +frontend_origin = os.getenv("FRONTEND_ORIGIN", "").strip() +allow_all_origins = os.getenv("ALLOW_ALL_ORIGINS", "0") == "1" +if allow_all_origins and not frontend_origin: + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=False, + allow_methods=["*"], + allow_headers=["*"], + ) +elif frontend_origin: + app.add_middleware( + CORSMiddleware, + allow_origins=[frontend_origin], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + +app.mount("/static", StaticFiles(directory="static"), name="static") +if os.path.exists(os.path.join("static", "assets")): + app.mount("/assets", StaticFiles(directory=os.path.join("static", "assets")), name="assets") +if os.path.exists(os.path.join("static", "vendor")): + app.mount("/vendor", StaticFiles(directory=os.path.join("static", "vendor")), name="vendor") + +@app.get("/") +async def serve_frontend_index(): + index_path = os.path.join("static", "index.html") + if os.path.exists(index_path): + return FileResponse(index_path) + raise HTTPException(404, "Not Found") + +@app.get("/logo.svg") +async def serve_logo(): + logo_path = os.path.join("static", "logo.svg") + if os.path.exists(logo_path): + return FileResponse(logo_path) + raise HTTPException(404, "Not Found") + +@app.get("/health") +async def health_check(): + """健康检查端点,用于 Docker HEALTHCHECK""" + return {"status": "ok"} + +# ---------- Session 中间件配置 ---------- +from starlette.middleware.sessions import SessionMiddleware +app.add_middleware( + SessionMiddleware, + secret_key=SESSION_SECRET_KEY, + max_age=SESSION_EXPIRE_HOURS * 3600, # 转换为秒 + same_site="lax", + https_only=False # 本地开发可设为False,生产环境建议True +) + +# ---------- Uptime 追踪中间件 ---------- +@app.middleware("http") +async def track_uptime_middleware(request: Request, call_next): + """Uptime 监控:跟踪非对话接口的请求结果。""" + path = request.url.path + if ( + path.startswith("/images/") + or path.startswith("/public/") + or path.startswith("/favicon") + or path.endswith("/v1/chat/completions") + ): + return await call_next(request) + + start_time = time.time() + + try: + response = await call_next(request) + latency_ms = int((time.time() - start_time) * 1000) + success = response.status_code < 400 + uptime_tracker.record_request("api_service", success, latency_ms, response.status_code) + return response + + except Exception: + uptime_tracker.record_request("api_service", False) + raise + + +# ---------- 图片和视频静态服务初始化 ---------- +os.makedirs(IMAGE_DIR, exist_ok=True) +os.makedirs(VIDEO_DIR, exist_ok=True) +app.mount("/images", StaticFiles(directory=IMAGE_DIR), name="images") +app.mount("/videos", StaticFiles(directory=VIDEO_DIR), name="videos") +logger.info(f"[SYSTEM] 图片静态服务已启用: /images/ -> {IMAGE_DIR}") +logger.info(f"[SYSTEM] 视频静态服务已启用: /videos/ -> {VIDEO_DIR}") + +# ---------- 后台任务启动 ---------- + +# 全局变量:记录上次检测到的账号更新时间(用于自动刷新检测) +_last_known_accounts_version: float | None = None + + +async def auto_refresh_accounts_task(): + """后台任务:定期检查数据库中的账号变化,自动刷新""" + global multi_account_mgr, _last_known_accounts_version + + # 初始化:记录当前账号更新时间 + if storage.is_database_enabled() and not os.environ.get("ACCOUNTS_CONFIG"): + _last_known_accounts_version = await asyncio.to_thread( + storage.get_accounts_updated_at_sync + ) + + while True: + try: + # 获取配置的刷新间隔(支持热更新) + refresh_interval = config_manager.auto_refresh_accounts_seconds + if refresh_interval <= 0: + # 自动刷新已禁用,等待一段时间后再检查配置 + await asyncio.sleep(60) + continue + + await asyncio.sleep(refresh_interval) + + # 环境变量优先时无需自动刷新 + if os.environ.get("ACCOUNTS_CONFIG"): + continue + + # 检查数据库是否启用 + if not storage.is_database_enabled(): + continue + + # 获取数据库中的账号更新时间 + db_version = await asyncio.to_thread(storage.get_accounts_updated_at_sync) + if db_version is None: + continue + + # 比较更新时间变化 + if _last_known_accounts_version != db_version: + logger.info("[AUTO-REFRESH] 检测到账号变化,正在自动刷新...") + + # 重新加载账号配置 + multi_account_mgr = _reload_accounts( + multi_account_mgr, + http_client, + USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, + global_stats + ) + + # Fix inconsistent state: accounts that are no longer expired/disabled + # and have no quota cooldowns should be marked available + for acc_id, acc_mgr in multi_account_mgr.accounts.items(): + if not acc_mgr.config.is_expired() and not acc_mgr.config.disabled and not acc_mgr.is_available: + if not acc_mgr.quota_cooldowns: + acc_mgr.is_available = True + logger.info(f"[AUTO-REFRESH] 账号 {acc_id} 状态已修正为可用") + + _last_known_accounts_version = db_version + logger.info(f"[AUTO-REFRESH] 账号刷新完成,当前账号数: {len(multi_account_mgr.accounts)}") + + except asyncio.CancelledError: + logger.info("[AUTO-REFRESH] 自动刷新任务已停止") + break + except Exception as e: + logger.error(f"[AUTO-REFRESH] 自动刷新任务异常: {type(e).__name__}: {str(e)[:100]}") + await asyncio.sleep(60) # 出错后等待60秒再重试 + + +@app.on_event("startup") +async def startup_event(): + """应用启动时初始化后台任务""" + global global_stats + + # 加载统计数据 + global_stats = await load_stats() + global_stats.setdefault("request_timestamps", []) + global_stats.setdefault("model_request_timestamps", {}) + global_stats.setdefault("failure_timestamps", []) + global_stats.setdefault("rate_limit_timestamps", []) + global_stats.setdefault("recent_conversations", []) + global_stats.setdefault("success_count", 0) + global_stats.setdefault("failed_count", 0) + global_stats.setdefault("account_conversations", {}) + global_stats.setdefault("account_failures", {}) + uptime_tracker.configure_storage(os.path.join(DATA_DIR, "uptime.json")) + uptime_tracker.load_heartbeats() + for account_id, account_mgr in multi_account_mgr.accounts.items(): + account_mgr.conversation_count = global_stats["account_conversations"].get(account_id, 0) + account_mgr.failure_count = global_stats["account_failures"].get(account_id, 0) + logger.info("[SYSTEM] 已恢复账户成功/失败统计") + logger.info(f"[SYSTEM] 统计数据已加载: {global_stats['total_requests']} 次请求, {global_stats['total_visitors']} 位访客") + + # 启动缓存清理任务 + asyncio.create_task(multi_account_mgr.start_background_cleanup()) + logger.info("[SYSTEM] 后台缓存清理任务已启动(间隔: 5分钟)") + + # 启动数据库清理任务 + asyncio.create_task(cleanup_database_task()) + logger.info("[SYSTEM] 数据库清理任务已启动(每天清理一次,保留30天数据)") + + # 启动自动刷新账号任务(仅数据库模式有效) + if os.environ.get("ACCOUNTS_CONFIG"): + logger.info("[SYSTEM] 自动刷新账号已跳过(使用 ACCOUNTS_CONFIG)") + elif storage.is_database_enabled() and AUTO_REFRESH_ACCOUNTS_SECONDS > 0: + asyncio.create_task(auto_refresh_accounts_task()) + logger.info(f"[SYSTEM] 自动刷新账号任务已启动(间隔: {AUTO_REFRESH_ACCOUNTS_SECONDS}秒)") + elif storage.is_database_enabled(): + logger.info("[SYSTEM] 自动刷新账号功能已禁用(配置为0)") + + # 启动自动登录刷新轮询(始终启动,但默认禁用) + if login_service: + try: + asyncio.create_task(login_service.start_polling()) + logger.info("[SYSTEM] 账户刷新轮询服务已启动(默认禁用,可在设置中启用)") + except Exception as e: + logger.error(f"[SYSTEM] 启动登录服务失败: {e}") + else: + logger.info("[SYSTEM] 自动登录刷新未启用或依赖不可用") + + # 启动冷却状态定期保存任务(每5分钟保存一次) + if storage.is_database_enabled(): + asyncio.create_task(save_cooldown_states_task()) + logger.info("[SYSTEM] 冷却状态定期保存任务已启动(间隔: 5分钟)") + + # 启动媒体文件过期清理任务 + asyncio.create_task(cleanup_expired_media_task()) + expire_hours = config.basic.image_expire_hours + if expire_hours < 0: + logger.info("[SYSTEM] 媒体文件过期清理已跳过(设置为永不删除)") + else: + logger.info(f"[SYSTEM] 媒体文件过期清理任务已启动(过期时间: {expire_hours}小时,检查间隔: 30分钟)") + + +@app.on_event("shutdown") +async def shutdown_event(): + """应用关闭时保存冷却状态""" + if storage.is_database_enabled(): + try: + success_count = await account.save_all_cooldown_states(multi_account_mgr) + logger.info(f"[SYSTEM] 应用关闭,已保存 {success_count}/{len(multi_account_mgr.accounts)} 个账户的冷却状态") + except Exception as e: + logger.error(f"[SYSTEM] 关闭时保存冷却状态失败: {e}") + + +async def save_cooldown_states_task(): + """定期保存所有账户的冷却状态到数据库""" + while True: + try: + await asyncio.sleep(300) # 每5分钟执行一次 + for attempt in range(3): + try: + success_count = await account.save_all_cooldown_states(multi_account_mgr) + logger.debug(f"[COOLDOWN] 定期保存: {success_count}/{len(multi_account_mgr.accounts)} 个账户") + break + except Exception as retry_err: + err_msg = str(retry_err) + if "another operation" in err_msg or "ConnectionDoesNotExist" in err_msg or "connection was closed" in err_msg: + if attempt < 2: + logger.warning(f"[COOLDOWN] 数据库连接繁忙,{attempt+1}/3 次重试...") + await asyncio.sleep(5 * (attempt + 1)) + continue + raise + except Exception as e: + logger.error(f"[COOLDOWN] 定期保存失败: {e}") + + +async def cleanup_database_task(): + """定时清理数据库过期数据""" + while True: + try: + await asyncio.sleep(24 * 3600) # 每天执行一次 + deleted_count = await stats_db.cleanup_old_data(days=30) + logger.info(f"[DATABASE] 清理了 {deleted_count} 条过期数据(保留30天)") + except Exception as e: + logger.error(f"[DATABASE] 清理数据失败: {e}") + +# ---------- 图片画廊 API ---------- + +def _scan_media_files() -> list: + """扫描 data/images 和 data/videos 目录中的所有媒体文件""" + beijing_tz = timezone(timedelta(hours=8)) + now = time.time() + expire_hours = config.basic.image_expire_hours + files = [] + + for directory, url_prefix, media_type in [ + (IMAGE_DIR, "images", "image"), + (VIDEO_DIR, "videos", "video"), + ]: + if not os.path.isdir(directory): + continue + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + if not os.path.isfile(filepath): + continue + try: + stat = os.stat(filepath) + mtime = stat.st_mtime + size = stat.st_size + created_at = datetime.fromtimestamp(mtime, tz=beijing_tz).strftime("%Y-%m-%d %H:%M:%S") + # 计算剩余有效时间 + if expire_hours > 0: + expires_in_seconds = (mtime + expire_hours * 3600) - now + expired = expires_in_seconds <= 0 + else: + expires_in_seconds = -1 # 永不过期 + expired = False + + ext = os.path.splitext(filename)[1].lower() + file_type = "video" if ext in (".mp4", ".webm", ".mov") else media_type + + files.append({ + "filename": filename, + "url": f"/{url_prefix}/{filename}", + "size": size, + "created_at": created_at, + "mtime": mtime, + "type": file_type, + "expired": expired, + "expires_in_seconds": int(expires_in_seconds) if expire_hours > 0 else None, + }) + except Exception: + continue + + # 按创建时间倒序 + files.sort(key=lambda x: x["mtime"], reverse=True) + return files + + +@app.get("/admin/gallery") +@require_login() +async def admin_get_gallery(request: Request): + """获取图片画廊列表""" + files = await asyncio.to_thread(_scan_media_files) + total_size = sum(f["size"] for f in files) + + return { + "files": files, + "total": len(files), + "total_size": total_size, + "expire_hours": config.basic.image_expire_hours, + } + + +@app.delete("/admin/gallery/{filename:path}") +@require_login() +async def admin_delete_gallery_file(request: Request, filename: str): + """删除画廊中的单个文件""" + # 安全校验:防止路径穿越 + safe_name = os.path.basename(filename) + if safe_name != filename or ".." in filename: + raise HTTPException(400, "非法文件名") + + # 在 images 和 videos 目录中查找 + for directory in [IMAGE_DIR, VIDEO_DIR]: + filepath = os.path.join(directory, safe_name) + if os.path.isfile(filepath): + try: + os.remove(filepath) + logger.info(f"[GALLERY] 已删除文件: {safe_name}") + return {"success": True, "message": f"已删除 {safe_name}"} + except Exception as e: + raise HTTPException(500, f"删除失败: {str(e)}") + + raise HTTPException(404, "文件不存在") + + +@app.post("/admin/gallery/cleanup") +@require_login() +async def admin_cleanup_expired(request: Request): + """立即清理过期媒体文件""" + expire_hours = config.basic.image_expire_hours + if expire_hours < 0: + return {"success": True, "deleted": 0, "deleted_images": 0, "deleted_videos": 0, "message": "当前设置为永不删除"} + + now = time.time() + deleted_images = 0 + deleted_videos = 0 + video_exts = (".mp4", ".webm", ".mov") + + for directory, is_video_dir in [(IMAGE_DIR, False), (VIDEO_DIR, True)]: + if not os.path.isdir(directory): + continue + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + if not os.path.isfile(filepath): + continue + try: + mtime = os.path.getmtime(filepath) + age_hours = (now - mtime) / 3600 + if age_hours > expire_hours: + os.remove(filepath) + ext = os.path.splitext(filename)[1].lower() + if is_video_dir or ext in video_exts: + deleted_videos += 1 + else: + deleted_images += 1 + except Exception: + continue + + deleted_count = deleted_images + deleted_videos + if deleted_count > 0: + logger.info(f"[GALLERY] 手动清理了 {deleted_count} 个过期媒体文件(图片: {deleted_images}, 视频: {deleted_videos})") + + return { + "success": True, + "deleted": deleted_count, + "deleted_images": deleted_images, + "deleted_videos": deleted_videos, + "message": f"已清理 {deleted_count} 个过期文件" if deleted_count > 0 else "没有过期文件需要清理", + } + + +async def cleanup_expired_media_task(): + """定期清理过期的图片和视频文件""" + while True: + try: + await asyncio.sleep(30 * 60) # 每 30 分钟检查一次 + + expire_hours = config.basic.image_expire_hours + if expire_hours < 0: + # -1 表示永不删除 + continue + + now = time.time() + deleted_count = 0 + + for directory in [IMAGE_DIR, VIDEO_DIR]: + if not os.path.isdir(directory): + continue + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + if not os.path.isfile(filepath): + continue + try: + mtime = os.path.getmtime(filepath) + age_hours = (now - mtime) / 3600 + if age_hours > expire_hours: + os.remove(filepath) + deleted_count += 1 + except Exception: + continue + + if deleted_count > 0: + logger.info(f"[GALLERY] 清理了 {deleted_count} 个过期媒体文件(过期时间: {expire_hours}小时)") + + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"[GALLERY] 清理过期文件失败: {e}") + +# ---------- 日志脱敏函数 ---------- +def get_sanitized_logs(limit: int = 100) -> list: + """获取脱敏后的日志列表,按请求ID分组并提取关键事件""" + with log_lock: + logs = list(log_buffer) + + # 按请求ID分组(支持两种格式:带[req_xxx]和不带的) + request_logs = {} + orphan_logs = [] # 没有request_id的日志(如选择账户) + + for log in logs: + message = log["message"] + req_match = re.search(r'\[req_([a-z0-9]+)\]', message) + + if req_match: + request_id = req_match.group(1) + if request_id not in request_logs: + request_logs[request_id] = [] + request_logs[request_id].append(log) + else: + # 没有request_id的日志(如选择账户),暂存 + orphan_logs.append(log) + + # 将orphan_logs(如选择账户)关联到对应的请求 + # 策略:将orphan日志关联到时间上最接近的后续请求 + for orphan in orphan_logs: + orphan_time = orphan["time"] + # 找到时间上最接近且在orphan之后的请求 + closest_request_id = None + min_time_diff = None + + for request_id, req_logs in request_logs.items(): + if req_logs: + first_log_time = req_logs[0]["time"] + # orphan应该在请求之前或同时 + if first_log_time >= orphan_time: + if min_time_diff is None or first_log_time < min_time_diff: + min_time_diff = first_log_time + closest_request_id = request_id + + # 如果找到最接近的请求,将orphan日志插入到该请求的日志列表开头 + if closest_request_id: + request_logs[closest_request_id].insert(0, orphan) + + # 为每个请求提取关键事件 + sanitized = [] + for request_id, req_logs in request_logs.items(): + # 收集关键信息 + model = None + message_count = None + retry_events = [] + final_status = "in_progress" + duration = None + start_time = req_logs[0]["time"] + + # 遍历该请求的所有日志 + for log in req_logs: + message = log["message"] + + # 提取模型名称和消息数量(开始对话) + if '收到请求:' in message and not model: + model_match = re.search(r'收到请求: ([^ |]+)', message) + if model_match: + model = model_match.group(1) + count_match = re.search(r'(\d+)条消息', message) + if count_match: + message_count = int(count_match.group(1)) + + # 提取重试事件(包括失败尝试、账户切换、选择账户) + # 注意:不提取"正在重试"日志,因为它和"失败 (尝试"是配套的 + if any(keyword in message for keyword in ['切换账户', '选择账户', '失败 (尝试']): + retry_events.append({ + "time": log["time"], + "message": message + }) + + # 提取响应完成(最高优先级 - 最终成功则忽略中间错误) + if '响应完成:' in message: + time_match = re.search(r'响应完成: ([\d.]+)秒', message) + if time_match: + duration = time_match.group(1) + 's' + final_status = "success" + + # 检测非流式响应完成 + if '非流式响应完成' in message: + final_status = "success" + + # 检测失败状态(仅在非success状态下) + if final_status != "success" and (log['level'] == 'ERROR' or '失败' in message): + final_status = "error" + + # 检测超时(仅在非success状态下) + if final_status != "success" and '超时' in message: + final_status = "timeout" + + # 如果没有模型信息但有错误,仍然显示 + if not model and final_status == "in_progress": + continue + + # 构建关键事件列表 + events = [] + + # 1. 开始对话 + if model: + events.append({ + "time": start_time, + "type": "start", + "content": f"{model} | {message_count}条消息" if message_count else model + }) + else: + # 没有模型信息但有错误的情况 + events.append({ + "time": start_time, + "type": "start", + "content": "请求处理中" + }) + + # 2. 重试事件 + failure_count = 0 # 失败重试计数 + account_select_count = 0 # 账户选择计数 + + for i, retry in enumerate(retry_events): + msg = retry["message"] + + # 识别不同类型的重试事件(按优先级匹配) + if '失败 (尝试' in msg: + # 创建会话失败 + failure_count += 1 + events.append({ + "time": retry["time"], + "type": "retry", + "content": f"服务异常,正在重试({failure_count})" + }) + elif '选择账户' in msg: + # 账户选择/切换 + account_select_count += 1 + + # 检查下一条日志是否是"切换账户",如果是则跳过当前"选择账户"(避免重复) + next_is_switch = (i + 1 < len(retry_events) and '切换账户' in retry_events[i + 1]["message"]) + + if not next_is_switch: + if account_select_count == 1: + # 第一次选择:显示为"选择服务节点" + events.append({ + "time": retry["time"], + "type": "select", + "content": "选择服务节点" + }) + else: + # 第二次及以后:显示为"切换服务节点" + events.append({ + "time": retry["time"], + "type": "switch", + "content": "切换服务节点" + }) + elif '切换账户' in msg: + # 运行时切换账户(显示为"切换服务节点") + events.append({ + "time": retry["time"], + "type": "switch", + "content": "切换服务节点" + }) + + # 3. 完成事件 + if final_status == "success": + if duration: + events.append({ + "time": req_logs[-1]["time"], + "type": "complete", + "status": "success", + "content": f"响应完成 | 耗时{duration}" + }) + else: + events.append({ + "time": req_logs[-1]["time"], + "type": "complete", + "status": "success", + "content": "响应完成" + }) + elif final_status == "error": + events.append({ + "time": req_logs[-1]["time"], + "type": "complete", + "status": "error", + "content": "请求失败" + }) + elif final_status == "timeout": + events.append({ + "time": req_logs[-1]["time"], + "type": "complete", + "status": "timeout", + "content": "请求超时" + }) + + sanitized.append({ + "request_id": request_id, + "start_time": start_time, + "status": final_status, + "events": events + }) + + # 按时间排序并限制数量 + sanitized.sort(key=lambda x: x["start_time"], reverse=True) + return sanitized[:limit] + +class Message(BaseModel): + role: str + content: Union[str, List[Dict[str, Any]]] + +class ChatRequest(BaseModel): + model: str = "gemini-auto" + messages: List[Message] + stream: bool = False + temperature: Optional[float] = 0.7 + top_p: Optional[float] = 1.0 + +class ImageGenerationRequest(BaseModel): + """OpenAI /v1/images/generations 请求格式""" + prompt: str + model: str = "gemini-imagen" + n: Optional[int] = 1 + size: Optional[str] = "1024x1024" + response_format: Optional[str] = None # "url" or "b64_json",None 表示使用系统配置 + quality: Optional[str] = "standard" # "standard" or "hd" + style: Optional[str] = "natural" # "natural" or "vivid" + +def create_chunk(id: str, created: int, model: str, delta: dict, finish_reason: Union[str, None]) -> str: + chunk = { + "id": id, + "object": "chat.completion.chunk", + "created": created, + "model": model, + "choices": [{ + "index": 0, + "delta": delta, + "logprobs": None, # OpenAI 标准字段 + "finish_reason": finish_reason + }], + "system_fingerprint": None # OpenAI 标准字段(可选) + } + return json.dumps(chunk) +# ---------- Auth endpoints (API) ---------- + +@app.post("/login") +async def admin_login_post(request: Request, admin_key: str = Form(...)): + """Admin login (API)""" + if admin_key == ADMIN_KEY: + login_user(request) + logger.info("[AUTH] Admin login success") + return {"success": True} + logger.warning("[AUTH] Login failed - invalid key") + raise HTTPException(401, "Invalid key") + + +@app.post("/logout") +@require_login(redirect_to_login=False) +async def admin_logout(request: Request): + """Admin logout (API)""" + logout_user(request) + logger.info("[AUTH] Admin logout") + return {"success": True} + + + +@app.get("/admin/stats") +@require_login() +async def admin_stats(request: Request, time_range: str = "24h"): + """ + 获取统计数据 + + Args: + time_range: 时间范围 "24h", "7d", "30d" + """ + now = time.time() + + active_accounts = 0 + failed_accounts = 0 + rate_limited_accounts = 0 + idle_accounts = 0 + + for account_manager in multi_account_mgr.accounts.values(): + config = account_manager.config + cooldown_seconds, cooldown_reason = account_manager.get_cooldown_info() + + # 判断账户状态 + is_expired = config.is_expired() + is_manual_disabled = config.disabled + is_rate_limited = cooldown_seconds > 0 and cooldown_reason and "冷却" in cooldown_reason + is_failed = is_expired + is_active = (not is_failed) and (not is_manual_disabled) and (not is_rate_limited) + + if is_rate_limited: + rate_limited_accounts += 1 + elif is_failed: + failed_accounts += 1 + elif is_active: + active_accounts += 1 + else: + idle_accounts += 1 + + total_accounts = len(multi_account_mgr.accounts) + + # 从数据库获取统计数据 + trend_data = await stats_db.get_stats_by_time_range(time_range) + success_count, failed_count = await stats_db.get_total_counts() + + return { + "total_accounts": total_accounts, + "active_accounts": active_accounts, + "failed_accounts": failed_accounts, + "rate_limited_accounts": rate_limited_accounts, + "idle_accounts": idle_accounts, + "success_count": success_count, + "failed_count": failed_count, + "trend": trend_data + } + +@app.get("/admin/accounts") +@require_login() +async def admin_get_accounts(request: Request): + """获取所有账户的状态信息""" + accounts_info = [] + for account_id, account_manager in multi_account_mgr.accounts.items(): + config = account_manager.config + remaining_hours = config.get_remaining_hours() + status, status_color, remaining_display = format_account_expiration(remaining_hours) + cooldown_seconds, cooldown_reason = account_manager.get_cooldown_info() + quota_status = account_manager.get_quota_status() + + accounts_info.append({ + "id": config.account_id, + "status": status, + "expires_at": config.expires_at or "未设置", + "remaining_hours": remaining_hours, + "remaining_display": remaining_display, + "is_available": account_manager.is_available, + "failure_count": account_manager.failure_count, + "disabled": config.disabled, + "disabled_reason": getattr(account_manager, 'disabled_reason', None) or getattr(config, 'disabled_reason', None), + "cooldown_seconds": cooldown_seconds, + "cooldown_reason": cooldown_reason, + "conversation_count": account_manager.conversation_count, + "session_usage_count": account_manager.session_usage_count, + "quota_status": quota_status, + "trial_end": config.trial_end, + "trial_days_remaining": config.get_trial_days_remaining(), + }) + + return {"total": len(accounts_info), "accounts": accounts_info} + + +@app.get("/admin/accounts-config") +@require_login() +async def admin_get_config(request: Request): + """获取完整账户配置""" + try: + accounts_data = load_accounts_from_source() + return {"accounts": accounts_data} + except Exception as e: + logger.error(f"[CONFIG] 获取配置失败: {str(e)}") + raise HTTPException(500, f"获取失败: {str(e)}") + +@app.put("/admin/accounts-config") +@require_login() +async def admin_update_config(request: Request, accounts_data: list = Body(...)): + """更新整个账户配置""" + global multi_account_mgr + try: + multi_account_mgr = _update_accounts_config( + accounts_data, multi_account_mgr, http_client, USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, global_stats + ) + return {"status": "success", "message": "配置已更新", "account_count": len(multi_account_mgr.accounts)} + except Exception as e: + logger.error(f"[CONFIG] 更新配置失败: {str(e)}") + raise HTTPException(500, f"更新失败: {str(e)}") + +@app.post("/admin/register/start") +@require_login() +async def admin_start_register(request: Request, count: Optional[int] = Body(default=None), domain: Optional[str] = Body(default=None), mail_provider: Optional[str] = Body(default=None)): + if not register_service: + raise HTTPException(503, "register service unavailable") + task = await register_service.start_register(count=count, domain=domain, mail_provider=mail_provider) + return task.to_dict() + + +@app.post("/admin/register/cancel/{task_id}") +@require_login() +async def admin_cancel_register_task(request: Request, task_id: str, payload: dict = Body(default=None)): + if not register_service: + raise HTTPException(503, "register service unavailable") + payload = payload or {} + reason = payload.get("reason") or "cancelled" + task = await register_service.cancel_task(task_id, reason=reason) + if not task: + raise HTTPException(404, "task not found") + return task.to_dict() + +@app.get("/admin/register/task/{task_id}") +@require_login() +async def admin_get_register_task(request: Request, task_id: str): + if not register_service: + raise HTTPException(503, "register service unavailable") + task = register_service.get_task(task_id) + if not task: + raise HTTPException(404, "task not found") + return task.to_dict() + +@app.get("/admin/register/current") +@require_login() +async def admin_get_current_register_task(request: Request): + if not register_service: + raise HTTPException(503, "register service unavailable") + task = register_service.get_current_task() + if not task: + return {"status": "idle"} + return task.to_dict() + +@app.post("/admin/login/start") +@require_login() +async def admin_start_login(request: Request, account_ids: List[str] = Body(...)): + if not login_service: + raise HTTPException(503, "login service unavailable") + task = await login_service.start_login(account_ids) + return task.to_dict() + + +@app.post("/admin/login/cancel/{task_id}") +@require_login() +async def admin_cancel_login_task(request: Request, task_id: str, payload: dict = Body(default=None)): + if not login_service: + raise HTTPException(503, "login service unavailable") + payload = payload or {} + reason = payload.get("reason") or "cancelled" + task = await login_service.cancel_task(task_id, reason=reason) + if not task: + raise HTTPException(404, "task not found") + return task.to_dict() + +@app.get("/admin/login/task/{task_id}") +@require_login() +async def admin_get_login_task(request: Request, task_id: str): + if not login_service: + raise HTTPException(503, "login service unavailable") + task = login_service.get_task(task_id) + if not task: + raise HTTPException(404, "task not found") + return task.to_dict() + +@app.get("/admin/login/current") +@require_login() +async def admin_get_current_login_task(request: Request): + if not login_service: + raise HTTPException(503, "login service unavailable") + task = login_service.get_current_task() + if not task: + return {"status": "idle"} + return task.to_dict() + +@app.post("/admin/login/check") +@require_login() +async def admin_check_login_refresh(request: Request): + if not login_service: + raise HTTPException(503, "login service unavailable") + task = await login_service.check_and_refresh() + if not task: + return {"status": "idle"} + return task.to_dict() + +@app.delete("/admin/accounts/{account_id}") +@require_login() +async def admin_delete_account(request: Request, account_id: str): + """删除单个账户""" + global multi_account_mgr + try: + multi_account_mgr = _delete_account( + account_id, multi_account_mgr, http_client, USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, global_stats + ) + return {"status": "success", "message": f"账户 {account_id} 已删除", "account_count": len(multi_account_mgr.accounts)} + except Exception as e: + logger.error(f"[CONFIG] 删除账户失败: {str(e)}") + raise HTTPException(500, f"删除失败: {str(e)}") + +@app.put("/admin/accounts/bulk-delete") +@require_login() +async def admin_bulk_delete_accounts(request: Request, account_ids: list[str]): + """批量删除账户,单次最多50个""" + global multi_account_mgr + + # 数量限制验证 + if len(account_ids) > 50: + raise HTTPException(400, f"单次最多删除50个账户,当前请求 {len(account_ids)} 个") + if not account_ids: + raise HTTPException(400, "账户ID列表不能为空") + + try: + multi_account_mgr, success_count, errors = _bulk_delete_accounts( + account_ids, + multi_account_mgr, + http_client, + USER_AGENT, + RETRY_POLICY, + SESSION_CACHE_TTL_SECONDS, + global_stats + ) + return {"status": "success", "success_count": success_count, "errors": errors} + except Exception as e: + logger.error(f"[CONFIG] 批量删除账户失败: {str(e)}") + raise HTTPException(500, f"删除失败: {str(e)}") + +@app.put("/admin/accounts/{account_id}/disable") +@require_login() +async def admin_disable_account(request: Request, account_id: str): + """手动禁用账户""" + global multi_account_mgr + try: + multi_account_mgr = _update_account_disabled_status( + account_id, True, multi_account_mgr + ) + + # 立即保存当前状态到数据库,防止后台任务覆盖 + if account_id in multi_account_mgr.accounts: + account_mgr = multi_account_mgr.accounts[account_id] + await account.save_account_cooldown_state(account_id, account_mgr) + + return {"status": "success", "message": f"账户 {account_id} 已禁用", "account_count": len(multi_account_mgr.accounts)} + except Exception as e: + logger.error(f"[CONFIG] 禁用账户失败: {str(e)}") + raise HTTPException(500, f"禁用失败: {str(e)}") + +@app.put("/admin/accounts/{account_id}/enable") +@require_login() +async def admin_enable_account(request: Request, account_id: str): + """启用账户(同时重置冷却状态)""" + global multi_account_mgr + try: + multi_account_mgr = _update_account_disabled_status( + account_id, False, multi_account_mgr + ) + + # 重置运行时冷却状态(允许手动恢复冷却中的账户) + if account_id in multi_account_mgr.accounts: + account_mgr = multi_account_mgr.accounts[account_id] + account_mgr.quota_cooldowns = {} + logger.info(f"[CONFIG] 账户 {account_id} 冷却状态已重置") + + # 立即保存清空的冷却状态到数据库,防止后台任务覆盖 + await account.save_account_cooldown_state(account_id, account_mgr) + + return {"status": "success", "message": f"账户 {account_id} 已启用", "account_count": len(multi_account_mgr.accounts)} + except Exception as e: + logger.error(f"[CONFIG] 启用账户失败: {str(e)}") + raise HTTPException(500, f"启用失败: {str(e)}") + +@app.put("/admin/accounts/bulk-enable") +@require_login() +async def admin_bulk_enable_accounts(request: Request, account_ids: list[str]): + """批量启用账户,单次最多50个""" + global multi_account_mgr + success_count, errors = _bulk_update_account_disabled_status( + account_ids, False, multi_account_mgr + ) + # 重置运行时错误状态 + for account_id in account_ids: + if account_id in multi_account_mgr.accounts: + account_mgr = multi_account_mgr.accounts[account_id] + account_mgr.quota_cooldowns = {} + return {"status": "success", "success_count": success_count, "errors": errors} + +@app.put("/admin/accounts/bulk-disable") +@require_login() +async def admin_bulk_disable_accounts(request: Request, account_ids: list[str]): + """批量禁用账户,单次最多50个""" + global multi_account_mgr + success_count, errors = _bulk_update_account_disabled_status( + account_ids, True, multi_account_mgr + ) + return {"status": "success", "success_count": success_count, "errors": errors} + +# ---------- Auth endpoints (API) ---------- +@app.get("/admin/settings") +@require_login() +async def admin_get_settings(request: Request): + """获取系统设置""" + # 返回当前配置(转换为字典格式) + return { + "basic": { + "api_key": config.basic.api_key, + "base_url": config.basic.base_url, + "proxy_for_auth": config.basic.proxy_for_auth, + "proxy_for_chat": config.basic.proxy_for_chat, + "duckmail_base_url": config.basic.duckmail_base_url, + "duckmail_api_key": config.basic.duckmail_api_key, + "duckmail_verify_ssl": config.basic.duckmail_verify_ssl, + "temp_mail_provider": config.basic.temp_mail_provider, + "moemail_base_url": config.basic.moemail_base_url, + "moemail_api_key": config.basic.moemail_api_key, + "moemail_domain": config.basic.moemail_domain, + "freemail_base_url": config.basic.freemail_base_url, + "freemail_jwt_token": config.basic.freemail_jwt_token, + "freemail_verify_ssl": config.basic.freemail_verify_ssl, + "freemail_domain": config.basic.freemail_domain, + "mail_proxy_enabled": config.basic.mail_proxy_enabled, + "gptmail_base_url": config.basic.gptmail_base_url, + "gptmail_api_key": config.basic.gptmail_api_key, + "gptmail_verify_ssl": config.basic.gptmail_verify_ssl, + "gptmail_domain": config.basic.gptmail_domain, + "cfmail_base_url": config.basic.cfmail_base_url, + "cfmail_api_key": config.basic.cfmail_api_key, + "cfmail_verify_ssl": config.basic.cfmail_verify_ssl, + "cfmail_domain": config.basic.cfmail_domain, + "browser_engine": config.basic.browser_engine, + "browser_headless": config.basic.browser_headless, + "refresh_window_hours": config.basic.refresh_window_hours, + "register_default_count": config.basic.register_default_count, + "register_domain": config.basic.register_domain, + "image_expire_hours": config.basic.image_expire_hours, + }, + "image_generation": { + "enabled": config.image_generation.enabled, + "supported_models": config.image_generation.supported_models, + "output_format": config.image_generation.output_format + }, + "video_generation": { + "output_format": config.video_generation.output_format + }, + "retry": { + "max_account_switch_tries": config.retry.max_account_switch_tries, + "text_rate_limit_cooldown_seconds": config.retry.text_rate_limit_cooldown_seconds, + "images_rate_limit_cooldown_seconds": config.retry.images_rate_limit_cooldown_seconds, + "videos_rate_limit_cooldown_seconds": config.retry.videos_rate_limit_cooldown_seconds, + "session_cache_ttl_seconds": config.retry.session_cache_ttl_seconds, + "auto_refresh_accounts_seconds": config.retry.auto_refresh_accounts_seconds, + "scheduled_refresh_enabled": config.retry.scheduled_refresh_enabled, + "scheduled_refresh_interval_minutes": config.retry.scheduled_refresh_interval_minutes, + "scheduled_refresh_cron": config.retry.scheduled_refresh_cron, + "refresh_batch_size": config.retry.refresh_batch_size, + "refresh_batch_interval_minutes": config.retry.refresh_batch_interval_minutes, + "refresh_cooldown_hours": config.retry.refresh_cooldown_hours, + }, + "quota_limits": { + "enabled": config.quota_limits.enabled, + "text_daily_limit": config.quota_limits.text_daily_limit, + "images_daily_limit": config.quota_limits.images_daily_limit, + "videos_daily_limit": config.quota_limits.videos_daily_limit + }, + "public_display": { + "logo_url": config.public_display.logo_url, + "chat_url": config.public_display.chat_url + }, + "session": { + "expire_hours": config.session.expire_hours + } + } + +@app.put("/admin/settings") +@require_login() +async def admin_update_settings(request: Request, new_settings: dict = Body(...)): + """更新系统设置""" + global API_KEY, PROXY_FOR_AUTH, PROXY_FOR_CHAT, BASE_URL, LOGO_URL, CHAT_URL + global IMAGE_GENERATION_ENABLED, IMAGE_GENERATION_MODELS + global MAX_ACCOUNT_SWITCH_TRIES + global RETRY_POLICY + global SESSION_CACHE_TTL_SECONDS, AUTO_REFRESH_ACCOUNTS_SECONDS + global SESSION_EXPIRE_HOURS, multi_account_mgr, http_client, http_client_chat, http_client_auth + + try: + basic = dict(new_settings.get("basic") or {}) + basic.setdefault("duckmail_base_url", config.basic.duckmail_base_url) + basic.setdefault("duckmail_api_key", config.basic.duckmail_api_key) + basic.setdefault("duckmail_verify_ssl", config.basic.duckmail_verify_ssl) + basic.setdefault("temp_mail_provider", config.basic.temp_mail_provider) + basic.setdefault("moemail_base_url", config.basic.moemail_base_url) + basic.setdefault("moemail_api_key", config.basic.moemail_api_key) + basic.setdefault("moemail_domain", config.basic.moemail_domain) + basic.setdefault("freemail_base_url", config.basic.freemail_base_url) + basic.setdefault("freemail_jwt_token", config.basic.freemail_jwt_token) + basic.setdefault("freemail_verify_ssl", config.basic.freemail_verify_ssl) + basic.setdefault("freemail_domain", config.basic.freemail_domain) + basic.setdefault("mail_proxy_enabled", config.basic.mail_proxy_enabled) + basic.setdefault("gptmail_base_url", config.basic.gptmail_base_url) + basic.setdefault("gptmail_api_key", config.basic.gptmail_api_key) + basic.setdefault("gptmail_verify_ssl", config.basic.gptmail_verify_ssl) + basic.setdefault("gptmail_domain", config.basic.gptmail_domain) + basic.setdefault("cfmail_base_url", config.basic.cfmail_base_url) + basic.setdefault("cfmail_api_key", config.basic.cfmail_api_key) + basic.setdefault("cfmail_verify_ssl", config.basic.cfmail_verify_ssl) + basic.setdefault("cfmail_domain", config.basic.cfmail_domain) + basic.setdefault("browser_engine", config.basic.browser_engine) + basic.setdefault("browser_headless", config.basic.browser_headless) + basic.setdefault("refresh_window_hours", config.basic.refresh_window_hours) + basic.setdefault("register_default_count", config.basic.register_default_count) + basic.setdefault("register_domain", config.basic.register_domain) + basic.setdefault("image_expire_hours", config.basic.image_expire_hours) + if not isinstance(basic.get("register_domain"), str): + basic["register_domain"] = "" + basic.pop("duckmail_proxy", None) + new_settings["basic"] = basic + + image_generation = dict(new_settings.get("image_generation") or {}) + output_format = str(image_generation.get("output_format") or config_manager.image_output_format).lower() + if output_format not in ("base64", "url"): + output_format = "base64" + image_generation["output_format"] = output_format + new_settings["image_generation"] = image_generation + + video_generation = dict(new_settings.get("video_generation") or {}) + video_output_format = str(video_generation.get("output_format") or config_manager.video_output_format).lower() + if video_output_format not in ("html", "url", "markdown"): + video_output_format = "html" + video_generation["output_format"] = video_output_format + new_settings["video_generation"] = video_generation + + retry = dict(new_settings.get("retry") or {}) + retry.setdefault("auto_refresh_accounts_seconds", config.retry.auto_refresh_accounts_seconds) + retry.setdefault("scheduled_refresh_enabled", config.retry.scheduled_refresh_enabled) + retry.setdefault("scheduled_refresh_interval_minutes", config.retry.scheduled_refresh_interval_minutes) + retry.setdefault("text_rate_limit_cooldown_seconds", config.retry.text_rate_limit_cooldown_seconds) + retry.setdefault("images_rate_limit_cooldown_seconds", config.retry.images_rate_limit_cooldown_seconds) + retry.setdefault("videos_rate_limit_cooldown_seconds", config.retry.videos_rate_limit_cooldown_seconds) + new_settings["retry"] = retry + + # 配额上限配置 + quota_limits = dict(new_settings.get("quota_limits") or {}) + quota_limits.setdefault("enabled", config.quota_limits.enabled) + quota_limits.setdefault("text_daily_limit", config.quota_limits.text_daily_limit) + quota_limits.setdefault("images_daily_limit", config.quota_limits.images_daily_limit) + quota_limits.setdefault("videos_daily_limit", config.quota_limits.videos_daily_limit) + new_settings["quota_limits"] = quota_limits + + # 保存旧配置用于对比 + old_proxy_for_auth = PROXY_FOR_AUTH + old_proxy_for_chat = PROXY_FOR_CHAT + old_retry_config = { + "text_rate_limit_cooldown_seconds": RETRY_POLICY.cooldowns.text, + "images_rate_limit_cooldown_seconds": RETRY_POLICY.cooldowns.images, + "videos_rate_limit_cooldown_seconds": RETRY_POLICY.cooldowns.videos, + "session_cache_ttl_seconds": SESSION_CACHE_TTL_SECONDS + } + + # 保存到 YAML + config_manager.save_yaml(new_settings) + + # 热更新配置 + config_manager.reload() + + # 更新全局变量(实时生效) + API_KEY = config.basic.api_key + _proxy_auth, _no_proxy_auth = parse_proxy_setting(config.basic.proxy_for_auth) + _proxy_chat, _no_proxy_chat = parse_proxy_setting(config.basic.proxy_for_chat) + PROXY_FOR_AUTH = _proxy_auth + PROXY_FOR_CHAT = _proxy_chat + _NO_PROXY = ",".join(filter(None, {_no_proxy_auth, _no_proxy_chat})) + if _NO_PROXY: + os.environ["NO_PROXY"] = _NO_PROXY + else: + os.environ.pop("NO_PROXY", None) + BASE_URL = config.basic.base_url + LOGO_URL = config.public_display.logo_url + CHAT_URL = config.public_display.chat_url + IMAGE_GENERATION_ENABLED = config.image_generation.enabled + IMAGE_GENERATION_MODELS = config.image_generation.supported_models + MAX_ACCOUNT_SWITCH_TRIES = config.retry.max_account_switch_tries + RETRY_POLICY = build_retry_policy() + SESSION_CACHE_TTL_SECONDS = config.retry.session_cache_ttl_seconds + AUTO_REFRESH_ACCOUNTS_SECONDS = config.retry.auto_refresh_accounts_seconds + SESSION_EXPIRE_HOURS = config.session.expire_hours + + # 检查是否需要重建 HTTP 客户端(代理变化) + if old_proxy_for_auth != PROXY_FOR_AUTH or old_proxy_for_chat != PROXY_FOR_CHAT: + logger.info(f"[CONFIG] Proxy configuration changed, rebuilding HTTP clients") + await http_client.aclose() + await http_client_chat.aclose() + await http_client_auth.aclose() + + # 重新创建对话客户端 + http_client = httpx.AsyncClient( + proxy=(PROXY_FOR_CHAT or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) + ) + + # 重新创建对话流式客户端 + http_client_chat = httpx.AsyncClient( + proxy=(PROXY_FOR_CHAT or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) + ) + + # 重新创建账户操作客户端 + http_client_auth = httpx.AsyncClient( + proxy=(PROXY_FOR_AUTH or None), + verify=False, + http2=False, + timeout=httpx.Timeout(TIMEOUT_SECONDS, connect=60.0), + limits=httpx.Limits( + max_keepalive_connections=100, + max_connections=200 + ) + ) + + # 打印新的代理配置 + logger.info(f"[PROXY] Account operations (register/login/refresh): {PROXY_FOR_AUTH if PROXY_FOR_AUTH else 'disabled'}") + logger.info(f"[PROXY] Chat operations (JWT/session/messages): {PROXY_FOR_CHAT if PROXY_FOR_CHAT else 'disabled'}") + + # 更新所有账户的 http_client 引用(对话用) + multi_account_mgr.update_http_client(http_client) + + # 更新注册/登录服务的 http_client 引用(账户操作用) + if register_service: + register_service.http_client = http_client_auth + if login_service: + login_service.http_client = http_client_auth + + # 检查是否需要更新账户管理器配置(重试策略变化) + retry_changed = ( + old_retry_config["text_rate_limit_cooldown_seconds"] != RETRY_POLICY.cooldowns.text or + old_retry_config["images_rate_limit_cooldown_seconds"] != RETRY_POLICY.cooldowns.images or + old_retry_config["videos_rate_limit_cooldown_seconds"] != RETRY_POLICY.cooldowns.videos or + old_retry_config["session_cache_ttl_seconds"] != SESSION_CACHE_TTL_SECONDS + ) + + if retry_changed: + logger.info(f"[CONFIG] 重试策略已变化,更新账户管理器配置") + # 更新所有账户管理器的配置 + multi_account_mgr.cache_ttl = SESSION_CACHE_TTL_SECONDS + for account_id, account_mgr in multi_account_mgr.accounts.items(): + account_mgr.apply_retry_policy(RETRY_POLICY) + if register_service: + register_service.retry_policy = RETRY_POLICY + if login_service: + login_service.retry_policy = RETRY_POLICY + + logger.info(f"[CONFIG] 系统设置已更新并实时生效") + return {"status": "success", "message": "设置已保存并实时生效!"} + except Exception as e: + logger.error(f"[CONFIG] 更新设置失败: {str(e)}") + raise HTTPException(500, f"更新失败: {str(e)}") + +@app.get("/admin/log") +@require_login() +async def admin_get_logs( + request: Request, + limit: int = 300, + level: str = None, + search: str = None, + start_time: str = None, + end_time: str = None +): + with log_lock: + logs = list(log_buffer) + + stats_by_level = {} + error_logs = [] + chat_count = 0 + for log in logs: + level_name = log.get("level", "INFO") + stats_by_level[level_name] = stats_by_level.get(level_name, 0) + 1 + if level_name in ["ERROR", "CRITICAL"]: + error_logs.append(log) + if "收到请求" in log.get("message", ""): + chat_count += 1 + + if level: + level = level.upper() + logs = [log for log in logs if log["level"] == level] + if search: + logs = [log for log in logs if search.lower() in log["message"].lower()] + if start_time: + logs = [log for log in logs if log["time"] >= start_time] + if end_time: + logs = [log for log in logs if log["time"] <= end_time] + + limit = min(limit, log_buffer.maxlen) + filtered_logs = logs[-limit:] + + return { + "total": len(filtered_logs), + "limit": limit, + "filters": {"level": level, "search": search, "start_time": start_time, "end_time": end_time}, + "logs": filtered_logs, + "stats": { + "memory": {"total": len(log_buffer), "by_level": stats_by_level, "capacity": log_buffer.maxlen}, + "errors": {"count": len(error_logs), "recent": error_logs[-10:]}, + "chat_count": chat_count + } + } + +@app.delete("/admin/log") +@require_login() +async def admin_clear_logs(request: Request, confirm: str = None): + if confirm != "yes": + raise HTTPException(400, "需要 confirm=yes 参数确认清空操作") + with log_lock: + cleared_count = len(log_buffer) + log_buffer.clear() + logger.info("[LOG] 日志已清空") + return {"status": "success", "message": "已清空内存日志", "cleared_count": cleared_count} + +@app.get("/admin/task-history") +@require_login() +async def admin_get_task_history(request: Request, limit: int = 100): + """获取任务历史记录""" + _load_task_history() + with task_history_lock: + history = list(task_history) + + live_entries = [] + try: + if register_service: + current_register = register_service.get_current_task() + if current_register and current_register.status in ("running", "pending"): + live_entries.append(_build_history_entry("register", current_register.to_dict(), is_live=True)) + if login_service: + current_login = login_service.get_current_task() + if current_login and current_login.status in ("running", "pending"): + live_entries.append(_build_history_entry("login", current_login.to_dict(), is_live=True)) + except Exception as exc: + logger.warning(f"[HISTORY] build live entries failed: {exc}") + + merged = {} + for entry in live_entries + history: + entry_id = entry.get("id") or str(uuid.uuid4()) + if entry_id not in merged: + merged[entry_id] = entry + + # 按创建时间倒序排序 + history = list(merged.values()) + history.sort(key=lambda x: x.get("created_at", 0), reverse=True) + + # 限制返回数量 + limit = min(limit, 100) + return { + "total": len(history), + "limit": limit, + "history": history[:limit] + } + +@app.delete("/admin/task-history") +@require_login() +async def admin_clear_task_history(request: Request, confirm: str = None): + """清空任务历史记录""" + if confirm != "yes": + raise HTTPException(400, "需要 confirm=yes 参数确认清空操作") + with task_history_lock: + cleared_count = len(task_history) + task_history.clear() + _persist_task_history() + logger.info("[HISTORY] 任务历史已清空") + return {"status": "success", "message": "已清空任务历史", "cleared_count": cleared_count} + +# ---------- Auth endpoints (API) ---------- + +@app.get("/v1/models") +async def list_models(authorization: str = Header(None)): + data = [] + now = int(time.time()) + for m in MODEL_MAPPING.keys(): + data.append({"id": m, "object": "model", "created": now, "owned_by": "google", "permission": []}) + data.append({"id": "gemini-imagen", "object": "model", "created": now, "owned_by": "google", "permission": []}) + data.append({"id": "gemini-veo", "object": "model", "created": now, "owned_by": "google", "permission": []}) + return {"object": "list", "data": data} + +@app.get("/v1/models/{model_id}") +async def get_model(model_id: str, authorization: str = Header(None)): + return {"id": model_id, "object": "model"} + +# ---------- Auth endpoints (API) ---------- + +@app.post("/v1/chat/completions") +async def chat( + req: ChatRequest, + request: Request, + authorization: Optional[str] = Header(None) +): + # API Key 验证 + verify_api_key(API_KEY, authorization) + # ... (保留原有的chat逻辑) + return await chat_impl(req, request, authorization) + +# chat实现函数 +async def chat_impl( + req: ChatRequest, + request: Request, + authorization: Optional[str] +): + # 生成请求ID(最优先,用于所有日志追踪) + request_id = str(uuid.uuid4())[:6] + + start_ts = time.time() + request.state.first_response_time = None + message_count = len(req.messages) + + monitor_recorded = False + account_manager: Optional[AccountManager] = None + + async def finalize_result( + status: str, + status_code: Optional[int] = None, + error_detail: Optional[str] = None + ) -> None: + nonlocal monitor_recorded + if monitor_recorded: + return + monitor_recorded = True + duration_s = time.time() - start_ts + latency_ms = None + first_response_time = getattr(request.state, "first_response_time", None) + if first_response_time: + latency_ms = int((first_response_time - start_ts) * 1000) + else: + latency_ms = int(duration_s * 1000) + + uptime_tracker.record_request("api_service", status == "success", latency_ms, status_code) + + entry = build_recent_conversation_entry( + request_id=request_id, + model=req.model if req else None, + message_count=message_count, + start_ts=start_ts, + status=status, + duration_s=duration_s if status == "success" else None, + error_detail=error_detail, + ) + + async with stats_lock: + global_stats.setdefault("failure_timestamps", []) + global_stats.setdefault("rate_limit_timestamps", []) + global_stats.setdefault("recent_conversations", []) + global_stats.setdefault("success_count", 0) + global_stats.setdefault("failed_count", 0) + global_stats.setdefault("account_conversations", {}) + global_stats.setdefault("account_failures", {}) + global_stats.setdefault("response_times", deque(maxlen=10000)) + + # 记录响应时间(只记录成功的请求) + if status == "success" and latency_ms is not None: + # 记录首响时间和完成时间,按模型分类 + ttfb_ms = int((first_response_time - start_ts) * 1000) if first_response_time else latency_ms + total_ms = int((time.time() - start_ts) * 1000) + model_name = req.model if req else "unknown" + + global_stats["response_times"].append({ + "timestamp": time.time(), + "ttfb_ms": ttfb_ms, # 首响时间 + "total_ms": total_ms, # 完成时间 + "model": model_name # 模型名称 + }) + + # 写入数据库 + asyncio.create_task(stats_db.insert_request_log( + timestamp=time.time(), + model=model_name, + ttfb_ms=ttfb_ms, + total_ms=total_ms, + status=status, + status_code=status_code + )) + elif status != "success": + # 失败请求也记录到数据库 + model_name = req.model if req else "unknown" + asyncio.create_task(stats_db.insert_request_log( + timestamp=time.time(), + model=model_name, + ttfb_ms=None, + total_ms=None, + status=status, + status_code=status_code + )) + + if status != "success": + global_stats["failed_count"] += 1 + global_stats["failure_timestamps"].append(time.time()) + if status_code == 429: + global_stats["rate_limit_timestamps"].append(time.time()) + failure_account_id = None + if account_manager: + account_manager.failure_count += 1 + failure_account_id = account_manager.config.account_id + global_stats["account_failures"][failure_account_id] = account_manager.failure_count + else: + failure_account_id = getattr(request.state, "last_account_id", None) + if failure_account_id and failure_account_id in multi_account_mgr.accounts: + account_mgr = multi_account_mgr.accounts[failure_account_id] + account_mgr.failure_count += 1 + global_stats["account_failures"][failure_account_id] = account_mgr.failure_count + elif failure_account_id: + global_stats["account_failures"][failure_account_id] = ( + global_stats["account_failures"].get(failure_account_id, 0) + 1 + ) + else: + global_stats["success_count"] += 1 + if account_manager: + global_stats["account_conversations"][account_manager.config.account_id] = account_manager.conversation_count + global_stats["recent_conversations"].append(entry) + global_stats["recent_conversations"] = global_stats["recent_conversations"][-60:] + await save_stats(global_stats) + + def classify_error_status(status_code: Optional[int], error: Exception) -> str: + if status_code == 504: + return "timeout" + if isinstance(error, (asyncio.TimeoutError, httpx.TimeoutException)): + return "timeout" + return "error" + + + # 获取客户端IP(用于会话隔离) + client_ip = request.headers.get("x-forwarded-for") + if client_ip: + client_ip = client_ip.split(",")[0].strip() + else: + client_ip = request.client.host if request.client else "unknown" + + # 记录请求统计 + async with stats_lock: + timestamp = time.time() + global_stats["total_requests"] += 1 + global_stats["request_timestamps"].append(timestamp) + global_stats.setdefault("model_request_timestamps", {}) + global_stats["model_request_timestamps"].setdefault(req.model, []).append(timestamp) + await save_stats(global_stats) + + # 2. 模型校验 + + if req.model not in MODEL_MAPPING and req.model not in VIRTUAL_MODELS: + logger.error(f"[CHAT] [req_{request_id}] 不支持的模型: {req.model}") + all_models = list(MODEL_MAPPING.keys()) + list(VIRTUAL_MODELS.keys()) + await finalize_result("error", 404, f"HTTP 404: Model '{req.model}' not found") + raise HTTPException( + status_code=404, + detail=f"Model '{req.model}' not found. Available models: {all_models}" + ) + + # 保存模型信息到 request.state(用于 Uptime 追踪) + request.state.model = req.model + + required_quota_types = get_required_quota_types(req.model) + + # 3. 生成会话指纹,获取Session锁(防止同一对话的并发请求冲突) + conv_key = get_conversation_key([m.model_dump() for m in req.messages], client_ip) + session_lock = await multi_account_mgr.acquire_session_lock(conv_key) + + # 4. 在锁的保护下检查缓存和处理Session(保证同一对话的请求串行化) + async with session_lock: + cached_session = multi_account_mgr.global_session_cache.get(conv_key) + + if cached_session: + # 使用已绑定的账户 + account_id = cached_session["account_id"] + try: + account_manager = await multi_account_mgr.get_account(account_id, request_id, required_quota_types) + google_session = cached_session["session_id"] + is_new_conversation = False + request.state.last_account_id = account_manager.config.account_id + logger.info(f"[CHAT] [{account_id}] [req_{request_id}] 继续会话: {google_session[-12:]}") + except HTTPException as e: + logger.warning( + f"[CHAT] [req_{request_id}] 缓存会话账户不可用,切换新账户: {account_id} ({str(e.detail)})" + ) + multi_account_mgr.global_session_cache.pop(conv_key, None) + cached_session = None + + if not cached_session: + # 新对话:尝试创建会话(遇到错误就切换账户) + available_accounts = multi_account_mgr.get_available_accounts(required_quota_types) + max_retries = min(MAX_ACCOUNT_SWITCH_TRIES, len(available_accounts)) + last_error = None + + for retry_idx in range(max_retries): + try: + account_manager = await multi_account_mgr.get_account(None, request_id, required_quota_types) + google_session = await create_google_session(account_manager, http_client, USER_AGENT, request_id) + # 线程安全地绑定账户到此对话 + await multi_account_mgr.set_session_cache( + conv_key, + account_manager.config.account_id, + google_session + ) + is_new_conversation = True + request.state.last_account_id = account_manager.config.account_id + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 新会话创建并绑定账户") + # 记录账号池状态(账户可用) + uptime_tracker.record_request("account_pool", True) + break + except Exception as e: + last_error = e + error_type = type(e).__name__ + # 安全获取账户ID + account_id = account_manager.config.account_id if 'account_manager' in locals() and account_manager else 'unknown' + logger.error(f"[CHAT] [req_{request_id}] 账户 {account_id} 创建会话失败 (尝试 {retry_idx + 1}/{max_retries}) - {error_type}: {str(e)}") + # 记录账号池状态(单个账户失败) + status_code = e.status_code if isinstance(e, HTTPException) else None + uptime_tracker.record_request("account_pool", False, status_code=status_code) + + # 注意:会话创建失败不触发冷却,直接切换到下一个账户重试 + # 网络抖动、超时等临时问题不应标记配额冷却 + + if retry_idx == max_retries - 1: + logger.error(f"[CHAT] [req_{request_id}] 所有账户均不可用") + status = classify_error_status(503, last_error if isinstance(last_error, Exception) else Exception("account_pool_unavailable")) + await finalize_result(status, 503, f"All accounts unavailable: {str(last_error)[:100]}") + raise HTTPException(503, f"All accounts unavailable: {str(last_error)[:100]}") + # 继续尝试下一个账户 + + # 确保 account_manager 已成功获取 + if account_manager is None: + logger.error(f"[CHAT] [req_{request_id}] 无可用账户") + await finalize_result("error", 503, "No available accounts") + raise HTTPException(503, "No available accounts") + + # 提取用户消息内容用于日志 + if req.messages: + last_content = req.messages[-1].content + if isinstance(last_content, str): + # 显示完整消息,但限制在500字符以内 + if len(last_content) > 500: + preview = last_content[:500] + "...(已截断)" + else: + preview = last_content + else: + preview = f"[多模态: {len(last_content)}部分]" + else: + preview = "[空消息]" + + # 记录请求基本信息 + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 收到请求: {req.model} | {len(req.messages)}条消息 | stream={req.stream}") + + # 单独记录用户消息内容(方便查看) + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 用户消息: {preview}") + + # 3. 解析请求内容 + try: + last_text, current_images = await parse_last_message(req.messages, http_client, request_id) + except HTTPException as e: + status = classify_error_status(e.status_code, e) + await finalize_result(status, e.status_code, f"HTTP {e.status_code}: {e.detail}") + raise + except Exception as e: + status = classify_error_status(None, e) + await finalize_result(status, 500, f"{type(e).__name__}: {str(e)[:200]}") + raise + + # 4. 准备文本内容 + if is_new_conversation: + # 新对话只发送最后一条 + text_to_send = last_text + is_retry_mode = True + else: + # 继续对话只发送当前消息 + text_to_send = last_text + is_retry_mode = False + # 线程安全地更新时间戳 + await multi_account_mgr.update_session_time(conv_key) + + chat_id = f"chatcmpl-{uuid.uuid4()}" + created_time = int(time.time()) + + # 封装生成器 (含图片上传和重试逻辑) + async def response_wrapper(): + nonlocal account_manager # 允许修改外层的 account_manager + + # 单层重试循环:遇到错误就切换账户 + available_accounts = multi_account_mgr.get_available_accounts(required_quota_types) + max_retries = min(MAX_ACCOUNT_SWITCH_TRIES, len(available_accounts)) + + current_text = text_to_send + current_retry_mode = is_retry_mode + current_file_ids = [] + + for retry_idx in range(max_retries): + try: + # 获取或创建 Session + cached = multi_account_mgr.global_session_cache.get(conv_key) + if not cached: + logger.warning(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 缓存已清理,重建Session") + new_sess = await create_google_session(account_manager, http_client, USER_AGENT, request_id) + await multi_account_mgr.set_session_cache( + conv_key, + account_manager.config.account_id, + new_sess + ) + current_session = new_sess + current_retry_mode = True + current_file_ids = [] + else: + current_session = cached["session_id"] + + # 上传图片(如果需要) + if current_images and not current_file_ids: + for img in current_images: + fid = await upload_context_file(current_session, img["mime"], img["data"], account_manager, http_client, USER_AGENT, request_id) + current_file_ids.append(fid) + + # 准备文本(重试模式下发全文) + if current_retry_mode: + current_text = build_full_context_text(req.messages) + + # 发起对话 + async for chunk in stream_chat_generator( + current_session, + current_text, + current_file_ids, + req.model, + chat_id, + created_time, + account_manager, + req.stream, + request_id, + request + ): + yield chunk + + if getattr(request.state, "first_response_time", None) is None: + # 空响应应该触发重试逻辑 + raise HTTPException(status_code=502, detail="Empty response from upstream") + + # 请求成功(conversation_count 已在生成器内统计) + uptime_tracker.record_request("account_pool", True) + await finalize_result("success", 200, None) + break + + except (httpx.HTTPError, ssl.SSLError, HTTPException) as e: + # 提取错误信息 + is_http_exception = isinstance(e, HTTPException) + status_code = e.status_code if is_http_exception else None + error_detail = ( + f"HTTP {e.status_code}: {e.detail}" + if is_http_exception + else f"{type(e).__name__}: {str(e)[:200]}" + ) + + # 记录账号池状态(请求失败) + uptime_tracker.record_request("account_pool", False, status_code=status_code) + + # 判断请求类型以传递 quota_type + quota_type = get_request_quota_type(req.model) + + # 使用统一的错误处理入口 + # 注意:502 空响应错误不触发冷却,只切换账户重试 + if is_http_exception: + if status_code == 502: + logger.warning(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 上游 502 错误,切换账户重试(不触发冷却)") + else: + account_manager.handle_http_error(status_code, str(e.detail) if hasattr(e, 'detail') else "", request_id, quota_type) + else: + account_manager.handle_non_http_error("聊天请求", request_id, quota_type) + + # 检查是否还能继续重试 + if retry_idx < max_retries - 1: + logger.warning(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 切换账户重试 ({retry_idx + 1}/{max_retries})") + + # 尝试切换到其他账户 + try: + new_account = await multi_account_mgr.get_account(None, request_id, required_quota_types) + logger.info(f"[CHAT] [req_{request_id}] 切换账户: {account_manager.config.account_id} -> {new_account.config.account_id}") + + # 创建新 Session + new_sess = await create_google_session(new_account, http_client, USER_AGENT, request_id) + + # 更新缓存绑定到新账户 + await multi_account_mgr.set_session_cache( + conv_key, + new_account.config.account_id, + new_sess + ) + + # 更新账户管理器 + account_manager = new_account + request.state.last_account_id = account_manager.config.account_id + + # 设置重试模式(发送完整上下文) + current_retry_mode = True + current_file_ids = [] # 清空 ID,强制重新上传到新 Session + + except Exception as create_err: + error_type = type(create_err).__name__ + logger.error(f"[CHAT] [req_{request_id}] 账户切换失败 ({error_type}): {str(create_err)}") + # 记录账号池状态(账户切换失败) + status_code = create_err.status_code if isinstance(create_err, HTTPException) else None + uptime_tracker.record_request("account_pool", False, status_code=status_code) + + status = classify_error_status(status_code, create_err) + await finalize_result(status, status_code, f"Account Failover Failed: {str(create_err)[:200]}") + if req.stream: yield f"data: {json.dumps({'error': {'message': 'Account Failover Failed'}})}\n\n" + return + else: + # 已达到最大重试次数 + logger.error(f"[CHAT] [req_{request_id}] 已达到最大重试次数 ({max_retries}),请求失败") + status = classify_error_status(status_code, e) + await finalize_result(status, status_code, error_detail) + if req.stream: yield f"data: {json.dumps({'error': {'message': f'Max retries ({max_retries}) exceeded: {error_detail}'}})}\n\n" + return + + if req.stream: + return StreamingResponse(response_wrapper(), media_type="text/event-stream") + + full_content = "" + full_reasoning = "" + async for chunk_str in response_wrapper(): + if chunk_str.startswith("data: [DONE]"): break + if chunk_str.startswith("data: "): + try: + data = json.loads(chunk_str[6:]) + delta = data["choices"][0]["delta"] + if "content" in delta: + full_content += delta["content"] + if "reasoning_content" in delta: + full_reasoning += delta["reasoning_content"] + except json.JSONDecodeError as e: + logger.error(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] JSON解析失败: {str(e)}") + except (KeyError, IndexError) as e: + logger.error(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 响应格式错误 ({type(e).__name__}): {str(e)}") + + # 构建响应消息 + message = {"role": "assistant", "content": full_content} + if full_reasoning: + message["reasoning_content"] = full_reasoning + + # 非流式请求完成日志 + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 非流式响应完成") + + # 记录响应内容(限制500字符) + response_preview = full_content[:500] + "...(已截断)" if len(full_content) > 500 else full_content + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] AI响应: {response_preview}") + + return { + "id": chat_id, + "object": "chat.completion", + "created": created_time, + "model": req.model, + "choices": [{"index": 0, "message": message, "finish_reason": "stop"}], + "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0} + } + +# ---------- 图片生成 API (OpenAI 兼容) ---------- +@app.post("/v1/images/generations") +async def generate_images( + req: ImageGenerationRequest, + request: Request, + authorization: Optional[str] = Header(None) +): + """OpenAI 兼容的图片生成接口 + + 将 /v1/images/generations 请求转换为内部格式处理, + 然后将响应转换回 OpenAI 图片生成格式 + """ + # API Key 验证 + verify_api_key(API_KEY, authorization) + + # 生成请求ID + request_id = str(uuid.uuid4())[:6] + + # 转换为 ChatRequest 格式 + chat_req = ChatRequest( + model=req.model, + messages=[ + Message(role="user", content=req.prompt) + ], + stream=False # 图片生成不支持流式 + ) + + logger.info(f"[IMAGE-GEN] [req_{request_id}] 收到图片生成请求: model={req.model}, prompt={req.prompt[:100]}") + + try: + # 调用 chat_impl 获取响应 + chat_response = await chat_impl(chat_req, request, authorization) + + # 从响应中提取图片 + message_content = chat_response["choices"][0]["message"]["content"] + + # 解析 markdown 中的图片 + import re + b64_pattern = r'!\[.*?\]\(data:([^;]+);base64,([^\)]+)\)' + b64_matches = re.findall(b64_pattern, message_content) + url_pattern = r'!\[.*?\]\((https?://[^\)]+)\)' + url_matches = re.findall(url_pattern, message_content) + + # 确定响应格式:始终使用系统配置 + system_format = config_manager.image_output_format + response_format = "b64_json" if system_format == "base64" else "url" + + logger.info(f"[IMAGE-GEN] [req_{request_id}] 使用系统配置: {system_format} -> {response_format}") + + # 构建 OpenAI 格式的响应 + created_time = int(time.time()) + data_list = [] + + if response_format == "b64_json": + # 返回 base64 格式 + for mime, b64_data in b64_matches[:req.n]: + data_list.append({"b64_json": b64_data, "revised_prompt": req.prompt}) + + # 如果没有 base64 但有 URL,下载并转换 + if not data_list and url_matches: + for url in url_matches[:req.n]: + try: + resp = await http_client.get(url) + if resp.status_code == 200: + b64_data = base64.b64encode(resp.content).decode() + data_list.append({"b64_json": b64_data, "revised_prompt": req.prompt}) + except Exception as e: + logger.error(f"[IMAGE-GEN] [req_{request_id}] 下载图片失败: {url}, {str(e)}") + else: + # 返回 URL 格式 + for url in url_matches[:req.n]: + data_list.append({"url": url, "revised_prompt": req.prompt}) + + # 如果没有 URL 但有 base64,保存并生成 URL + if not data_list and b64_matches: + base_url = get_base_url(request) + chat_id = f"img-{uuid.uuid4()}" + for idx, (mime, b64_data) in enumerate(b64_matches[:req.n], 1): + try: + img_data = base64.b64decode(b64_data) + file_id = f"gen-{uuid.uuid4()}" + url = save_image_to_hf(img_data, chat_id, file_id, mime, base_url, IMAGE_DIR) + data_list.append({"url": url, "revised_prompt": req.prompt}) + except Exception as e: + logger.error(f"[IMAGE-GEN] [req_{request_id}] 保存图片失败: {str(e)}") + + logger.info(f"[IMAGE-GEN] [req_{request_id}] 图片生成完成: {len(data_list)}张") + + return {"created": created_time, "data": data_list} + + except Exception as e: + logger.error(f"[IMAGE-GEN] [req_{request_id}] 图片生成失败: {type(e).__name__}: {str(e)}") + raise + +# ---------- 图片编辑 API (OpenAI 兼容 - 图生图) ---------- +@app.post("/v1/images/edits") +async def edit_images( + request: Request, + image: UploadFile = File(..., description="要编辑的原始图片"), + prompt: str = Form(..., description="编辑描述"), + model: str = Form("gemini-imagen"), + n: int = Form(1), + size: str = Form("1024x1024"), + response_format: Optional[str] = Form(None), + mask: Optional[UploadFile] = File(None, description="遮罩图片(可选)"), + authorization: Optional[str] = Header(None), +): + """OpenAI 兼容的图片编辑接口(图生图) + + 接收上传的图片和编辑描述,将其转换为多模态 ChatRequest, + 调用 chat_impl 处理,然后将响应转换回 OpenAI 图片格式。 + """ + # API Key 验证 + verify_api_key(API_KEY, authorization) + + # 生成请求ID + request_id = str(uuid.uuid4())[:6] + + try: + # 读取上传的图片 + image_bytes = await image.read() + image_b64 = base64.b64encode(image_bytes).decode() + mime_type = image.content_type or "image/png" + data_uri = f"data:{mime_type};base64,{image_b64}" + + logger.info( + f"[IMAGE-EDIT] [req_{request_id}] 收到图片编辑请求: " + f"model={model}, image_size={len(image_bytes)} bytes, " + f"mime={mime_type}, prompt={prompt[:100]}" + ) + + # 构造多模态消息内容(图片 + 文本) + content_parts = [ + {"type": "image_url", "image_url": {"url": data_uri}}, + {"type": "text", "text": prompt}, + ] + + # 如果有 mask,也加入消息 + if mask: + mask_bytes = await mask.read() + mask_b64 = base64.b64encode(mask_bytes).decode() + mask_mime = mask.content_type or "image/png" + mask_uri = f"data:{mask_mime};base64,{mask_b64}" + content_parts.insert(1, {"type": "image_url", "image_url": {"url": mask_uri}}) + logger.info(f"[IMAGE-EDIT] [req_{request_id}] 包含遮罩图片: {len(mask_bytes)} bytes") + + # 构造 ChatRequest + chat_req = ChatRequest( + model=model, + messages=[ + Message(role="user", content=content_parts) + ], + stream=False # 图片编辑不支持流式 + ) + + # 调用 chat_impl 获取响应 + chat_response = await chat_impl(chat_req, request, authorization) + + # 从响应中提取图片(复用 /v1/images/generations 的逻辑) + message_content = chat_response["choices"][0]["message"]["content"] + + b64_pattern = r'!\[.*?\]\(data:([^;]+);base64,([^\)]+)\)' + b64_matches = re.findall(b64_pattern, message_content) + url_pattern = r'!\[.*?\]\((https?://[^\)]+)\)' + url_matches = re.findall(url_pattern, message_content) + + # 确定响应格式:使用系统配置 + system_format = config_manager.image_output_format + fmt = "b64_json" if system_format == "base64" else "url" + + logger.info(f"[IMAGE-EDIT] [req_{request_id}] 使用系统配置: {system_format} -> {fmt}") + + # 构建 OpenAI 格式的响应 + created_time = int(time.time()) + data_list = [] + + if fmt == "b64_json": + for mime, b64_data in b64_matches[:n]: + data_list.append({"b64_json": b64_data, "revised_prompt": prompt}) + # 如果没有 base64 但有 URL,下载并转换 + if not data_list and url_matches: + for url in url_matches[:n]: + try: + resp = await http_client.get(url) + if resp.status_code == 200: + b64_data = base64.b64encode(resp.content).decode() + data_list.append({"b64_json": b64_data, "revised_prompt": prompt}) + except Exception as e: + logger.error(f"[IMAGE-EDIT] [req_{request_id}] 下载图片失败: {url}, {str(e)}") + else: + for url in url_matches[:n]: + data_list.append({"url": url, "revised_prompt": prompt}) + # 如果没有 URL 但有 base64,保存并生成 URL + if not data_list and b64_matches: + base_url = get_base_url(request) + chat_id = f"img-edit-{uuid.uuid4()}" + for idx, (mime, b64_data) in enumerate(b64_matches[:n], 1): + try: + img_data = base64.b64decode(b64_data) + file_id = f"edit-{uuid.uuid4()}" + url = save_image_to_hf(img_data, chat_id, file_id, mime, base_url, IMAGE_DIR) + data_list.append({"url": url, "revised_prompt": prompt}) + except Exception as e: + logger.error(f"[IMAGE-EDIT] [req_{request_id}] 保存图片失败: {str(e)}") + + logger.info(f"[IMAGE-EDIT] [req_{request_id}] 图片编辑完成: {len(data_list)}张") + + return {"created": created_time, "data": data_list} + + except Exception as e: + logger.error(f"[IMAGE-EDIT] [req_{request_id}] 图片编辑失败: {type(e).__name__}: {str(e)}") + raise + +# ---------- 图片生成处理函数 ---------- +def parse_images_from_response(data_list: list) -> tuple[list, str]: + """从API响应中解析图片文件引用 + 返回: (file_ids_list, session_name) + file_ids_list: [{"fileId": str, "mimeType": str}, ...] + """ + file_ids = [] + session_name = "" + seen_file_ids = set() # 用于去重 + + for data in data_list: + sar = data.get("streamAssistResponse") + if not sar: + continue + + # 获取session信息(优先使用最新的) + session_info = sar.get("sessionInfo", {}) + if session_info.get("session"): + session_name = session_info["session"] + + answer = sar.get("answer") or {} + replies = answer.get("replies") or [] + + for reply in replies: + gc = reply.get("groundedContent", {}) + content = gc.get("content", {}) + + # 检查file字段(图片生成的关键) + file_info = content.get("file") + if file_info and file_info.get("fileId"): + file_id = file_info["fileId"] + # 去重:同一个 fileId 只处理一次 + if file_id in seen_file_ids: + continue + seen_file_ids.add(file_id) + + mime_type = file_info.get("mimeType", "image/png") + logger.debug(f"[PARSE] 解析文件: fileId={file_id}, mimeType={mime_type}") + file_ids.append({ + "fileId": file_id, + "mimeType": mime_type + }) + + return file_ids, session_name + + +async def stream_chat_generator(session: str, text_content: str, file_ids: List[str], model_name: str, chat_id: str, created_time: int, account_manager: AccountManager, is_stream: bool = True, request_id: str = "", request: Request = None): + start_time = time.time() + full_content = "" + first_response_time = None + usage_counted = False + + # 记录发送给API的内容 + text_preview = text_content[:500] + "...(已截断)" if len(text_content) > 500 else text_content + logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 发送内容: {text_preview}") + if file_ids: + logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 附带文件: {len(file_ids)}个") + + jwt = await account_manager.get_jwt(request_id) + headers = get_common_headers(jwt, USER_AGENT) + + + tools_spec = get_tools_spec(model_name) + + body = { + "configId": account_manager.config.config_id, + "additionalParams": {"token": "-"}, + "streamAssistRequest": { + "session": session, + "query": {"parts": [{"text": text_content}]}, + "filter": "", + "fileIds": file_ids, # 注入文件 ID + "answerGenerationMode": "NORMAL", + "toolsSpec": tools_spec, + "languageCode": "zh-CN", + "userMetadata": {"timeZone": "Asia/Shanghai"}, + "assistSkippingMode": "REQUEST_ASSIST" + } + } + + target_model_id = MODEL_MAPPING.get(model_name) + if target_model_id: + body["streamAssistRequest"]["assistGenerationConfig"] = { + "modelId": target_model_id + } + + if is_stream: + chunk = create_chunk(chat_id, created_time, model_name, {"role": "assistant"}, None) + yield f"data: {chunk}\n\n" + + # 使用流式请求 + json_objects = [] # 收集所有响应对象用于图片解析 + file_ids_info = None # 保存图片信息 + + async with http_client.stream( + "POST", + "https://biz-discoveryengine.googleapis.com/v1alpha/locations/global/widgetStreamAssist", + headers=headers, + json=body, + timeout=300.0, + ) as r: + if r.status_code != 200: + error_text = await r.aread() + uptime_tracker.record_request(model_name, False, status_code=r.status_code) + raise HTTPException(status_code=r.status_code, detail=f"Upstream Error {error_text.decode()}") + + # 使用异步解析器处理 JSON 数组流 + try: + response_count = 0 + async for json_obj in parse_json_array_stream_async(r.aiter_lines()): + response_count += 1 + json_objects.append(json_obj) # 收集响应 + + # 记录原始响应结构(用于调试空响应) + logger.debug(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 收到响应#{response_count}: {json.dumps(json_obj, ensure_ascii=False)[:1000]}") + + # 检查是否有错误或政策违规信息 + if "error" in json_obj: + error_info = json_obj.get("error", {}) + error_code = error_info.get("code", 0) + error_message = error_info.get("message", "") + logger.warning(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 上游返回错误: {json.dumps(error_info, ensure_ascii=False)}") + + # 上游 429 配额耗尽:立即标记冷却并抛异常,触发切换账户 + if error_code == 429 or "RESOURCE_EXHAUSTED" in error_info.get("status", ""): + quota_type = get_request_quota_type(model_name) + account_manager.handle_http_error(429, error_message[:200], request_id, quota_type) + raise HTTPException(status_code=429, detail=f"Upstream quota exhausted: {error_message[:200]}") + + stream_response = json_obj.get("streamAssistResponse", {}) + answer = stream_response.get("answer", {}) + + # 检查是否被政策阻止 + answer_state = answer.get("state", "") + if answer_state == "SKIPPED": + skip_reasons = answer.get("assistSkippedReasons", []) + policy_result = answer.get("customerPolicyEnforcementResult", {}) + + if "CUSTOMER_POLICY_VIOLATION" in skip_reasons: + # 提取具体的违规信息(用于日志) + policy_results = policy_result.get("policyResults", []) + violation_detail = "" + + for policy in policy_results: + armor_result = policy.get("modelArmorEnforcementResult", {}) + if armor_result: + violation_detail = armor_result.get("modelArmorViolation", "") + if violation_detail: + break + + logger.warning(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 内容被安全策略阻止: {violation_detail or 'CUSTOMER_POLICY_VIOLATION'}") + + # 向用户返回官方风格的错误信息 + error_text = "\n⚠️ 违反政策\n\n由于提示违反了 Google 定义的安全政策,因此 Gemini 无法回复。\n\n请修改提示以符合安全政策。\n" + + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + + full_content += error_text + chunk = create_chunk(chat_id, created_time, model_name, {"content": error_text}, None) + yield f"data: {chunk}\n\n" + continue + elif skip_reasons: + # 处理其他跳过原因 + reason_text = ", ".join(skip_reasons) + logger.warning(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 响应被跳过: {reason_text}") + + error_text = f"\n⚠️ 抱歉,无法生成响应。\n\n原因:{reason_text}\n\n请稍后重试或联系管理员。\n" + + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + + full_content += error_text + chunk = create_chunk(chat_id, created_time, model_name, {"content": error_text}, None) + yield f"data: {chunk}\n\n" + continue + + replies = answer.get("replies", []) + + # 记录replies数量 + if not replies: + logger.debug(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 响应#{response_count}无replies,完整answer结构: {json.dumps(answer, ensure_ascii=False)[:500]}") + else: + logger.debug(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 响应#{response_count}包含{len(replies)}个replies") + + # 提取文本内容 + for idx, reply in enumerate(replies): + content_obj = reply.get("groundedContent", {}).get("content", {}) + text = content_obj.get("text", "") + + if not text: + # 记录为什么没有text + logger.debug(f"[API] [{account_manager.config.account_id}] [req_{request_id}] Reply#{idx}无text,content_obj结构: {json.dumps(content_obj, ensure_ascii=False)[:300]}") + continue + + # 首次收到响应时记录时间和计数 + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + if not usage_counted: + usage_counted = True + account_manager.conversation_count += 1 + account_manager.increment_daily_usage(get_request_quota_type(model_name)) + + # 区分思考过程和正常内容 + if content_obj.get("thought"): + # 思考过程使用 reasoning_content 字段(类似 OpenAI o1) + chunk = create_chunk(chat_id, created_time, model_name, {"reasoning_content": text}, None) + yield f"data: {chunk}\n\n" + else: + # 正常内容使用 content 字段 + full_content += text + chunk = create_chunk(chat_id, created_time, model_name, {"content": text}, None) + yield f"data: {chunk}\n\n" + + # 提取图片信息(在 async with 块内) + if json_objects: + file_ids, session_name = parse_images_from_response(json_objects) + if file_ids and session_name: + file_ids_info = (file_ids, session_name) + logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 检测到{len(file_ids)}张生成图片") + + # 记录流处理总结 + logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 流处理完成: 收到{response_count}个响应对象, 累计内容长度{len(full_content)}字符") + if response_count > 0 and len(full_content) == 0: + # 画图/视频请求不产生文本内容,空响应是正常的 + quota_type = get_request_quota_type(model_name) + if quota_type in ("images", "videos"): + logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 媒体生成请求,无文本内容属正常情况") + # 媒体生成成功,计入每日配额(避免重复计数) + if not usage_counted: + usage_counted = True + account_manager.conversation_count += 1 + account_manager.increment_daily_usage(quota_type) + else: + logger.warning(f"[API] [{account_manager.config.account_id}] [req_{request_id}] ⚠️ 空响应警告: 收到{response_count}个响应但无文本内容,可能是思考模型未生成最终回答或上游错误") + # 打印第一个响应对象的完整结构用于调试 + if json_objects: + logger.warning(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 第一个响应完整结构: {json.dumps(json_objects[0], ensure_ascii=False)}") + + # 重置 first_response_time 并抛异常,触发调用方切换账号重试 + if request is not None: + request.state.first_response_time = None + raise HTTPException(status_code=502, detail="Thinking model produced thoughts but no final content") + + + except ValueError as e: + uptime_tracker.record_request(model_name, False) + logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] JSON解析失败: {str(e)}") + except Exception as e: + error_type = type(e).__name__ + uptime_tracker.record_request(model_name, False) + logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 流处理错误 ({error_type}): {str(e)}") + raise + + # 在 async with 块外处理图片下载(避免占用上游连接) + if file_ids_info: + file_ids, session_name = file_ids_info + try: + base_url = get_base_url(request) if request else "" + file_metadata = await get_session_file_metadata(account_manager, session_name, http_client, USER_AGENT, request_id) + + # 并行下载所有图片 + download_tasks = [] + for file_info in file_ids: + fid = file_info["fileId"] + mime = file_info["mimeType"] + meta = file_metadata.get(fid, {}) + # 优先使用 metadata 中的 MIME 类型 + mime = meta.get("mimeType", mime) + correct_session = meta.get("session") or session_name + task = download_image_with_jwt(account_manager, correct_session, fid, http_client, USER_AGENT, request_id) + download_tasks.append((fid, mime, task)) + + results = await asyncio.gather(*[task for _, _, task in download_tasks], return_exceptions=True) + + # 处理下载结果 + success_count = 0 + for idx, ((fid, mime, _), result) in enumerate(zip(download_tasks, results), 1): + if isinstance(result, Exception): + logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}下载失败: {type(result).__name__}: {str(result)[:100]}") + # 降级处理:返回错误提示而不是静默失败 + error_msg = f"\n\n⚠️ 图片 {idx} 下载失败\n\n" + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None) + yield f"data: {chunk}\n\n" + continue + + try: + markdown = process_media(result, mime, chat_id, fid, base_url, idx, request_id, account_manager.config.account_id) + success_count += 1 + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + chunk = create_chunk(chat_id, created_time, model_name, {"content": markdown}, None) + yield f"data: {chunk}\n\n" + except Exception as save_error: + logger.error(f"[MEDIA] [{account_manager.config.account_id}] [req_{request_id}] 媒体{idx}处理失败: {str(save_error)[:100]}") + error_msg = f"\n\n⚠️ 媒体 {idx} 处理失败\n\n" + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None) + yield f"data: {chunk}\n\n" + + logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片处理完成: {success_count}/{len(file_ids)} 成功") + + except Exception as e: + logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片处理失败: {type(e).__name__}: {str(e)[:100]}") + # 降级处理:通知用户图片处理失败 + error_msg = f"\n\n⚠️ 图片处理失败: {type(e).__name__}\n\n" + if first_response_time is None: + first_response_time = time.time() + if request is not None: + request.state.first_response_time = first_response_time + chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None) + yield f"data: {chunk}\n\n" + + if full_content: + response_preview = full_content[:500] + "...(已截断)" if len(full_content) > 500 else full_content + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] AI响应: {response_preview}") + else: + # 画图/视频请求不产生文本内容,空响应是正常的 + quota_type = get_request_quota_type(model_name) + if quota_type in ("images", "videos"): + logger.info(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] 媒体生成请求,文本响应为空属正常情况") + else: + logger.warning(f"[CHAT] [{account_manager.config.account_id}] [req_{request_id}] ⚠️ 最终响应为空,请检查上游日志") + + + if first_response_time: + latency_ms = int((first_response_time - start_time) * 1000) + uptime_tracker.record_request(model_name, True, latency_ms) + else: + uptime_tracker.record_request(model_name, True) + + total_time = time.time() - start_time + logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 响应完成: {total_time:.2f}秒") + + if is_stream: + final_chunk = create_chunk(chat_id, created_time, model_name, {}, "stop") + yield f"data: {final_chunk}\n\n" + yield "data: [DONE]\n\n" + +# ---------- 公开端点(无需认证) ---------- +@app.get("/public/uptime") +async def get_public_uptime(days: int = 90): + """获取 Uptime 监控数据(JSON格式)""" + if days < 1 or days > 90: + days = 90 + return await uptime_tracker.get_uptime_summary(days) + + +@app.get("/public/stats") +async def get_public_stats(): + """获取公开统计信息""" + async with stats_lock: + # 清理1小时前的请求时间戳 + current_time = time.time() + recent_requests = [ + ts for ts in global_stats["request_timestamps"] + if current_time - ts < 3600 + ] + + # 计算每分钟请求数 + recent_minute = [ + ts for ts in recent_requests + if current_time - ts < 60 + ] + requests_per_minute = len(recent_minute) + + # 计算负载状态 + if requests_per_minute < 10: + load_status = "low" + load_color = "#10b981" # 绿色 + elif requests_per_minute < 30: + load_status = "medium" + load_color = "#f59e0b" # 黄色 + else: + load_status = "high" + load_color = "#ef4444" # 红色 + + return { + "total_visitors": global_stats["total_visitors"], + "total_requests": global_stats["total_requests"], + "requests_per_minute": requests_per_minute, + "load_status": load_status, + "load_color": load_color + } + +@app.get("/public/display") +async def get_public_display(): + """获取公开展示信息""" + return { + "logo_url": LOGO_URL, + "chat_url": CHAT_URL + } + +@app.get("/public/log") +async def get_public_logs(request: Request, limit: int = 100): + try: + # 基于IP的访问统计(24小时内去重) + client_ip = request.client.host + current_time = time.time() + + async with stats_lock: + # 清理24小时前的IP记录 + if "visitor_ips" not in global_stats: + global_stats["visitor_ips"] = {} + global_stats["visitor_ips"] = { + ip: timestamp for ip, timestamp in global_stats["visitor_ips"].items() + if current_time - timestamp <= 86400 + } + + # 记录新访问(24小时内同一IP只计数一次) + if client_ip not in global_stats["visitor_ips"]: + global_stats["visitor_ips"][client_ip] = current_time + global_stats["total_visitors"] = global_stats.get("total_visitors", 0) + 1 + + global_stats.setdefault("recent_conversations", []) + await save_stats(global_stats) + + stored_logs = list(global_stats.get("recent_conversations", [])) + + sanitized_logs = get_sanitized_logs(limit=min(limit, 1000)) + + log_map = {log.get("request_id"): log for log in sanitized_logs} + for log in stored_logs: + request_id = log.get("request_id") + if request_id and request_id not in log_map: + log_map[request_id] = log + + def get_log_ts(item: dict) -> float: + if "start_ts" in item: + return float(item["start_ts"]) + try: + return datetime.strptime(item.get("start_time", ""), "%Y-%m-%d %H:%M:%S").timestamp() + except Exception: + return 0.0 + + merged_logs = sorted(log_map.values(), key=get_log_ts, reverse=True)[:min(limit, 1000)] + output_logs = [] + for log in merged_logs: + if "start_ts" in log: + log = dict(log) + log.pop("start_ts", None) + output_logs.append(log) + + return { + "total": len(output_logs), + "logs": output_logs + } + except Exception as e: + logger.error(f"[LOG] 获取公开日志失败: {e}") + return {"total": 0, "logs": [], "error": str(e)} + except Exception as e: + logger.error(f"[LOG] 获取公开日志失败: {e}") + return {"total": 0, "logs": [], "error": str(e)} + +# ---------- 全局 404 处理(必须在最后) ---------- + +@app.exception_handler(404) +async def not_found_handler(request: Request, exc: HTTPException): + """全局 404 处理器""" + return JSONResponse( + status_code=404, + content={"detail": "Not Found"} + ) + +if __name__ == "__main__": + import uvicorn + port = int(os.getenv("PORT", "7860")) + uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d98e7f1a12c65c33902ff754cad9c0fc94eb574 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 +httpx[socks]==0.27.0 +pydantic==2.10.0 +aiofiles==24.1.0 +python-dotenv==1.0.1 +itsdangerous==2.1.2 +python-multipart==0.0.6 +pyyaml>=6.0 +jinja2>=3.1.0 +requests[socks]==2.32.3 +DrissionPage==4.0.5.6 + +# Optional: PostgreSQL database support for environments without persistent storage +# Uncomment the line below and set DATABASE_URL environment variable if needed +asyncpg>=0.29.0 diff --git a/scripts/migrate_to_database.py b/scripts/migrate_to_database.py new file mode 100644 index 0000000000000000000000000000000000000000..0451de901b62d71eb2ea9dede7152d2c0295891a --- /dev/null +++ b/scripts/migrate_to_database.py @@ -0,0 +1,744 @@ +#!/usr/bin/env python3 +""" +数据库迁移脚本 + +用途:将数据从 kv_store 或本地文件迁移到新的数据库表结构 + +迁移内容: + - accounts (账户配置) + - settings (系统设置) + - stats (统计数据) + +使用方法: + python scripts/migrate_to_database.py + +迁移后: + - kv_store 数据保留(作为备份,仅 PostgreSQL) + - 本地文件重命名为 .migrated_YYYYMMDD-HHMMSS(防止重复迁移) + +支持的数据库: + - PostgreSQL:配置 DATABASE_URL 环境变量 + - SQLite:不配置 DATABASE_URL,自动使用 data/data.db +""" + +import asyncio +import json +import os +import sqlite3 +import sys +import time +from pathlib import Path + +# 添加项目根目录到路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from dotenv import load_dotenv +load_dotenv() + + +def rename_migrated_file(file_path: str) -> str: + """重命名已迁移的文件,添加时间戳后缀""" + if not os.path.exists(file_path): + return None + + timestamp = time.strftime("%Y%m%d-%H%M%S") + new_path = f"{file_path}.migrated_{timestamp}" + os.rename(file_path, new_path) + return new_path + + +async def migrate_from_kv_store(conn): + """从 kv_store 迁移数据到新表""" + print("\n" + "=" * 60) + print("从 kv_store 迁移数据") + print("=" * 60) + + # 检查 kv_store 是否存在 + exists = await conn.fetchval( + """ + SELECT EXISTS( + SELECT 1 FROM information_schema.tables + WHERE table_schema='public' AND table_name='kv_store' + ) + """ + ) + + if not exists: + print("❌ kv_store 表不存在,跳过") + return False + + migrated_any = False + + # 1. 迁移 accounts + print("\n### 迁移 accounts ###") + row = await conn.fetchrow("SELECT value FROM kv_store WHERE key = $1", "accounts") + if row: + value = row["value"] + if isinstance(value, str): + value = json.loads(value) + + if isinstance(value, list) and len(value) > 0: + # 检查新表是否已有数据 + count = await conn.fetchval("SELECT COUNT(*) FROM accounts") + if count > 0: + print(f"⚠️ accounts 表已有 {count} 条记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 accounts 迁移") + else: + # 清空并迁移 + await conn.execute("DELETE FROM accounts") + for index, acc in enumerate(value, 1): + account_id = acc.get("id") or f"account_{index}" + await conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES ($1, $2, $3, CURRENT_TIMESTAMP) + """, + account_id, + index, + json.dumps(acc, ensure_ascii=False) + ) + print(f"✅ 成功迁移 {len(value)} 个账户") + migrated_any = True + else: + # 新表为空,直接迁移 + for index, acc in enumerate(value, 1): + account_id = acc.get("id") or f"account_{index}" + await conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES ($1, $2, $3, CURRENT_TIMESTAMP) + """, + account_id, + index, + json.dumps(acc, ensure_ascii=False) + ) + print(f"✅ 成功迁移 {len(value)} 个账户") + migrated_any = True + else: + print("⚠️ kv_store 中 accounts 为空") + else: + print("⚠️ kv_store 中未找到 accounts") + + # 2. 迁移 settings + print("\n### 迁移 settings ###") + row = await conn.fetchrow("SELECT value FROM kv_store WHERE key = $1", "settings") + if row: + value = row["value"] + if isinstance(value, str): + value = json.loads(value) + + if isinstance(value, dict): + # 检查新表是否已有数据 + exists = await conn.fetchval("SELECT 1 FROM kv_settings WHERE key = $1", "settings") + if exists: + print("⚠️ kv_settings 表已有 settings 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 settings 迁移") + else: + await conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = CURRENT_TIMESTAMP + """, + "settings", + json.dumps(value, ensure_ascii=False) + ) + print("✅ 成功迁移 settings") + migrated_any = True + else: + await conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + """, + "settings", + json.dumps(value, ensure_ascii=False) + ) + print("✅ 成功迁移 settings") + migrated_any = True + else: + print("⚠️ kv_store 中 settings 格式错误") + else: + print("⚠️ kv_store 中未找到 settings") + + # 3. 迁移 stats + print("\n### 迁移 stats ###") + row = await conn.fetchrow("SELECT value FROM kv_store WHERE key = $1", "stats") + if row: + value = row["value"] + if isinstance(value, str): + value = json.loads(value) + + if isinstance(value, dict): + # 检查新表是否已有数据 + exists = await conn.fetchval("SELECT 1 FROM kv_stats WHERE key = $1", "stats") + if exists: + print("⚠️ kv_stats 表已有 stats 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 stats 迁移") + else: + await conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = CURRENT_TIMESTAMP + """, + "stats", + json.dumps(value, ensure_ascii=False) + ) + print("✅ 成功迁移 stats") + migrated_any = True + else: + await conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + """, + "stats", + json.dumps(value, ensure_ascii=False) + ) + print("✅ 成功迁移 stats") + migrated_any = True + else: + print("⚠️ kv_store 中 stats 格式错误") + else: + print("⚠️ kv_store 中未找到 stats") + + return migrated_any + + +async def migrate_from_local_files(conn): + """从本地文件迁移数据到数据库""" + print("\n" + "=" * 60) + print("从本地文件迁移数据") + print("=" * 60) + + data_dir = project_root / "data" + accounts_file = data_dir / "accounts.json" + settings_file = data_dir / "settings.yaml" + stats_file = data_dir / "stats.json" + + migrated_any = False + + # 1. 迁移 accounts.json + print("\n### 迁移 accounts.json ###") + if accounts_file.exists(): + try: + with open(accounts_file, "r", encoding="utf-8") as f: + accounts_data = json.load(f) + + if isinstance(accounts_data, list) and len(accounts_data) > 0: + # 检查新表是否已有数据 + count = await conn.fetchval("SELECT COUNT(*) FROM accounts") + if count > 0: + print(f"⚠️ accounts 表已有 {count} 条记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 accounts.json 迁移") + else: + # 清空并迁移 + await conn.execute("DELETE FROM accounts") + for index, acc in enumerate(accounts_data, 1): + account_id = acc.get("id") or f"account_{index}" + await conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES ($1, $2, $3, CURRENT_TIMESTAMP) + """, + account_id, + index, + json.dumps(acc, ensure_ascii=False) + ) + print(f"✅ 成功迁移 {len(accounts_data)} 个账户") + # 重命名文件 + new_path = rename_migrated_file(str(accounts_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + # 新表为空,直接迁移 + for index, acc in enumerate(accounts_data, 1): + account_id = acc.get("id") or f"account_{index}" + await conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES ($1, $2, $3, CURRENT_TIMESTAMP) + """, + account_id, + index, + json.dumps(acc, ensure_ascii=False) + ) + print(f"✅ 成功迁移 {len(accounts_data)} 个账户") + # 重命名文件 + new_path = rename_migrated_file(str(accounts_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + print("⚠️ accounts.json 为空") + except Exception as e: + print(f"❌ 迁移 accounts.json 失败: {e}") + else: + print("⚠️ accounts.json 不存在") + + # 2. 迁移 settings.yaml + print("\n### 迁移 settings.yaml ###") + if settings_file.exists(): + try: + import yaml + with open(settings_file, "r", encoding="utf-8") as f: + settings_data = yaml.safe_load(f) or {} + + if isinstance(settings_data, dict): + # 检查新表是否已有数据 + exists = await conn.fetchval("SELECT 1 FROM kv_settings WHERE key = $1", "settings") + if exists: + print("⚠️ kv_settings 表已有 settings 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 settings.yaml 迁移") + else: + await conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = CURRENT_TIMESTAMP + """, + "settings", + json.dumps(settings_data, ensure_ascii=False) + ) + print("✅ 成功迁移 settings") + # 重命名文件 + new_path = rename_migrated_file(str(settings_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + await conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + """, + "settings", + json.dumps(settings_data, ensure_ascii=False) + ) + print("✅ 成功迁移 settings") + # 重命名文件 + new_path = rename_migrated_file(str(settings_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + print("⚠️ settings.yaml 格式错误") + except Exception as e: + print(f"❌ 迁移 settings.yaml 失败: {e}") + else: + print("⚠️ settings.yaml 不存在") + + # 3. 迁移 stats.json + print("\n### 迁移 stats.json ###") + if stats_file.exists(): + try: + with open(stats_file, "r", encoding="utf-8") as f: + stats_data = json.load(f) + + if isinstance(stats_data, dict): + # 检查新表是否已有数据 + exists = await conn.fetchval("SELECT 1 FROM kv_stats WHERE key = $1", "stats") + if exists: + print("⚠️ kv_stats 表已有 stats 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 stats.json 迁移") + else: + await conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = CURRENT_TIMESTAMP + """, + "stats", + json.dumps(stats_data, ensure_ascii=False) + ) + print("✅ 成功迁移 stats") + # 重命名文件 + new_path = rename_migrated_file(str(stats_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + await conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES ($1, $2, CURRENT_TIMESTAMP) + """, + "stats", + json.dumps(stats_data, ensure_ascii=False) + ) + print("✅ 成功迁移 stats") + # 重命名文件 + new_path = rename_migrated_file(str(stats_file)) + print(f"✅ 文件已重命名: {new_path}") + migrated_any = True + else: + print("⚠️ stats.json 格式错误") + except Exception as e: + print(f"❌ 迁移 stats.json 失败: {e}") + else: + print("⚠️ stats.json 不存在") + + return migrated_any + + +def _init_sqlite_tables(conn: sqlite3.Connection) -> None: + """初始化 SQLite 表结构""" + with conn: + conn.execute( + """ + CREATE TABLE IF NOT EXISTS accounts ( + account_id TEXT PRIMARY KEY, + position INTEGER NOT NULL, + data TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS kv_stats ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + """ + ) + conn.execute( + """ + CREATE TABLE IF NOT EXISTS task_history ( + id TEXT PRIMARY KEY, + data TEXT NOT NULL, + created_at TEXT NOT NULL + ) + """ + ) + conn.execute( + """ + CREATE INDEX IF NOT EXISTS task_history_created_at_idx + ON task_history(created_at DESC) + """ + ) + + +def migrate_from_local_files_sqlite(conn: sqlite3.Connection) -> bool: + """从本地文件迁移数据到 SQLite""" + print("\n" + "=" * 60) + print("从本地文件迁移数据") + print("=" * 60) + + data_dir = project_root / "data" + accounts_file = data_dir / "accounts.json" + settings_file = data_dir / "settings.yaml" + stats_file = data_dir / "stats.json" + + migrated_any = False + + # 1. 迁移 accounts + print("\n### 迁移 accounts ###") + if accounts_file.exists(): + try: + with open(accounts_file, "r", encoding="utf-8") as f: + accounts_data = json.load(f) + + if isinstance(accounts_data, list) and len(accounts_data) > 0: + # 检查新表是否已有数据 + count = conn.execute("SELECT COUNT(*) FROM accounts").fetchone()[0] + if count > 0: + print(f"⚠️ accounts 表已有 {count} 条记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 accounts 迁移") + else: + # 清空并迁移 + with conn: + conn.execute("DELETE FROM accounts") + for index, acc in enumerate(accounts_data, 1): + account_id = acc.get("id") or f"account_{index}" + conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES (?, ?, ?, CURRENT_TIMESTAMP) + """, + (account_id, index, json.dumps(acc, ensure_ascii=False)) + ) + print(f"✅ 成功迁移 {len(accounts_data)} 个账户") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(accounts_file)) + if new_path: + print(f"✅ 已重命名: {accounts_file.name} → {Path(new_path).name}") + else: + # 直接迁移 + with conn: + for index, acc in enumerate(accounts_data, 1): + account_id = acc.get("id") or f"account_{index}" + conn.execute( + """ + INSERT INTO accounts (account_id, position, data, updated_at) + VALUES (?, ?, ?, CURRENT_TIMESTAMP) + """, + (account_id, index, json.dumps(acc, ensure_ascii=False)) + ) + print(f"✅ 成功迁移 {len(accounts_data)} 个账户") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(accounts_file)) + if new_path: + print(f"✅ 已重命名: {accounts_file.name} → {Path(new_path).name}") + else: + print("⚠️ accounts.json 为空或格式错误") + except Exception as e: + print(f"❌ 迁移 accounts 失败: {e}") + else: + print("⚠️ 未找到 accounts.json") + + # 2. 迁移 settings + print("\n### 迁移 settings ###") + if settings_file.exists(): + try: + import yaml + with open(settings_file, "r", encoding="utf-8") as f: + settings_data = yaml.safe_load(f) + + if settings_data: + # 检查新表是否已有数据 + row = conn.execute( + "SELECT 1 FROM kv_settings WHERE key = ?", + ("settings",) + ).fetchone() + if row: + print("⚠️ kv_settings 表已有 settings 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 settings 迁移") + else: + with conn: + conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = CURRENT_TIMESTAMP + """, + ("settings", json.dumps(settings_data, ensure_ascii=False)) + ) + print("✅ 成功迁移 settings") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(settings_file)) + if new_path: + print(f"✅ 已重命名: {settings_file.name} → {Path(new_path).name}") + else: + with conn: + conn.execute( + """ + INSERT INTO kv_settings (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + """, + ("settings", json.dumps(settings_data, ensure_ascii=False)) + ) + print("✅ 成功迁移 settings") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(settings_file)) + if new_path: + print(f"✅ 已重命名: {settings_file.name} → {Path(new_path).name}") + else: + print("⚠️ settings.yaml 为空") + except Exception as e: + print(f"❌ 迁移 settings 失败: {e}") + else: + print("⚠️ 未找到 settings.yaml") + + # 3. 迁移 stats + print("\n### 迁移 stats ###") + if stats_file.exists(): + try: + with open(stats_file, "r", encoding="utf-8") as f: + stats_data = json.load(f) + + if stats_data: + # 检查新表是否已有数据 + row = conn.execute( + "SELECT 1 FROM kv_stats WHERE key = ?", + ("stats",) + ).fetchone() + if row: + print("⚠️ kv_stats 表已有 stats 记录") + confirm = input("是否覆盖?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("跳过 stats 迁移") + else: + with conn: + conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = CURRENT_TIMESTAMP + """, + ("stats", json.dumps(stats_data, ensure_ascii=False)) + ) + print("✅ 成功迁移 stats") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(stats_file)) + if new_path: + print(f"✅ 已重命名: {stats_file.name} → {Path(new_path).name}") + else: + with conn: + conn.execute( + """ + INSERT INTO kv_stats (key, value, updated_at) + VALUES (?, ?, CURRENT_TIMESTAMP) + """, + ("stats", json.dumps(stats_data, ensure_ascii=False)) + ) + print("✅ 成功迁移 stats") + migrated_any = True + + # 重命名文件 + new_path = rename_migrated_file(str(stats_file)) + if new_path: + print(f"✅ 已重命名: {stats_file.name} → {Path(new_path).name}") + else: + print("⚠️ stats.json 为空") + except Exception as e: + print(f"❌ 迁移 stats 失败: {e}") + else: + print("⚠️ 未找到 stats.json") + + return migrated_any + + +async def main(): + """主函数""" + database_url = os.environ.get("DATABASE_URL", "").strip() + + # 判断使用哪种数据库 + if database_url: + backend = "postgres" + db_info = database_url.split('@')[1] if '@' in database_url else 'PostgreSQL' + else: + backend = "sqlite" + db_info = "data/data.db (SQLite)" + + print("=" * 60) + print("数据库迁移脚本") + print("=" * 60) + print(f"目标数据库: {db_info}") + print() + print("迁移内容:") + if backend == "postgres": + print(" 1. kv_store → 新表(accounts, kv_settings, kv_stats)") + print(" 2. 本地文件 → 新表") + else: + print(" 1. 本地文件 → SQLite 数据库") + print() + print("迁移后:") + if backend == "postgres": + print(" - kv_store 保留(作为备份)") + print(" - 本地文件重命名为 .migrated_YYYYMMDD-HHMMSS") + print() + + confirm = input("开始迁移?(yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("❌ 操作已取消") + return False + + try: + if backend == "postgres": + # PostgreSQL 迁移 + import asyncpg + conn = await asyncpg.connect(database_url) + + # 1. 从 kv_store 迁移 + kv_migrated = await migrate_from_kv_store(conn) + + # 2. 从本地文件迁移 + file_migrated = await migrate_from_local_files(conn) + + await conn.close() + + else: + # SQLite 迁移 + sqlite_path = project_root / "data" / "data.db" + os.makedirs(sqlite_path.parent, exist_ok=True) + + conn = sqlite3.connect(str(sqlite_path)) + conn.row_factory = sqlite3.Row + + # 初始化表结构 + _init_sqlite_tables(conn) + + # 从本地文件迁移 + file_migrated = migrate_from_local_files_sqlite(conn) + kv_migrated = False + + conn.close() + + print("\n" + "=" * 60) + if kv_migrated or file_migrated: + print("✅ 迁移完成!") + else: + print("⚠️ 没有数据需要迁移") + print("=" * 60) + print() + print("下一步:") + print("1. 重启应用") + print("2. 应用会自动使用新表数据") + print() + + return True + + except ImportError as e: + if "asyncpg" in str(e): + print("❌ 错误:未安装 asyncpg") + print(" 请运行: pip install asyncpg") + else: + print(f"❌ 错误:{e}") + return False + except Exception as e: + print(f"❌ 错误:{e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + success = asyncio.run(main()) + sys.exit(0 if success else 1) diff --git a/setup.bat b/setup.bat new file mode 100644 index 0000000000000000000000000000000000000000..ed0f37acb6104240c9b4f8da8432460b4e7d9431 --- /dev/null +++ b/setup.bat @@ -0,0 +1,185 @@ +@echo off +REM Gemini Business2API Setup Script +REM Handles both installation and updates automatically +REM Uses uv for Python environment management +REM Usage: setup.bat + +setlocal enabledelayedexpansion + +echo ========================================== +echo Gemini Business2API Setup Script +echo ========================================== +echo. + +REM Color codes for output (using echo instead of ANSI codes for better Windows compatibility) +set GREEN=[92m +set RED=[91m +set YELLOW=[93m +set BLUE=[94m +set NC=[0m + +REM Function to print colored messages (simplified for Windows) +set "PRINT_SUCCESS=echo [SUCCESS]" +set "PRINT_ERROR=echo [ERROR]" +set "PRINT_INFO=echo [INFO]" +set "PRINT_STEP=echo [STEP]" + +REM Check if git is installed +where git >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Git is not installed. Please install git first. + exit /b 1 +) + +REM Step 1: Install or update uv +echo [STEP] Step 1: Installing/Updating uv... + +where uv >nul 2>nul +if %errorlevel% neq 0 ( + echo [INFO] uv not found, installing... + REM Install uv using pipx or pip + pipx install uv 2>nul + if %errorlevel% neq 0 ( + pip install --user uv 2>nul + if %errorlevel% neq 0 ( + REM Fallback: download and install uv binary + curl -LsSf https://astral.sh/uv/install.bat | cmd + ) + ) + if %errorlevel% equ 0 ( + echo [SUCCESS] uv installed successfully + ) else ( + echo [ERROR] Failed to install uv + exit /b 1 + ) +) else ( + echo [INFO] Updating uv to latest version... + uv pip install --upgrade uv + echo [SUCCESS] uv updated +) +echo. + +REM Step 2: Ensure Python 3.11 is available +echo [STEP] Step 2: Ensuring Python 3.11 is available... +uv python list | findstr /C:"3.11" >nul +if %errorlevel% neq 0 ( + echo [INFO] Python 3.11 not found, installing... + uv python install 3.11 + if %errorlevel% neq 0 ( + echo [ERROR] Failed to install Python 3.11 + exit /b 1 + ) + echo [SUCCESS] Python 3.11 installed +) else ( + echo [SUCCESS] Python 3.11 is already available +) +echo. + +REM Step 3: Pull latest code from git +echo [STEP] Step 3: Syncing code from repository... +echo [INFO] Fetching latest changes... +git fetch origin + +echo [INFO] Pulling latest code... +git pull origin main 2>nul || git pull origin master 2>nul +if %errorlevel% equ 0 ( + echo [SUCCESS] Code synchronized successfully +) else ( + echo [INFO] No remote changes to pull +) +echo. + +REM Step 4: Setup .env file if it doesn't exist +echo [STEP] Step 4: Checking configuration... +if exist .env ( + echo [INFO] .env file exists +) else ( + if exist .env.example ( + copy .env.example .env >nul + echo [SUCCESS] .env file created from .env.example + echo [INFO] Please edit .env and configure your ADMIN_KEY + ) else ( + echo [ERROR] .env.example not found + exit /b 1 + ) +) +echo. + +REM Step 5: Setup Python virtual environment +echo [STEP] Step 5: Setting up Python environment... +if exist .venv ( + echo [INFO] Virtual environment already exists +) else ( + echo [INFO] Creating virtual environment with Python 3.11... + uv venv --python 3.11 .venv + if %errorlevel% neq 0 ( + echo [ERROR] Failed to create virtual environment + exit /b 1 + ) + echo [SUCCESS] Virtual environment created +) +echo. + +REM Step 6: Install/Update Python dependencies +echo [STEP] Step 6: Installing Python dependencies... +echo [INFO] Using uv to install dependencies (this may take a moment)... +.venv\Scripts\python.exe -m pip install --upgrade pip --quiet +uv pip install -r requirements.txt +if %errorlevel% neq 0 ( + echo [ERROR] Failed to install Python dependencies + exit /b 1 +) +echo [SUCCESS] Python dependencies installed +echo. + +REM Step 7: Setup frontend +echo [STEP] Step 7: Setting up frontend... +if exist frontend ( + cd frontend + + REM Check if npm is installed + where npm >nul 2>nul + if %errorlevel% equ 0 ( + echo [INFO] Installing dependencies... + npm install + + echo [INFO] Building frontend... + npm run build + echo [SUCCESS] Frontend built successfully + ) else ( + echo [ERROR] npm is not installed. Please install Node.js and npm first. + cd .. + exit /b 1 + ) + + cd .. +) else ( + echo [ERROR] Frontend directory not found. Are you in the project root? + exit /b 1 +) +echo. + +REM Step 8: Show completion message +echo ========================================== +echo [SUCCESS] Setup completed successfully! +echo ========================================== +echo. + +if exist .env ( + echo [INFO] Next steps: + echo. + echo 1. Edit .env file if needed: + echo notepad .env + echo. + echo 2. Start the service: + echo .venv\Scripts\python.exe main.py + echo. + echo 3. Access the admin panel: + echo http://localhost:7860/ + echo. + echo [INFO] To activate virtual environment later, run: + echo .venv\Scripts\activate.bat +) +echo. + +endlocal diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..23cad7ef286e99bb4f44d94d511014ee883cc4a2 --- /dev/null +++ b/setup.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +# Gemini Business2API Setup Script +# Handles both installation and updates automatically +# Uses uv for Python environment management +# Usage: ./setup.sh + +set -e # Exit on error + +echo "==========================================" +echo "Gemini Business2API Setup Script" +echo "==========================================" +echo "" + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored messages +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${YELLOW}→ $1${NC}" +} + +print_step() { + echo -e "${BLUE}[STEP] $1${NC}" +} + +# Check if git is installed +if ! command -v git &> /dev/null; then + print_error "Git is not installed. Please install git first." + exit 1 +fi + +# Step 1: Install or update uv +print_step "Step 1: Installing/Updating uv..." +if ! command -v uv &> /dev/null; then + print_info "uv not found, installing..." + # Install uv using pipx or pip + if command -v pipx &> /dev/null; then + pipx install uv + elif command -v pip &> /dev/null; then + pip install --user uv + else + # Fallback: download and install uv binary + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" + fi + print_success "uv installed successfully" +else + print_info "Updating uv to latest version..." + uv pip install --upgrade uv + print_success "uv updated" +fi +echo "" + +# Step 2: Ensure Python 3.11 is available +print_step "Step 2: Ensuring Python 3.11 is available..." +if ! uv python list | grep -q "3.11"; then + print_info "Python 3.11 not found, installing..." + uv python install 3.11 + print_success "Python 3.11 installed" +else + print_success "Python 3.11 is already available" +fi +echo "" + +# Step 3: Pull latest code from git +print_step "Step 3: Syncing code from repository..." +print_info "Fetching latest changes..." +git fetch origin + +print_info "Pulling latest code..." +if git pull origin main 2>/dev/null || git pull origin master 2>/dev/null; then + print_success "Code synchronized successfully" +else + print_info "No remote changes to pull" +fi +echo "" + +# Step 4: Setup .env file if it doesn't exist +print_step "Step 4: Checking configuration..." +if [ -f ".env" ]; then + print_info ".env file exists" +else + if [ -f ".env.example" ]; then + cp .env.example .env + print_success ".env file created from .env.example" + print_info "Please edit .env and configure your ADMIN_KEY" + else + print_error ".env.example not found" + exit 1 + fi +fi +echo "" + +# Step 5: Setup Python virtual environment +print_step "Step 5: Setting up Python environment..." +if [ -d ".venv" ]; then + print_info "Virtual environment already exists" +else + print_info "Creating virtual environment with Python 3.11..." + uv venv --python 3.11 .venv + print_success "Virtual environment created" +fi +echo "" + +# Step 6: Install/Update Python dependencies +print_step "Step 6: Installing Python dependencies..." +print_info "Using uv to install dependencies (this may take a moment)..." +uv pip install --python .venv/bin/python -r requirements.txt --system +print_success "Python dependencies installed" +echo "" + +# Step 7: Setup frontend +print_step "Step 7: Setting up frontend..." +if [ -d "frontend" ]; then + cd frontend + + # Check if npm is installed + if command -v npm &> /dev/null; then + print_info "Installing dependencies..." + npm install + + print_info "Building frontend..." + npm run build + print_success "Frontend built successfully" + else + print_error "npm is not installed. Please install Node.js and npm first." + cd .. + exit 1 + fi + + cd .. +else + print_error "Frontend directory not found. Are you in the project root?" + exit 1 +fi +echo "" + +# Step 8: Show completion message +echo "==========================================" +print_success "Setup completed successfully!" +echo "==========================================" +echo "" + +if [ -f ".env" ]; then + print_info "Next steps:" + echo "" + echo " 1. Edit .env file if needed:" + echo " ${BLUE}nano .env${NC} or ${BLUE}vim .env${NC}" + echo "" + echo " 2. Start the service:" + echo " ${BLUE}uv run python main.py${NC}" + echo "" + echo " 3. Access the admin panel:" + echo " ${BLUE}http://localhost:7860/${NC}" + echo "" + print_info "To activate virtual environment later, run:" + echo " ${BLUE}source .venv/bin/activate${NC}" +fi +echo "" diff --git a/util/streaming_parser.py b/util/streaming_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..898bd590c64d5ea2fc456610eb1b1c88a3f66938 --- /dev/null +++ b/util/streaming_parser.py @@ -0,0 +1,255 @@ +import json +from typing import Iterator, Dict, Any, Iterable, AsyncIterator +from itertools import chain + +def parse_json_array_stream(line_iterator: Iterable[str]) -> Iterator[Dict[str, Any]]: + """ + 解析一个由文本行组成的、格式化的(pretty-printed)JSON数组流。 + + 这个函数是一个生成器,它会为在流中发现的每个第一层级的JSON对象 + 产出(yield)一个完整的Python字典。它的设计目标是高内存效率, + 因为它会逐行处理流,而不是一次性加载所有内容。 + + Args: + line_iterator: 一个产生响应行的迭代器。例如,`requests.Response.iter_lines()` + 解码后的结果。 + + Yields: + 一个从流中解析出的JSON对象的字典。 + + Raises: + ValueError: 如果流看起来不像是以JSON数组开始,或者其格式错误 + 导致无法按对象进行解析。 + """ + # 状态变量 + buffer = [] + brace_level = 0 + in_array = False + + # 1. 寻找数组的起始符 '[',并忽略之前的所有行 + for line in line_iterator: + stripped_line = line.strip() + if not stripped_line: + continue + + if stripped_line.startswith('['): + in_array = True + # 去掉起始的 '[' 字符,剩下的部分继续处理 + line = stripped_line[1:] + # 使用 chain 连接剩余行,避免转换为列表(内存优化) + line_iterator = chain([line], line_iterator) + break + + if not in_array: + raise ValueError("数据流不是以一个JSON数组 ( '[' ) 开始。") + + # 2. 遍历流,逐个字符地构建和解析对象 + in_string = False # 是否在字符串内部 + escape_next = False # 下一个字符是否被转义 + + for line in line_iterator: + for char in line: + # 处理转义字符 + if escape_next: + if brace_level > 0: + buffer.append(char) + escape_next = False + continue + + # 检查是否是转义符 + if char == '\\': + if brace_level > 0: + buffer.append(char) + escape_next = True + continue + + # 检查字符串边界(只在对象内部时才处理) + if char == '"' and brace_level > 0: + in_string = not in_string + buffer.append(char) + continue + + # 只有在非字符串内部时,才处理括号 + if not in_string: + # 当遇到 '{' 时,增加嵌套层级 + if char == '{': + # 如果是第一层级的对象,清空缓冲区,准备接收新对象 + if brace_level == 0: + buffer = [] + brace_level += 1 + + # 只有在对象内部时 (brace_level > 0),才将字符加入缓冲区 + if brace_level > 0: + buffer.append(char) + + # 当遇到 '}' 时,减少嵌套层级 + if char == '}': + brace_level -= 1 + # 当层级回到0时,说明一个第一层级的对象已经完整 + if brace_level == 0 and buffer: + obj_str = "".join(buffer) + try: + # 解析这个完整的对象字符串并产出结果 + # 使用 strict=False 允许控制字符 + yield json.loads(obj_str, strict=False) + except json.JSONDecodeError as e: + # 如果解析失败,抛出带上下文的异常 + raise ValueError(f"解析JSON对象失败: {e}\n内容: {obj_str}") from e + finally: + # 重置缓冲区,为下一个对象做准备 + buffer = [] + in_string = False # 重置字符串状态 + else: + # 在字符串内部,直接添加字符 + if brace_level > 0: + buffer.append(char) + + # 3. 检查流结束后,是否还有未闭合的对象 + if brace_level != 0: + print(f"警告: JSON流意外结束,括号层级为 {brace_level},可能数据不完整。") + +async def parse_json_array_stream_async(line_iterator: AsyncIterator[str]) -> AsyncIterator[Dict[str, Any]]: + """ + 异步版本:解析一个由文本行组成的、格式化的(pretty-printed)JSON数组流。 + + 这个函数是一个异步生成器,它会为在流中发现的每个第一层级的JSON对象 + 产出(yield)一个完整的Python字典。它的设计目标是高内存效率, + 因为它会逐行处理流,而不是一次性加载所有内容。 + + Args: + line_iterator: 一个产生响应行的异步迭代器。例如,`httpx.Response.aiter_lines()` + + Yields: + 一个从流中解析出的JSON对象的字典。 + + Raises: + ValueError: 如果流看起来不像是以JSON数组开始,或者其格式错误 + 导致无法按对象进行解析。 + """ + # 状态变量 + buffer = [] + brace_level = 0 + in_array = False + + # 1. 寻找数组的起始符 '[',并忽略之前的所有行 + in_string = False + escape_next = False + + async for line in line_iterator: + stripped_line = line.strip() + if not stripped_line: + continue + + if stripped_line.startswith('['): + in_array = True + # 去掉起始的 '[' 字符,剩下的部分继续处理 + line = stripped_line[1:] + # 处理剩余部分(使用相同的字符串状态跟踪逻辑) + for char in line: + if escape_next: + if brace_level > 0: + buffer.append(char) + escape_next = False + continue + + if char == '\\': + if brace_level > 0: + buffer.append(char) + escape_next = True + continue + + if char == '"' and brace_level > 0: + in_string = not in_string + buffer.append(char) + continue + + if not in_string: + if char == '{': + if brace_level == 0: + buffer = [] + brace_level += 1 + + if brace_level > 0: + buffer.append(char) + + if char == '}': + brace_level -= 1 + if brace_level == 0 and buffer: + obj_str = "".join(buffer) + try: + yield json.loads(obj_str, strict=False) + except json.JSONDecodeError as e: + raise ValueError(f"解析JSON对象失败: {e}\n内容: {obj_str}") from e + finally: + buffer = [] + in_string = False + else: + if brace_level > 0: + buffer.append(char) + break + + if not in_array: + raise ValueError("数据流不是以一个JSON数组 ( '[' ) 开始。") + + # 2. 遍历流,逐个字符地构建和解析对象(保持第一行处理后的状态) + async for line in line_iterator: + for char in line: + # 处理转义字符 + if escape_next: + if brace_level > 0: + buffer.append(char) + escape_next = False + continue + + # 检查是否是转义符 + if char == '\\': + if brace_level > 0: + buffer.append(char) + escape_next = True + continue + + # 检查字符串边界(只在对象内部时才处理) + if char == '"' and brace_level > 0: + in_string = not in_string + buffer.append(char) + continue + + # 只有在非字符串内部时,才处理括号 + if not in_string: + # 当遇到 '{' 时,增加嵌套层级 + if char == '{': + # 如果是第一层级的对象,清空缓冲区,准备接收新对象 + if brace_level == 0: + buffer = [] + brace_level += 1 + + # 只有在对象内部时 (brace_level > 0),才将字符加入缓冲区 + if brace_level > 0: + buffer.append(char) + + # 当遇到 '}' 时,减少嵌套层级 + if char == '}': + brace_level -= 1 + # 当层级回到0时,说明一个第一层级的对象已经完整 + if brace_level == 0 and buffer: + obj_str = "".join(buffer) + try: + # 解析这个完整的对象字符串并产出结果 + # 使用 strict=False 允许控制字符 + yield json.loads(obj_str, strict=False) + except json.JSONDecodeError as e: + # 如果解析失败,抛出带上下文的异常 + raise ValueError(f"解析JSON对象失败: {e}\n内容: {obj_str}") from e + finally: + # 重置缓冲区,为下一个对象做准备 + buffer = [] + in_string = False # 重置字符串状态 + else: + # 在字符串内部,直接添加字符 + if brace_level > 0: + buffer.append(char) + + # 3. 检查流结束后,是否还有未闭合的对象 + if brace_level != 0: + print(f"警告: JSON流意外结束,括号层级为 {brace_level},可能数据不完整。") +