diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..6e8e325b246907dee937fe855f3cf8c95ff7abf5
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,93 @@
+# ======================================
+# GitHub Agent Demo - 环境变量配置
+# ======================================
+
+# --- LLM 供应商选择 ---
+# 支持: openai, deepseek, anthropic, gemini
+# 默认: deepseek
+LLM_PROVIDER=deepseek
+
+# --- API Keys (根据选择的供应商配置对应的 Key) ---
+
+# OpenAI (如果 LLM_PROVIDER=openai)
+OPENAI_API_KEY=
+# OPENAI_BASE_URL= # 可选: 自定义端点 (如 Azure OpenAI)
+
+# DeepSeek (如果 LLM_PROVIDER=deepseek)
+DEEPSEEK_API_KEY=
+# DEEPSEEK_BASE_URL=https://api.deepseek.com # 可选: 默认值
+
+# Anthropic Claude (如果 LLM_PROVIDER=anthropic)
+ANTHROPIC_API_KEY=
+
+# Google Gemini (如果 LLM_PROVIDER=gemini)
+GEMINI_API_KEY=
+# GEMINI_BASE_URL= # 可选: OpenAI 兼容端点
+
+# --- 模型配置 ---
+# 如果不指定,将使用各供应商的默认模型:
+# - openai: gpt-4o-mini
+# - deepseek: deepseek-chat
+# - anthropic: claude-3-5-sonnet-20241022
+# - gemini: gemini-1.5-flash
+# MODEL_NAME=deepseek-chat
+
+# --- GitHub Token ---
+# 用于访问 GitHub API,提高请求限制
+GITHUB_TOKEN=
+
+# --- Embedding 服务 ---
+# SiliconFlow API Key (用于 BGE-M3 Embedding)
+SILICON_API_KEY=
+
+# --- Langfuse 追踪配置 (可选) ---
+# LANGFUSE_ENABLED=true
+# LANGFUSE_HOST=http://localhost:3000
+# LANGFUSE_PUBLIC_KEY=
+# LANGFUSE_SECRET_KEY=
+
+# --- Qdrant 向量数据库配置 ---
+# 模式选择: "local" | "server" | "cloud"
+# - local: 本地嵌入式存储 (开发环境, 单 Worker)
+# - server: Qdrant Server Docker (生产环境, 多 Worker)
+# - cloud: Qdrant Cloud 托管服务
+QDRANT_MODE=local
+QDRANT_LOCAL_PATH=data/qdrant_db
+
+# Server 模式: 连接 Qdrant Server (Docker)
+# QDRANT_MODE=server
+# QDRANT_URL=http://localhost:6333
+# 或分开配置:
+# QDRANT_HOST=localhost
+# QDRANT_PORT=6333
+
+# Cloud 模式: 连接 Qdrant Cloud
+# QDRANT_MODE=cloud
+# QDRANT_URL=https://xxx.qdrant.tech
+# QDRANT_API_KEY=your-api-key
+
+# 向量维度 (BGE-M3 = 1024)
+# QDRANT_VECTOR_SIZE=1024
+
+# --- Gunicorn Worker 配置 ---
+# 2核2G服务器建议设为 2
+# 4核8G服务器可设为 4
+GUNICORN_WORKERS=2
+
+# --- 分布式锁配置 ---
+# 锁后端: "memory" | "file" | "redis"
+# - memory: 内存锁 (单进程)
+# - file: 文件锁 (多 Worker 单节点)
+# - redis: Redis 分布式锁 (多节点)
+LOCK_BACKEND=file
+LOCK_DIR=data/locks
+# REDIS_URL=redis://localhost:6379/0
+
+# --- 服务配置 ---
+HOST=0.0.0.0
+PORT=8000
+
+# --- LLM 参数 (可选) ---
+# LLM_TEMPERATURE=0.1
+# LLM_MAX_TOKENS=4096
+# LLM_TIMEOUT=600
\ No newline at end of file
diff --git a/.github/workflows/sync_to_hub.yml b/.github/workflows/sync_to_hub.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a07b8c3c4bb7f452273787d9d1ebe3f62d23e4d4
--- /dev/null
+++ b/.github/workflows/sync_to_hub.yml
@@ -0,0 +1,58 @@
+name: Sync to Hugging Face hub
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+jobs:
+ sync-to-hub:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Push to hub
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ HF_USERNAME: realdexter
+ SPACE_NAME: RepoReaper
+ run: |
+ echo "🚀 Starting deployment to Hugging Face..."
+
+ # 1. 配置 Git
+ git config --global user.email "bot@github.com"
+ git config --global user.name "GitHub Actions Bot"
+
+ # 2. 【核心魔法】动态生成 Hugging Face 专用的 README
+ # 这一步会在发送给 HF 之前,强行在 README.md 顶部插入配置头
+ # GitHub 本地的文件不会受影响,依然保持干净漂亮
+ echo "---" > hf_header.yml
+ echo "title: RepoReaper" >> hf_header.yml
+ echo "emoji: 💀" >> hf_header.yml
+ echo "colorFrom: blue" >> hf_header.yml
+ echo "colorTo: indigo" >> hf_header.yml
+ echo "sdk: docker" >> hf_header.yml
+ echo "pinned: false" >> hf_header.yml
+ echo "app_port: 8000" >> hf_header.yml # 👈 关键:这里指定端口,你就不用改代码了
+ echo "---" >> hf_header.yml
+ echo "" >> hf_header.yml
+
+ # 将配置头和原 README 内容拼接
+ cat hf_header.yml README.md > README_temp.md
+ mv README_temp.md README.md
+
+ # 3. 清理不需要的文件
+ rm -rf docs/
+ rm -f *.jpg *.png *.gif hf_header.yml
+ rm -rf .git
+
+ # 4. 初始化新仓库并推送
+ git init -b main
+ git add .
+ git commit -m "deploy: auto-inject hf config & sync"
+
+ git remote add space https://$HF_USERNAME:$HF_TOKEN@huggingface.co/spaces/$HF_USERNAME/$SPACE_NAME
+ git push --force space main
+
+ echo "✅ Deployment successful! Config header injected on-the-fly."
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e1f6e29f6e3f6d7b89de136deb4bb2e5215dc444
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+# .gitignore
+__pycache__/
+*.py[cod]
+.env
+.venv/
+venv/
+.DS_Store
+data/
+# Vue 构建输出
+#frontend-dist/
+frontend-vue/node_modules/
+frontend-vue/dist/
+
+# 锁文件目录
+data/locks/
+
+# 日志
+logs/
+*.log
+
+# IDE
+.idea/
+.vscode/
+*.swp
+
+# 临时文件
+*.tmp
+*.bak
+QUICKSTART.md
+docs/INTERVIEW_QA.md
+docs/ROADMAP.md
+docs/TECHNICAL_REPORT.md
+evaluation/000_START_HERE.md
+evaluation/golden_dataset.json
+evaluation/HIGH_QUALITY_QUESTIONS.md
+
+evaluation/README_EVALUATION_SYSTEM.md
+evaluation/ragas_eval_dataset.json
+evaluation/sft_data/eval_results.jsonl
+evaluation/sft_data/negative_samples.jsonl
+evaluation/sft_data/positive_samples.jsonl
+evaluation/sft_data/skipped_samples.jsonl
+evaluation/sft_data/cleaned/rejected_20260128_010745.jsonl
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..67ffb714e5c9bf3a0006a52635d7ea0c479c8e1a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+# 1. 基础镜像:选择 Python 3.10 的轻量版 (Slim)
+FROM python:3.10-slim
+
+# 2. 设置环境变量
+ENV PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1 \
+ # 默认 LLM 供应商 (可通过 docker run -e 覆盖)
+ LLM_PROVIDER=deepseek
+
+# 3. 设置工作目录
+WORKDIR /app
+
+# 4. 安装系统级依赖
+# build-essential: ChromaDB 编译需要
+# curl: 健康检查
+# git: 某些 pip 包可能需要
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ build-essential \
+ curl \
+ git \
+ && rm -rf /var/lib/apt/lists/* \
+ && apt-get clean
+
+# 5. 复制依赖文件并安装 (利用 Docker 层缓存)
+COPY requirements.txt .
+
+# 6. 安装 Python 依赖
+RUN pip install --no-cache-dir --upgrade pip && \
+ pip install --no-cache-dir -r requirements.txt
+
+# 7. 复制项目代码
+COPY . .
+
+# 8. 创建数据目录 (Qdrant 本地存储 + 上下文缓存)
+RUN mkdir -p /app/data/qdrant_db /app/data/contexts
+
+# 9. 暴露端口
+EXPOSE 8000
+
+# 10. 健康检查
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:8000/health || exit 1
+
+# 11. 启动命令
+CMD ["gunicorn", "-c", "gunicorn_conf.py", "app.main:app"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..242fa6488fcde1ae993f6959de666741a3f71eab
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 tzzp1224
+
+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..05f8c1528f6176026d9e06e4eec497f5086b488b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,224 @@
+---
+title: RepoReaper
+emoji: 💀
+colorFrom: blue
+colorTo: indigo
+sdk: docker
+pinned: false
+app_port: 8000
+---
+
+
+
+

+
+
RepoReaper
+
+
💀 Harvest Logic. Dissect Architecture. Chat with Code.
+
+
+ English •
+ 简体中文
+
+
+
+
+
+

+

+

+
+
+
+

+

+

+

+

+
+
+
+
+
+ 👇 Live Demo / 在线体验 👇
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⚠️ Public demos use shared API quotas. Deploy locally for the best experience.
+
+
+
+
+
+

+
+
+
+
+---
+
+An autonomous Agent that dissects any GitHub repository. It maps code architecture, warms up semantic cache, and answers questions with Just-In-Time context retrieval.
+
+---
+
+## ✨ Key Features
+
+| Feature | Description |
+|:--------|:------------|
+| **Multi-Language AST Parsing** | Python AST + Regex patterns for Java, TypeScript, Go, Rust, etc. |
+| **Hybrid Search** | Qdrant vectors + BM25 with RRF fusion |
+| **JIT Context Loading** | Auto-fetches missing files during Q&A |
+| **Query Rewrite** | Translates natural language to code keywords |
+| **End-to-End Tracing** | Langfuse integration for observability |
+| **Auto Evaluation** | LLM-as-Judge scoring pipeline |
+
+---
+
+## 🏗 Architecture
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Vue 3 Frontend (SSE Streaming + Mermaid Diagrams) │
+└─────────────────────┬───────────────────────────────────────┘
+ │
+┌─────────────────────▼───────────────────────────────────────┐
+│ FastAPI Backend │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
+│ │ Agent │ │ Chat │ │ Evaluation │ │
+│ │ Service │ │ Service │ │ Framework │ │
+│ └──────┬──────┘ └──────┬──────┘ └─────────────────────┘ │
+│ │ │ │
+│ ┌──────▼───────────────▼──────┐ ┌─────────────────────┐ │
+│ │ Vector Service (Qdrant+BM25)│ │ Tracing (Langfuse) │ │
+│ └─────────────────────────────┘ └─────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🛠 Tech Stack
+
+**Backend:** Python 3.10+ · FastAPI · AsyncIO · Qdrant · BM25
+**Frontend:** Vue 3 · Pinia · Mermaid.js · SSE
+**LLM:** DeepSeek V3 · SiliconFlow BGE-M3
+**Ops:** Docker · Gunicorn · Langfuse
+
+---
+
+## 🏁 Quick Start
+
+**Prerequisites:** Python 3.10+ · (Optional) Node 18+ for rebuilding frontend · GitHub Token (recommended) · LLM API Key (required)
+
+```bash
+# Clone & Setup
+git clone https://github.com/tzzp1224/RepoReaper.git && cd RepoReaper
+python -m venv venv && source venv/bin/activate
+pip install -r requirements.txt
+
+# Configure .env (copy from example and fill in your keys)
+cp .env.example .env
+# Required: set LLM_PROVIDER and the matching *_API_KEY
+# Recommended: GITHUB_TOKEN and SILICON_API_KEY (embeddings)
+
+# (Optional) Build frontend (repo already contains frontend-dist)
+cd frontend-vue
+npm install
+npm run build
+cd ..
+
+# Run
+python -m app.main
+```
+
+Open `http://localhost:8000` and paste any GitHub repo URL.
+
+**Docker (single container, local Qdrant):**
+```bash
+cp .env.example .env
+docker build -t reporeaper .
+docker run -d -p 8000:8000 --env-file .env reporeaper
+```
+
+**Docker Compose (recommended, with Qdrant Server):**
+```bash
+cp .env.example .env
+# Set QDRANT_MODE=server and QDRANT_URL=http://qdrant:6333 in .env
+docker compose up -d --build
+```
+
+
+
+
+
+## 📊 Evaluation & Tracing Status
+
+| Component | Status | Notes |
+|:----------|:------:|:------|
+| **Self-built Eval Engine** | ✅ Working | 4-layer metrics (QueryRewrite / Retrieval / Generation / Agentic), LLM-as-Judge |
+| **Auto Evaluation** | ✅ Working | Triggers after every `/chat`, async, writes to `evaluation/sft_data/` |
+| **Data Routing (SFT)** | ✅ Working | Auto-grades Gold/Silver/Bronze/Rejected → JSONL files |
+| **Eval API Endpoints** | ✅ Working | `/evaluate`, `/evaluation/stats`, `/dashboard/*`, `/auto-eval/*` (7 endpoints) |
+| **Offline Retrieval Eval** | ✅ Working | `test_retrieval.py` — Hit Rate, Recall@K, Precision@K, MRR |
+| **Langfuse Tracing** | ⚠️ Partial | Framework + 14 call sites wired in agent/chat services; falls back to local JSON logs (`logs/traces/`) when Langfuse unavailable |
+| **Ragas Integration** | ❌ Placeholder | `use_ragas=False` by default; `_ragas_eval()` API call doesn't match latest Ragas SDK |
+| **Langfuse ↔ Eval** | ❌ Not connected | Eval results only write JSONL, not reported to Langfuse Scores API |
+
+> **Overall completion: ~65%** — the self-built eval loop is production-ready; Ragas and Langfuse integrations are scaffolded but not functional.
+
+---
+
+## ⚠️ Known Issues
+
+1. **Python 3.14 + Langfuse import error**
+ `pydantic.V1.errors.ConfigError: unable to infer type for attribute "description"` — Langfuse 3.x internally uses `pydantic.v1` compat layer which breaks on Python 3.14.
+ **Workaround:** set `LANGFUSE_ENABLED=false` in `.env`, or use Python 3.10–3.12.
+
+2. **Langfuse Server not included in `docker-compose.yml`**
+ Even if the import works, you need a running Langfuse instance. Add it yourself or use [app.langfuse.com](https://app.langfuse.com).
+
+3. **Trace spans are not linked**
+ `tracing_service` records spans/events but doesn't pass `trace_id` to Langfuse API calls — the Langfuse UI will show isolated events instead of a connected trace tree.
+
+4. **Ragas `_ragas_eval()` uses outdated API**
+ Passes a plain dict to `ragas.evaluate()`, but latest Ragas requires a `Dataset` object. The `ragas_eval_dataset.json` export exists but no script consumes it.
+
+5. **Golden dataset has no reference answers**
+ All 26 test cases have `expected_answer: ""` — generation quality cannot be compared against ground truth.
+
+6. **Heuristic fallback is coarse**
+ When no LLM client is available, `faithfulness` uses keyword overlap + 0.2 baseline; `completeness` is purely length-based.
+
+---
+
+## 🗺 Roadmap
+
+- [ ] **Fix Langfuse compat** — pin `langfuse`/`pydantic` versions or gate import behind Python version check
+- [ ] **Add Langfuse to `docker-compose.yml`** — one-command local observability
+- [ ] **Wire trace_id through spans** — enable full trace tree in Langfuse UI
+- [ ] **Integrate Ragas properly** — update `_ragas_eval()` to use `ragas.evaluate(Dataset(...))`, add a standalone eval script
+- [ ] **Enrich golden dataset** — add `expected_answer` for generation benchmarking, expand to 50+ cases
+- [ ] **Eval dashboard frontend** — Vue component to visualize quality distribution and bad cases
+- [ ] **CI regression baseline** — run `test_retrieval.py` in GitHub Actions, fail on metric regression
+- [ ] **Export to Langfuse Datasets** — push eval results to Langfuse Scores/Datasets API for unified observability
+
+---
+
+## 📈 Star History
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 0000000000000000000000000000000000000000..647001b61060e425ccf3d93f487f9816e4ea3db1
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,212 @@
+
+
+

+
+
RepoReaper
+
+
💀 Harvest Logic. Dissect Architecture. Chat with Code.
+
+
+ English •
+ 简体中文
+
+
+
+
+
+

+

+

+
+
+
+

+

+

+

+

+
+
+
+
+
+ 👇 在线体验 👇
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⚠️ 中国用户请使用 Seoul Server。如遇限流,建议本地部署。
+
+
+
+
+
+

+
+
+
+
+---
+
+自治型代码审计 Agent:解析任意 GitHub 仓库架构,构建语义缓存,支持即时上下文检索问答。
+
+---
+
+## ✨ 核心特性
+
+| 特性 | 说明 |
+|:----|:----|
+| **多语言 AST 解析** | Python AST + 正则适配 Java / TS / Go / Rust 等 |
+| **混合检索** | Qdrant 向量 + BM25 关键词,RRF 融合排序 |
+| **JIT 动态加载** | 问答时自动拉取缺失文件 |
+| **查询重写** | 自然语言 → 代码检索关键词 |
+| **端到端追踪** | Langfuse 集成,全链路可观测 |
+| **自动评估** | LLM-as-Judge 质量评分 |
+
+---
+
+## 🏗 系统架构
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Vue 3 前端 (SSE 流式 + Mermaid 架构图) │
+└─────────────────────┬───────────────────────────────────────┘
+ │
+┌─────────────────────▼───────────────────────────────────────┐
+│ FastAPI 后端 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
+│ │ Agent │ │ Chat │ │ Evaluation │ │
+│ │ Service │ │ Service │ │ Framework │ │
+│ └──────┬──────┘ └──────┬──────┘ └─────────────────────┘ │
+│ │ │ │
+│ ┌──────▼───────────────▼──────┐ ┌─────────────────────┐ │
+│ │ Vector Service (Qdrant+BM25)│ │ Tracing (Langfuse) │ │
+│ └─────────────────────────────┘ └─────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🛠 技术栈
+
+**后端:** Python 3.10+ · FastAPI · AsyncIO · Qdrant · BM25
+**前端:** Vue 3 · Pinia · Mermaid.js · SSE
+**模型:** DeepSeek V3 · SiliconFlow BGE-M3
+**运维:** Docker · Gunicorn · Langfuse
+
+---
+
+## 🏁 快速开始
+
+**前置要求:** Python 3.10+ ·(可选)Node 18+ 用于重新构建前端 · GitHub Token(推荐)· LLM API Key(必需)
+
+```bash
+# 克隆 & 安装
+git clone https://github.com/tzzp1224/RepoReaper.git && cd RepoReaper
+python -m venv venv && source venv/bin/activate
+pip install -r requirements.txt
+
+# 配置 .env(建议从示例复制)
+cp .env.example .env
+# 必需:设置 LLM_PROVIDER 以及对应的 *_API_KEY
+# 推荐:GITHUB_TOKEN 和 SILICON_API_KEY(Embedding)
+
+# (可选)构建前端(仓库已包含 frontend-dist)
+cd frontend-vue
+npm install
+npm run build
+cd ..
+
+# 启动
+python -m app.main
+```
+
+访问 `http://localhost:8000`,输入任意 GitHub 仓库地址开始审计。
+
+**Docker(单容器,本地 Qdrant):**
+```bash
+cp .env.example .env
+docker build -t reporeaper .
+docker run -d -p 8000:8000 --env-file .env reporeaper
+```
+
+**Docker Compose(推荐,包含 Qdrant Server):**
+```bash
+cp .env.example .env
+# 在 .env 中设置 QDRANT_MODE=server 与 QDRANT_URL=http://qdrant:6333
+docker compose up -d --build
+```
+
+---
+
+## 📊 评估与追踪现状
+
+| 组件 | 状态 | 说明 |
+|:----|:----:|:----|
+| **自研评估引擎** | ✅ 可用 | 四层指标(QueryRewrite / Retrieval / Generation / Agentic),LLM-as-Judge 判分 |
+| **在线自动评估** | ✅ 可用 | 每次 `/chat` 结束后异步触发,结果写入 `evaluation/sft_data/` |
+| **数据路由 (SFT)** | ✅ 可用 | 按评分自动分流 Gold/Silver/Bronze/Rejected → JSONL 文件 |
+| **评估 API** | ✅ 可用 | `/evaluate`、`/evaluation/stats`、`/dashboard/*`、`/auto-eval/*` 共 7 个端点 |
+| **离线检索评估** | ✅ 可用 | `test_retrieval.py` — Hit Rate、Recall@K、Precision@K、MRR |
+| **Langfuse 追踪** | ⚠️ 部分完成 | 框架 + 14 处埋点已就位(agent/chat service);不可用时自动降级为本地日志 `logs/traces/` |
+| **Ragas 集成** | ❌ 占位 | 默认 `use_ragas=False`;`_ragas_eval()` 调用方式与最新 Ragas SDK 不兼容 |
+| **Langfuse ↔ 评估** | ❌ 未打通 | 评估结果仅写 JSONL,未上报 Langfuse Scores API |
+
+> **综合完成度约 65%**:自研评估链路已闭环可用;Ragas 与 Langfuse 集成均为半成品。
+
+---
+
+## ⚠️ 已知问题
+
+1. **Python 3.14 + Langfuse 导入报错**
+ `pydantic.V1.errors.ConfigError: unable to infer type for attribute "description"` — Langfuse 3.x 内部依赖 `pydantic.v1` 兼容层,在 Python 3.14 下不兼容。
+ **临时方案:** 在 `.env` 中设置 `LANGFUSE_ENABLED=false`,或使用 Python 3.10–3.12。
+
+2. **`docker-compose.yml` 未包含 Langfuse 服务**
+ 即使导入成功,仍需运行中的 Langfuse 实例。请自行添加或使用 [app.langfuse.com](https://app.langfuse.com)。
+
+3. **Trace 链路未关联**
+ `tracing_service` 记录了 span/event,但调用 Langfuse API 时未传 `trace_id`,Langfuse UI 中只能看到孤立事件而非完整链路树。
+
+4. **Ragas `_ragas_eval()` API 过时**
+ 当前向 `ragas.evaluate()` 传递 dict,最新 Ragas 要求 `Dataset` 对象。已导出 `ragas_eval_dataset.json` 但无脚本消费它。
+
+5. **黄金数据集缺少标准答案**
+ 26 条测试用例的 `expected_answer` 均为空,无法做生成质量的 ground truth 对比。
+
+6. **启发式降级较粗糙**
+ 无 LLM client 时,`faithfulness` 用关键词重叠 + 0.2 基础分;`completeness` 纯粹按字数判断。
+
+---
+
+## 🗺 路线图
+
+- [ ] **修复 Langfuse 兼容性** — 固定 `langfuse`/`pydantic` 版本或按 Python 版本门控导入
+- [ ] **`docker-compose.yml` 加入 Langfuse** — 一键启动本地可观测平台
+- [ ] **串联 trace_id** — 让 Langfuse UI 展示完整链路树
+- [ ] **正式接入 Ragas** — 更新 `_ragas_eval()` 使用 `ragas.evaluate(Dataset(...))`,新增独立评估脚本
+- [ ] **丰富黄金数据集** — 补充 `expected_answer`,扩展至 50+ 条用例
+- [ ] **评估仪表盘前端** — Vue 组件可视化质量分布与 Bad Case
+- [ ] **CI 回归基线** — 在 GitHub Actions 中运行 `test_retrieval.py`,指标回退时失败
+- [ ] **对接 Langfuse Datasets** — 将评估结果推送到 Langfuse Scores/Datasets API,统一可观测
+
+---
+
+## 📈 Star History
+
+
+
+
+
+
+
+
diff --git a/app/core/config.py b/app/core/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfdc2f70187b44d11fe3cf99cbd2664061a7dd4f
--- /dev/null
+++ b/app/core/config.py
@@ -0,0 +1,246 @@
+# 文件路径: app/core/config.py
+"""
+应用配置模块 - 统一配置中心
+
+支持多 LLM 供应商配置:
+- OpenAI (GPT-4, GPT-4o 等)
+- DeepSeek (deepseek-chat 等)
+- Anthropic (Claude 系列)
+- Google Gemini (gemini-3-flash-preview 等)
+"""
+import os
+from dataclasses import dataclass, field
+from typing import Optional, Tuple
+from dotenv import load_dotenv
+
+# 加载 .env 文件
+load_dotenv()
+
+
+# ============================================================
+# Agent 分析配置
+# ============================================================
+
+@dataclass
+class AgentAnalysisConfig:
+ """Agent 分析引擎配置"""
+ # Repo Map 配置
+ initial_map_limit: int = 25 # 初始 Repo Map 文件数量 (提高精度)
+ max_symbols_per_file: int = 40 # 每文件最大符号数 (提高精度)
+
+ # 分析轮次配置
+ max_rounds: int = 4 # 最大分析轮数 (提高精度,因为报告可复用)
+ files_per_round: int = 5 # 每轮选择文件数 (提高精度)
+ max_context_length: int = 20000 # 上下文最大长度 (提高精度)
+
+ # 优先级配置
+ priority_exts: Tuple[str, ...] = (
+ '.py', '.java', '.go', '.js', '.ts', '.tsx', '.cpp', '.cs', '.rs'
+ )
+ priority_keywords: Tuple[str, ...] = (
+ 'main', 'app', 'core', 'api', 'service', 'utils', 'controller', 'model', 'config'
+ )
+
+
+# ============================================================
+# 向量服务配置
+# ============================================================
+
+@dataclass
+class VectorServiceConfig:
+ """向量服务配置"""
+ # 数据目录
+ data_dir: str = "data"
+ context_dir: str = "data/contexts"
+ cache_version: str = "2.0"
+
+ # Embedding 配置
+ embedding_api_url: str = "https://api.siliconflow.cn/v1"
+ embedding_model: str = "BAAI/bge-m3"
+ embedding_batch_size: int = 50
+ embedding_max_length: int = 8000
+ embedding_concurrency: int = 5
+ embedding_dimensions: int = 1024
+
+ # BM25 配置
+ tokenize_regex: str = r'[^a-zA-Z0-9_\.@\u4e00-\u9fa5]+'
+
+ # 混合搜索 RRF 参数
+ rrf_k: int = 60
+ rrf_weight_vector: float = 1.0
+ rrf_weight_bm25: float = 0.3
+ search_oversample: int = 2
+ default_top_k: int = 3
+
+ # Session LRU 缓存配置
+ session_max_count: int = 100 # 内存中最大 session 数
+
+
+# ============================================================
+# 对话记忆配置
+# ============================================================
+
+@dataclass
+class ConversationConfig:
+ """对话记忆配置"""
+ # 滑动窗口
+ max_recent_turns: int = 10 # 保留最近 N 轮对话
+ max_context_tokens: int = 8000 # 最大上下文 token 数
+ summary_threshold: int = 15 # 超过 N 轮开始压缩
+ # 对话记忆是纯内存存储,服务重启自动清空,无需定时清理
+
+
+# ============================================================
+# Qdrant 配置
+# ============================================================
+
+@dataclass
+class QdrantServiceConfig:
+ """
+ Qdrant 向量数据库配置
+
+ 支持三种模式 (通过环境变量 QDRANT_MODE 切换):
+ - local: 本地嵌入式存储 (开发环境, 单 Worker)
+ - server: Qdrant Server Docker (生产环境, 多 Worker)
+ - cloud: Qdrant Cloud 托管服务
+
+ 环境变量:
+ - QDRANT_MODE: "local" | "server" | "cloud"
+ - QDRANT_URL: 服务器 URL (server/cloud 模式)
+ - QDRANT_API_KEY: API 密钥 (cloud 模式必需)
+ - QDRANT_LOCAL_PATH: 本地存储路径 (local 模式)
+ """
+ mode: str = os.getenv("QDRANT_MODE", "local")
+ url: str = os.getenv("QDRANT_URL", "")
+ host: str = os.getenv("QDRANT_HOST", "localhost")
+ port: int = int(os.getenv("QDRANT_PORT", "6333"))
+ grpc_port: int = int(os.getenv("QDRANT_GRPC_PORT", "6334"))
+ prefer_grpc: bool = True
+ api_key: str = os.getenv("QDRANT_API_KEY", "")
+
+ local_path: str = os.getenv("QDRANT_LOCAL_PATH", "data/qdrant_db")
+
+ vector_size: int = 1024 # BGE-M3 维度
+ hnsw_m: int = 16
+ hnsw_ef_construct: int = 100
+ batch_size: int = 100
+ timeout: float = 30.0
+
+
+# ============================================================
+# LLM 供应商配置
+# ============================================================
+
+
+class Settings:
+ """应用配置类"""
+
+ # --- LLM 供应商选择 ---
+ # 支持: "openai", "deepseek", "anthropic", "gemini"
+ LLM_PROVIDER = os.getenv("LLM_PROVIDER", "deepseek")
+
+ # --- API Keys (根据选择的供应商配置对应的 Key) ---
+ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
+
+ # OpenAI
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
+ OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL") # 可选自定义端点
+
+ # DeepSeek
+ DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
+ DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
+
+ # Anthropic (Claude)
+ ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
+
+ # Google Gemini
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
+ GEMINI_BASE_URL = os.getenv("GEMINI_BASE_URL") # 可选 OpenAI 兼容端点
+
+ # SiliconFlow (Embedding)
+ SILICON_API_KEY = os.getenv("SILICON_API_KEY")
+
+ # --- 模型配置 ---
+ # 如果不指定,将使用各供应商的默认模型
+ MODEL_NAME = os.getenv("MODEL_NAME")
+
+ # --- 服务配置 ---
+ HOST = os.getenv("HOST", "127.0.0.1")
+ PORT = int(os.getenv("PORT", 8000))
+
+ # --- LLM 默认参数 ---
+ LLM_TEMPERATURE = float(os.getenv("LLM_TEMPERATURE", "0.1"))
+ LLM_MAX_TOKENS = int(os.getenv("LLM_MAX_TOKENS", "4096"))
+ LLM_TIMEOUT = int(os.getenv("LLM_TIMEOUT", "600"))
+
+ @property
+ def current_api_key(self) -> Optional[str]:
+ """获取当前选择的供应商的 API Key"""
+ key_mapping = {
+ "openai": self.OPENAI_API_KEY,
+ "deepseek": self.DEEPSEEK_API_KEY,
+ "anthropic": self.ANTHROPIC_API_KEY,
+ "gemini": self.GEMINI_API_KEY,
+ }
+ return key_mapping.get(self.LLM_PROVIDER.lower())
+
+ @property
+ def current_base_url(self) -> Optional[str]:
+ """获取当前选择的供应商的 Base URL"""
+ url_mapping = {
+ "openai": self.OPENAI_BASE_URL,
+ "deepseek": self.DEEPSEEK_BASE_URL,
+ "anthropic": None,
+ "gemini": self.GEMINI_BASE_URL,
+ }
+ return url_mapping.get(self.LLM_PROVIDER.lower())
+
+ @property
+ def default_model_name(self) -> str:
+ """获取当前供应商的默认模型名称"""
+ defaults = {
+ "openai": "gpt-4o-mini",
+ "deepseek": "deepseek-chat",
+ "anthropic": "claude-3-5-sonnet-20241022",
+ "gemini": "gemini-3-flash-preview",
+ }
+ return self.MODEL_NAME or defaults.get(self.LLM_PROVIDER.lower(), "default")
+
+ def validate(self):
+ """启动时检查必要的配置是否存在"""
+ provider = self.LLM_PROVIDER.lower()
+ print(f"🔧 LLM Provider: {provider.upper()}")
+
+ # 1. 检查选择的供应商的 API Key
+ if not self.current_api_key:
+ key_name = f"{provider.upper()}_API_KEY"
+ raise ValueError(
+ f"❌ 错误: 缺少 {key_name}。\n"
+ f" 当前选择的 LLM 供应商是: {provider}\n"
+ f" 请在 .env 文件中设置 {key_name},或更改 LLM_PROVIDER 为其他供应商。"
+ )
+
+ # 2. 检查 SiliconCloud Key (Embedding 功能)
+ if not self.SILICON_API_KEY:
+ print("⚠️ 警告: 未找到 SILICON_API_KEY,向量检索功能可能无法工作。")
+
+ # 3. 检查 GitHub Token (可选但建议)
+ if not self.GITHUB_TOKEN:
+ print("⚠️ 警告: 未找到 GITHUB_TOKEN,GitHub API 请求将受到每小时 60 次的严格限制。")
+
+ print(f"✅ 配置验证通过 (Model: {self.default_model_name})")
+
+
+# ============================================================
+# 全局配置实例
+# ============================================================
+
+# LLM 设置
+settings = Settings()
+settings.validate()
+
+# 子系统配置
+agent_config = AgentAnalysisConfig()
+vector_config = VectorServiceConfig()
+conversation_config = ConversationConfig()
+qdrant_config = QdrantServiceConfig()
\ No newline at end of file
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ffa1b5ffced747d94f01a5d2ec033c76decb911
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,560 @@
+# 文件路径: app/main.py
+import sys
+import io
+import os
+import asyncio
+from contextlib import asynccontextmanager
+
+# 强制 stdout 使用 utf-8,防止 Windows 控制台乱码
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
+
+from fastapi import FastAPI, Request
+from fastapi.middleware.cors import CORSMiddleware
+from sse_starlette.sse import EventSourceResponse
+from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
+from fastapi.staticfiles import StaticFiles
+import uvicorn
+
+from app.core.config import settings
+from app.services.agent_service import agent_stream
+from app.services.chat_service import process_chat_stream, get_eval_data, clear_eval_data
+from app.services.vector_service import store_manager
+from app.services.auto_evaluation_service import (
+ init_auto_evaluation_service,
+ get_auto_evaluation_service,
+ EvaluationConfig
+)
+from evaluation.evaluation_framework import EvaluationEngine, EvaluationResult, DataRoutingEngine
+from datetime import datetime
+import uuid
+
+settings.validate()
+
+# === 生命周期管理 ===
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """应用生命周期管理"""
+ from app.services.vector_service import store_manager
+
+ # 启动时运行
+ print("🚀 Application starting...")
+ # 仓库数据永久存储,对话记忆纯内存存储(重启自动清空)
+
+ yield
+
+ # 关闭时运行
+ print("🛑 Application shutting down...")
+
+ # 清理 GitHub 客户端连接
+ from app.utils.github_client import close_github_client
+ await close_github_client()
+
+ # 清理向量存储连接
+ await store_manager.close_all()
+
+ # 关闭共享的 Qdrant 客户端
+ from app.storage.qdrant_store import close_shared_client
+ await close_shared_client()
+
+ print("✅ Cleanup complete")
+
+app = FastAPI(title="GitHub RAG Agent", lifespan=lifespan)
+
+# === 初始化评估引擎 ===
+from app.utils.llm_client import client
+eval_engine = EvaluationEngine(llm_client=client, model_name=settings.default_model_name)
+data_router = DataRoutingEngine()
+
+# === 初始化自动评估服务 (Phase 1) ===
+auto_eval_config = EvaluationConfig(
+ enabled=True,
+ use_ragas=False, # Phase 1: 先不用 Ragas,避免额外依赖
+ async_evaluation=True, # 异步模式,不阻塞响应
+ min_quality_score=0.4, # 最低分数阈值(0.4 = 只拒绝最差的)
+ min_query_length=10, # 最小 query 长度
+ min_answer_length=100, # 最小 answer 长度
+ require_repo_url=True, # 必须有仓库 URL
+ require_code_in_context=True # 上下文必须包含代码
+)
+auto_eval_service = init_auto_evaluation_service(
+ eval_engine=eval_engine,
+ data_router=data_router,
+ config=auto_eval_config
+)
+print("✅ Auto Evaluation Service Initialized")
+
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# === 静态文件与前端 ===
+app.mount("/static", StaticFiles(directory="app"), name="static")
+
+# Vue 3 构建输出的静态资源 (JS/CSS/assets)
+import os
+FRONTEND_DIST = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend-dist")
+if os.path.exists(FRONTEND_DIST):
+ app.mount("/assets", StaticFiles(directory=os.path.join(FRONTEND_DIST, "assets")), name="vue-assets")
+
+@app.get("/", response_class=HTMLResponse)
+async def read_root():
+ # 优先使用 Vue 3 构建版本,否则回退到原版
+ vue_index = os.path.join(FRONTEND_DIST, "index.html")
+ if os.path.exists(vue_index):
+ with open(vue_index, "r", encoding="utf-8") as f:
+ return f.read()
+ # 回退到原版前端
+ with open("frontend/index.html", "r", encoding="utf-8") as f:
+ return f.read()
+
+@app.get("/health")
+def health_check():
+ return {"status": "ok"}
+
+@app.get("/api/sessions")
+async def get_sessions():
+ """获取 session 管理状态"""
+ return JSONResponse(store_manager.get_stats())
+
+@app.post("/api/sessions/cleanup")
+async def trigger_cleanup():
+ """手动触发过期文件清理"""
+ stats = await store_manager.cleanup_expired_files()
+ return JSONResponse({"message": "Cleanup completed", "stats": stats})
+
+@app.delete("/api/sessions/{session_id}")
+async def close_session(session_id: str):
+ """关闭指定 session"""
+ await store_manager.close_session(session_id)
+ return JSONResponse({"message": f"Session {session_id} closed"})
+
+
+# === 仓库级 Session API ===
+
+@app.post("/api/repo/check")
+async def check_repo_session(request: Request):
+ """
+ 检查仓库是否已有指定语言的索引和报告
+
+ 请求: { "url": "https://github.com/owner/repo", "language": "zh" }
+ 响应: {
+ "exists": true/false,
+ "session_id": "repo_xxx",
+ "report": "..." (如果存在对应语言的报告),
+ "has_index": true/false,
+ "available_languages": ["en", "zh"]
+ }
+ """
+ from app.utils.session import generate_repo_session_id
+
+ data = await request.json()
+ repo_url = data.get("url", "").strip()
+ language = data.get("language", "en")
+
+ if not repo_url:
+ return JSONResponse({"error": "Missing URL"}, status_code=400)
+
+ # 生成基于仓库的 Session ID
+ session_id = generate_repo_session_id(repo_url)
+
+ # 检查是否存在
+ store = store_manager.get_store(session_id)
+
+ # 尝试加载上下文
+ context = store.load_context()
+
+ if context and context.get("repo_url"):
+ # 存在已分析的仓库
+ # 获取指定语言的报告
+ report = store.get_report(language)
+ available_languages = store.get_available_languages()
+ global_context = context.get("global_context", {})
+ has_index = bool(global_context.get("file_tree"))
+
+ return JSONResponse({
+ "exists": True,
+ "session_id": session_id,
+ "repo_url": context.get("repo_url"),
+ "report": report, # 指定语言的报告,可能为 None
+ "has_index": has_index,
+ "available_languages": available_languages,
+ "requested_language": language,
+ })
+ else:
+ return JSONResponse({
+ "exists": False,
+ "session_id": session_id,
+ "has_index": False,
+ "available_languages": [],
+ })
+
+
+@app.get("/analyze")
+async def analyze(url: str, session_id: str, language: str = "en", regenerate_only: bool = False):
+ """
+ 仓库分析端点
+
+ Args:
+ url: 仓库 URL
+ session_id: Session ID
+ language: 报告语言 ("en" 或 "zh")
+ regenerate_only: True 时跳过抓取/索引,直接使用已有索引生成新语言报告
+ """
+ if not session_id:
+ return {"error": "Missing session_id"}
+ return EventSourceResponse(agent_stream(url, session_id, language, regenerate_only))
+
+@app.post("/chat")
+async def chat(request: Request):
+ """
+ 聊天端点 - 自动评估版本
+
+ 改进点:
+ 1. 立即返回聊天结果(不阻塞)
+ 2. 后台异步进行自动评估
+ 3. 评估结果自动存储到 evaluation/sft_data/
+ """
+ data = await request.json()
+ user_query = data.get("query")
+ session_id = data.get("session_id")
+ repo_url = data.get("repo_url", "")
+
+ if not user_query:
+ return {"answer": "Please enter your question"}
+ if not session_id:
+ return {"answer": "Session lost"}
+
+ # 标记流是否完成
+ stream_completed = False
+
+ async def chat_stream_with_eval():
+ """包装 process_chat_stream,流结束后触发评估"""
+ nonlocal stream_completed
+
+ # 清除旧的评估数据
+ clear_eval_data(session_id)
+
+ # 执行聊天流
+ async for chunk in process_chat_stream(user_query, session_id):
+ yield chunk
+
+ # 流完成后标记
+ stream_completed = True
+
+ # 流结束后触发评估(此时数据已存储在 chat_service 中)
+ try:
+ auto_eval_service = get_auto_evaluation_service()
+ eval_data = get_eval_data(session_id)
+
+ if auto_eval_service and eval_data and eval_data.answer:
+ print(f"\n📊 [Auto-Eval] Starting evaluation for session {session_id}")
+ print(f" - Query: {user_query[:50]}...")
+ print(f" - Context length: {len(eval_data.retrieved_context)} chars")
+ print(f" - Answer length: {len(eval_data.answer)} chars")
+
+ # 异步执行评估(不阻塞流结束)
+ asyncio.create_task(
+ auto_eval_service.auto_evaluate_async(
+ query=user_query,
+ retrieved_context=eval_data.retrieved_context,
+ generated_answer=eval_data.answer,
+ session_id=session_id,
+ repo_url=repo_url,
+ language="zh" if any('\u4e00' <= c <= '\u9fff' for c in user_query) else "en"
+ )
+ )
+ else:
+ if not auto_eval_service:
+ print("⚠️ Auto evaluation service not initialized")
+ elif not eval_data:
+ print(f"⚠️ No eval data found for session {session_id}")
+ elif not eval_data.answer:
+ print(f"⚠️ Empty answer for session {session_id}")
+ except Exception as e:
+ print(f"⚠️ Failed to trigger auto-eval: {e}")
+ import traceback
+ traceback.print_exc()
+
+ # 返回流
+ return StreamingResponse(
+ chat_stream_with_eval(),
+ media_type="text/plain"
+ )
+
+# ===== Phase 2: 新增评估端点 =====
+
+@app.post("/evaluate")
+async def evaluate(request: Request):
+ """
+ 评估端点: 接收生成结果,进行多维度评估
+
+ POST /evaluate
+ {
+ "query": "用户问题",
+ "retrieved_context": "检索到的文件内容",
+ "generated_answer": "生成的回答",
+ "session_id": "会话ID",
+ "repo_url": "仓库URL(可选)"
+ }
+ """
+ try:
+ data = await request.json()
+
+ # 提取必需字段
+ query = data.get("query")
+ retrieved_context = data.get("retrieved_context", "")
+ generated_answer = data.get("generated_answer")
+ session_id = data.get("session_id", "unknown")
+ repo_url = data.get("repo_url", "")
+
+ if not query or not generated_answer:
+ return {
+ "error": "Missing required fields: query, generated_answer",
+ "status": "failed"
+ }
+
+ # 调用评估引擎获取生成层指标
+ generation_metrics = await eval_engine.evaluate_generation(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer
+ )
+
+ # 构建完整的评估结果对象
+ evaluation_result = EvaluationResult(
+ session_id=session_id,
+ query=query,
+ repo_url=repo_url,
+ timestamp=datetime.now(),
+ language="en",
+ generation_metrics=generation_metrics
+ )
+
+ # 计算综合得分
+ evaluation_result.compute_overall_score()
+
+ # 数据路由: 根据得分将样本分类
+ quality_tier = data_router.route_sample(evaluation_result)
+
+ return {
+ "status": "success",
+ "evaluation": {
+ "faithfulness": generation_metrics.faithfulness,
+ "answer_relevance": generation_metrics.answer_relevance,
+ "answer_completeness": generation_metrics.answer_completeness,
+ "overall_score": evaluation_result.overall_score
+ },
+ "quality_tier": quality_tier,
+ "session_id": session_id
+ }
+
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ return {
+ "error": str(e),
+ "status": "failed"
+ }
+
+
+# ===== 自动评估相关端点 =====
+
+@app.get("/auto-eval/review-queue")
+async def get_review_queue():
+ """
+ 获取需要人工审查的样本列表
+
+ 这些是评估出现异常(自己的分数和Ragas分数差异过大)的样本
+ 需要人工判断哪个评估器更准确
+
+ GET /auto-eval/review-queue
+ """
+ try:
+ auto_eval_service = get_auto_evaluation_service()
+ if not auto_eval_service:
+ return {"error": "Auto evaluation service not initialized", "status": "failed"}
+
+ queue = auto_eval_service.get_review_queue()
+
+ return {
+ "status": "success",
+ "queue_size": len(queue),
+ "samples": [
+ {
+ "index": i,
+ "query": item["eval_result"].query,
+ "custom_score": item["custom_score"],
+ "ragas_score": item["ragas_score"],
+ "diff": item["diff"],
+ "quality_tier": item["eval_result"].data_quality_tier.value,
+ "timestamp": item["timestamp"]
+ }
+ for i, item in enumerate(queue)
+ ]
+ }
+ except Exception as e:
+ return {"error": str(e), "status": "failed"}
+
+
+@app.post("/auto-eval/approve/{index}")
+async def approve_sample(index: int):
+ """
+ 人工批准某个样本(接受该评估结果)
+
+ POST /auto-eval/approve/0
+ """
+ try:
+ auto_eval_service = get_auto_evaluation_service()
+ if not auto_eval_service:
+ return {"error": "Auto evaluation service not initialized", "status": "failed"}
+
+ auto_eval_service.approve_sample(index)
+
+ return {
+ "status": "success",
+ "message": f"Sample {index} approved and stored"
+ }
+ except Exception as e:
+ return {"error": str(e), "status": "failed"}
+
+
+@app.post("/auto-eval/reject/{index}")
+async def reject_sample(index: int):
+ """
+ 人工拒绝某个样本(抛弃该评估结果)
+
+ POST /auto-eval/reject/0
+ """
+ try:
+ auto_eval_service = get_auto_evaluation_service()
+ if not auto_eval_service:
+ return {"error": "Auto evaluation service not initialized", "status": "failed"}
+
+ auto_eval_service.reject_sample(index)
+
+ return {
+ "status": "success",
+ "message": f"Sample {index} rejected and removed from queue"
+ }
+ except Exception as e:
+ return {"error": str(e), "status": "failed"}
+
+
+@app.get("/auto-eval/stats")
+async def auto_eval_stats():
+ """
+ 获取自动评估统计信息
+
+ GET /auto-eval/stats
+ """
+ try:
+ auto_eval_service = get_auto_evaluation_service()
+ if not auto_eval_service:
+ return {"error": "Auto evaluation service not initialized", "status": "failed"}
+
+ queue = auto_eval_service.get_review_queue()
+
+ return {
+ "status": "success",
+ "auto_evaluation": {
+ "enabled": auto_eval_service.config.enabled,
+ "use_ragas": auto_eval_service.config.use_ragas,
+ "async_mode": auto_eval_service.config.async_evaluation,
+ "custom_weight": auto_eval_service.config.custom_weight,
+ "ragas_weight": auto_eval_service.config.ragas_weight,
+ "diff_threshold": auto_eval_service.config.diff_threshold
+ },
+ "review_queue_size": len(queue),
+ "last_update": datetime.now().isoformat()
+ }
+ except Exception as e:
+ return {"error": str(e), "status": "failed"}
+
+
+@app.get("/evaluation/stats")
+async def evaluation_stats():
+ """
+ 获取评估统计信息
+
+ GET /evaluation/stats
+ """
+ try:
+ stats = eval_engine.get_statistics()
+ return {
+ "status": "success",
+ "statistics": {
+ "total_evaluations": stats.get("total_evaluations", 0),
+ "average_score": stats.get("average_score", 0),
+ "quality_distribution": stats.get("quality_distribution", {}),
+ "top_issues": stats.get("top_issues", [])
+ }
+ }
+ except Exception as e:
+ return {
+ "error": str(e),
+ "status": "failed"
+ }
+
+
+@app.get("/dashboard/quality-distribution")
+async def quality_distribution():
+ """
+ 获取数据质量分布 (用于仪表盘)
+
+ GET /dashboard/quality-distribution
+ """
+ try:
+ distribution = data_router.get_distribution()
+ return {
+ "status": "success",
+ "distribution": {
+ "gold": distribution.get("gold", 0),
+ "silver": distribution.get("silver", 0),
+ "bronze": distribution.get("bronze", 0),
+ "rejected": distribution.get("rejected", 0),
+ "corrected": distribution.get("corrected", 0)
+ },
+ "timestamp": datetime.now().isoformat()
+ }
+ except Exception as e:
+ return {
+ "error": str(e),
+ "status": "failed"
+ }
+
+
+@app.get("/dashboard/bad-cases")
+async def bad_cases():
+ """
+ 获取低质量样本 (用于人工审核)
+
+ GET /dashboard/bad-cases
+ """
+ try:
+ bad_samples = data_router.get_bad_samples(limit=10)
+ return {
+ "status": "success",
+ "bad_cases": [
+ {
+ "query": s.get("query", ""),
+ "issue": s.get("issue", ""),
+ "score": s.get("score", 0)
+ }
+ for s in bad_samples
+ ],
+ "total_bad_cases": len(bad_samples)
+ }
+ except Exception as e:
+ return {
+ "error": str(e),
+ "status": "failed"
+ }
+
+
+if __name__ == "__main__":
+ # 生产模式建议关掉 reload
+ uvicorn.run("app.main:app", host=settings.HOST, port=settings.PORT, reload=False)
\ No newline at end of file
diff --git a/app/services/agent_service.py b/app/services/agent_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..352be08436e9dc7559a80a30388f48657911e3b2
--- /dev/null
+++ b/app/services/agent_service.py
@@ -0,0 +1,779 @@
+# 文件路径: app/services/agent_service.py
+import json
+import asyncio
+import traceback
+import re
+import ast
+import httpx
+import time
+from typing import Set, Tuple, List
+from datetime import datetime
+from app.core.config import settings, agent_config
+from app.utils.llm_client import client
+from app.utils.repo_lock import RepoLock
+from app.services.github_service import get_repo_structure, get_file_content
+from app.services.vector_service import store_manager
+from app.services.chunking_service import UniversalChunker, ChunkingConfig
+from app.services.tracing_service import tracing_service
+from evaluation.evaluation_framework import EvaluationEngine, EvaluationResult, DataRoutingEngine
+
+# === Helper: 鲁棒的 JSON 提取 ===
+def extract_json_from_text(text):
+ try:
+ text = re.sub(r"^```(json)?|```$", "", text.strip(), flags=re.MULTILINE).strip()
+ return json.loads(text)
+ except:
+ pass
+ match = re.search(r"\[.*\]", text, re.DOTALL)
+ if match:
+ try: return json.loads(match.group(0))
+ except: pass
+ return []
+
+# === 多语言符号提取 ===
+def _extract_symbols(content, file_path):
+ """
+ 根据文件类型,智能提取 Class 和 Function 签名生成地图。
+ """
+ ext = file_path.split('.')[-1].lower() if '.' in file_path else ""
+
+ # 1. Python 使用 AST (最准)
+ if ext == 'py':
+ return _extract_symbols_python(content)
+
+ # 2. 其他语言使用正则 (Java, TS, JS, Go, C++)
+ elif ext in ['java', 'ts', 'tsx', 'js', 'jsx', 'go', 'cpp', 'cs', 'rs']:
+ return _extract_symbols_regex(content, ext)
+
+ return []
+
+def _extract_symbols_python(content):
+ try:
+ tree = ast.parse(content)
+ symbols = []
+ for node in tree.body:
+ if isinstance(node, ast.ClassDef):
+ symbols.append(f" [C] {node.name}")
+ for sub in node.body:
+ if isinstance(sub, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ if not sub.name.startswith("_") or sub.name == "__init__":
+ symbols.append(f" - {sub.name}")
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ symbols.append(f" [F] {node.name}")
+ return symbols
+ except:
+ return []
+
+def _extract_symbols_regex(content, ext):
+ """
+ 针对类 C 语言的通用正则提取。
+ """
+ symbols = []
+ lines = content.split('\n')
+
+ # 定义各语言的正则模式
+ patterns = {
+ 'java': {
+ 'class': re.compile(r'(?:public|protected|private)?\s*(?:static|abstract)?\s*(?:class|interface|enum)\s+([a-zA-Z0-9_]+)'),
+ 'func': re.compile(r'(?:public|protected|private)\s+(?:static\s+)?[\w<>[\]]+\s+([a-zA-Z0-9_]+)\s*\(')
+ },
+ 'ts': {
+ 'class': re.compile(r'class\s+([a-zA-Z0-9_]+)'),
+ 'func': re.compile(r'(?:function\s+([a-zA-Z0-9_]+)|const\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\(|([a-zA-Z0-9_]+)\s*\([^)]*\)\s*[:\{])')
+ },
+ 'go': {
+ 'class': re.compile(r'type\s+([a-zA-Z0-9_]+)\s+(?:struct|interface)'),
+ 'func': re.compile(r'func\s+(?:(?:\(.*\)\s+)?([a-zA-Z0-9_]+)|([a-zA-Z0-9_]+)\()')
+ }
+ }
+
+ lang_key = 'java' if ext in ['java', 'cs', 'cpp', 'rs'] else 'go' if ext == 'go' else 'ts'
+ rules = patterns.get(lang_key, patterns['java'])
+
+ count = 0
+ for line in lines:
+ line = line.strip()
+ # === 正则解析优化 (过滤更多干扰项) ===
+ if not line or line.startswith(("//", "/*", "*", "#", "print", "console.")): continue
+ if count > agent_config.max_symbols_per_file: break
+
+ # 匹配类
+ c_match = rules['class'].search(line)
+ if c_match:
+ name = next((g for g in c_match.groups() if g), "Unknown")
+ symbols.append(f" [C] {name}")
+ count += 1
+ continue
+
+ # 匹配方法
+ if line.endswith('{') or "=>" in line:
+ f_match = rules['func'].search(line)
+ if f_match:
+ name = next((g for g in f_match.groups() if g), None)
+ # 增强过滤
+ if name and len(name) > 2 and name not in ['if', 'for', 'switch', 'while', 'catch', 'return']:
+ symbols.append(f" - {name}")
+ count += 1
+
+ return symbols
+
+async def generate_repo_map(repo_url, file_list, limit=agent_config.initial_map_limit) -> Tuple[str, Set[str]]:
+ """
+ 生成增强版仓库地图 (多语言版)
+ Returns:
+ str: 地图字符串
+ set: 已包含在地图中的文件路径集合 (用于增量更新查重)
+ """
+ # === 扩展高优先级文件列表 (使用配置) ===
+ priority_files = [
+ f for f in file_list
+ if f.endswith(agent_config.priority_exts) and
+ (f.count('/') <= 2 or any(k in f.lower() for k in agent_config.priority_keywords))
+ ]
+
+ # 去重并截取
+ targets = sorted(list(set(priority_files)))[:limit]
+ remaining = [f for f in file_list if f not in targets]
+
+ repo_map_lines = []
+ mapped_files_set = set(targets) # === 记录已映射的文件 ===
+
+ async def process_file(path):
+ content = await get_file_content(repo_url, path)
+ if not content: return f"{path} (Read Failed)"
+
+ symbols = await asyncio.to_thread(_extract_symbols, content, path)
+
+ if symbols:
+ return f"{path}\n" + "\n".join(symbols)
+ return path
+
+ repo_map_lines.append(f"--- Key Files Structure (Top {len(targets)}) ---")
+
+ tasks = [process_file(f) for f in targets]
+ results = await asyncio.gather(*tasks)
+ repo_map_lines.extend(results)
+
+ if remaining:
+ repo_map_lines.append("\n--- Other Files ---")
+ if len(remaining) > 300:
+ repo_map_lines.extend(remaining[:300])
+ repo_map_lines.append(f"... ({len(remaining)-300} more files)")
+ else:
+ repo_map_lines.extend(remaining)
+
+ return "\n".join(repo_map_lines), mapped_files_set
+
+
+async def agent_stream(repo_url: str, session_id: str, language: str = "en", regenerate_only: bool = False):
+ """
+ 主分析流程。
+
+ Args:
+ repo_url: GitHub 仓库 URL
+ session_id: 会话 ID
+ language: 报告语言 (zh/en)
+ regenerate_only: 如果为 True,跳过索引步骤,直接使用已有数据生成新语言报告
+ """
+ short_id = session_id[-6:] if session_id else "unknown"
+
+ # === 追踪初始化 ===
+ trace_id = tracing_service.start_trace(
+ trace_name="agent_analysis",
+ session_id=session_id,
+ metadata={"repo_url": repo_url, "language": language, "regenerate_only": regenerate_only}
+ )
+ start_time = time.time()
+
+ # === 检查是否有其他用户正在分析同一仓库 ===
+ if not regenerate_only:
+ if await RepoLock.is_locked(session_id):
+ yield json.dumps({
+ "step": "waiting",
+ "message": f"⏳ Another user is analyzing this repository. Please wait..."
+ })
+
+ # === 获取仓库锁 (仅写操作需要) ===
+ try:
+ async with RepoLock.acquire(session_id):
+ async for event in _agent_stream_inner(
+ repo_url, session_id, language, regenerate_only,
+ short_id, trace_id, start_time
+ ):
+ yield event
+ except TimeoutError as e:
+ yield json.dumps({
+ "step": "error",
+ "message": f"❌ {str(e)}. The repository is being analyzed by another user."
+ })
+
+
+async def _agent_stream_inner(
+ repo_url: str, session_id: str, language: str, regenerate_only: bool,
+ short_id: str, trace_id: str, start_time: float
+):
+ """
+ 实际的分析流程 (在锁保护下执行)
+ """
+ try:
+ vector_db = store_manager.get_store(session_id)
+
+ # 调试日志:确认 session 隔离
+ print(f"🔍 [DEBUG] session_id: {session_id}, collection: {vector_db.collection_name}, context_file: {vector_db._context_file}")
+
+ # === regenerate_only 模式:跳过索引,直接生成报告 ===
+ if regenerate_only:
+ yield json.dumps({"step": "init", "message": f"🔄 [Session: {short_id}] Regenerating report in {language}..."})
+ await asyncio.sleep(0.3)
+
+ # 从已有索引加载上下文
+ context = vector_db.load_context()
+ if not context:
+ yield json.dumps({"step": "error", "message": "❌ No existing index found. Please analyze the repository first."})
+ return
+
+ # 正确读取 global_context 内的字段
+ global_ctx = context.get("global_context", {})
+ file_tree_str = global_ctx.get("file_tree", "")
+ context_summary = global_ctx.get("summary", "")
+ visited_files = set() # regenerate 模式不需要这个,但报告生成需要引用
+
+ # 验证上下文与请求的仓库匹配
+ stored_repo_url = context.get("repo_url", "")
+ if stored_repo_url and repo_url not in stored_repo_url and stored_repo_url not in repo_url:
+ print(f"⚠️ [WARNING] repo_url mismatch! Request: {repo_url}, Stored: {stored_repo_url}")
+
+ yield json.dumps({"step": "generating", "message": f"📝 Generating report in {'Chinese' if language == 'zh' else 'English'}..."})
+ else:
+ # === 正常分析模式 ===
+ yield json.dumps({"step": "init", "message": f"🚀 [Session: {short_id}] Connecting to GitHub..."})
+ await asyncio.sleep(0.5)
+
+ await vector_db.reset() # 使用异步方法
+
+ chunker = UniversalChunker(config=ChunkingConfig(min_chunk_size=50))
+
+ file_list = await get_repo_structure(repo_url)
+ if not file_list:
+ raise Exception("Repository is empty or unreadable.")
+
+ yield json.dumps({"step": "fetched", "message": f"📦 Found {len(file_list)} files. Building Repo Map (AST Parsing)..."})
+
+ # === 接收 mapped_files 用于后续查重 + 计时 ===
+ map_start = time.time()
+ file_tree_str, mapped_files = await generate_repo_map(repo_url, file_list, limit=agent_config.initial_map_limit)
+ map_latency_ms = (time.time() - map_start) * 1000
+ tracing_service.add_event("repo_map_generated", {"latency_ms": map_latency_ms, "files_mapped": len(mapped_files)})
+
+ visited_files = set()
+ context_summary = ""
+ readme_file = next((f for f in file_list if f.lower().endswith("readme.md")), None)
+
+ for round_idx in range(agent_config.max_rounds):
+ yield json.dumps({"step": "thinking", "message": f"🕵️ [Round {round_idx+1}/{agent_config.max_rounds}] DeepSeek is analyzing Repo Map..."})
+
+ system_prompt = "You are a Senior Software Architect. Your goal is to understand the codebase."
+ user_content = f"""
+ [Project Repo Map]
+ (Contains file paths and key Class/Function signatures)
+ {file_tree_str}
+
+ [Files Already Read]
+ {list(visited_files)}
+
+ [Current Knowledge]
+ {context_summary}
+
+ [Task]
+ Select 1-{agent_config.files_per_round} MOST CRITICAL files to read next to understand the core logic.
+ Focus on files that seem to contain main logic based on the Repo Map symbols.
+
+ [Constraint]
+ Return ONLY a raw JSON list of strings. No markdown.
+ Example: ["src/main.py", "app/auth.py"]
+ """
+
+ if not client:
+ yield json.dumps({"step": "error", "message": "❌ LLM Client Not Initialized."})
+ return
+
+ # === Token & Latency Tracing ===
+ llm_start_time = time.time()
+ plan_messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": user_content}
+ ]
+
+ response = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=plan_messages,
+ temperature=0.1,
+ timeout=settings.LLM_TIMEOUT
+ )
+
+ llm_latency_ms = (time.time() - llm_start_time) * 1000
+ raw_content = response.choices[0].message.content
+
+ # 记录 Token 使用量
+ usage = getattr(response, 'usage', None)
+ tracing_service.record_llm_generation(
+ model=settings.default_model_name,
+ prompt_messages=plan_messages,
+ generated_text=raw_content,
+ total_latency_ms=llm_latency_ms,
+ prompt_tokens=usage.prompt_tokens if usage else None,
+ completion_tokens=usage.completion_tokens if usage else None,
+ total_tokens=usage.total_tokens if usage else None,
+ is_streaming=False,
+ metadata={"step": "file_selection", "round": round_idx + 1}
+ )
+ target_files = extract_json_from_text(raw_content)
+
+ valid_files = [f for f in target_files if f in file_list and f not in visited_files]
+
+ if round_idx == 0 and readme_file and readme_file not in visited_files and readme_file not in valid_files:
+ valid_files.insert(0, readme_file)
+
+ if not valid_files:
+ yield json.dumps({"step": "plan", "message": f"🛑 [Round {round_idx+1}] Sufficient context gathered."})
+ break
+
+ yield json.dumps({"step": "plan", "message": f"👉 [Round {round_idx+1}] Selected: {valid_files}"})
+
+ # === 并发模型缺陷优化 (并行下载处理) ===
+ async def process_single_file(file_path):
+ try:
+ file_start = time.time()
+
+ # 🔧 异步 GitHub API (已优化为非阻塞)
+ content = await get_file_content(repo_url, file_path)
+ if not content:
+ tracing_service.add_event("file_read_failed", {"file": file_path})
+ return None
+
+ # 1. 摘要与 Context
+ lines = content.split('\n')[:50]
+ preview = "\n".join(lines)
+ file_knowledge = f"\n--- File: {file_path} ---\n{preview}\n"
+
+ # 2. Repo Map 增量更新与查重
+ new_map_entry = None
+ if file_path not in mapped_files:
+ symbols = await asyncio.to_thread(_extract_symbols, content, file_path)
+ if symbols:
+ new_map_entry = f"{file_path}\n" + "\n".join(symbols)
+
+ # 3. 切片与入库
+ chunks = await asyncio.to_thread(chunker.chunk_file, content, file_path)
+ if chunks:
+ documents = [c["content"] for c in chunks]
+ metadatas = []
+ for c in chunks:
+ meta = c["metadata"]
+ metadatas.append({
+ "file": meta["file"],
+ "type": meta["type"],
+ "name": meta.get("name", ""),
+ "class": meta.get("class") or ""
+ })
+ if documents:
+ try:
+ await vector_db.add_documents(documents, metadatas)
+ except Exception as e:
+ print(f"❌ 索引错误 {file_path}: {e}")
+ # 不中断,继续处理其他文件
+ return None
+
+ file_latency_ms = (time.time() - file_start) * 1000
+ tracing_service.add_event("file_processed", {
+ "file": file_path,
+ "latency_ms": file_latency_ms,
+ "chunks_count": len(chunks) if chunks else 0
+ })
+
+ return {
+ "path": file_path,
+ "knowledge": file_knowledge,
+ "map_entry": new_map_entry
+ }
+ except Exception as e:
+ print(f"❌ 处理文件错误 {file_path}: {e}")
+ return None
+
+ # 提示开始并发下载
+ yield json.dumps({"step": "download", "message": f"📥 Starting parallel download for {len(valid_files)} files..."})
+
+ # 启动并发任务 (return_exceptions=True 防止单个失败导致整个中断)
+ tasks = [process_single_file(f) for f in valid_files]
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+
+ # 聚合结果
+ download_count = 0
+ for res in results:
+ if not res or isinstance(res, Exception):
+ if isinstance(res, Exception):
+ print(f"❌ Task 异常: {res}")
+ continue
+ download_count += 1
+ visited_files.add(res["path"])
+ context_summary += res["knowledge"]
+
+ # 增量更新 Map
+ if res["map_entry"]:
+ file_tree_str = f"{res['map_entry']}\n\n{file_tree_str}"
+ mapped_files.add(res["path"])
+
+ # === 硬编码截断解耦 ===
+ context_summary = context_summary[:agent_config.max_context_length]
+
+ global_context_data = {
+ "file_tree": file_tree_str,
+ "summary": context_summary[:8000]
+ }
+ await vector_db.save_context(repo_url, global_context_data)
+
+ yield json.dumps({"step": "indexing", "message": f"🧠 [Round {round_idx+1}] Processed {download_count} files. Knowledge graph updated."})
+
+ # Final Report (正常分析模式下的提示)
+ yield json.dumps({"step": "generating", "message": "📝 Generating technical report..."})
+
+ # === 报告生成 (两种模式共用) ===
+
+ # === P0: 向量检索补充关键代码片段 ===
+ yield json.dumps({"step": "enriching", "message": "🔍 Retrieving key code snippets..."})
+
+ key_queries = [
+ "main entry point initialization startup",
+ "core business logic handler processor",
+ "API routes endpoints controllers",
+ "database models schema ORM",
+ "authentication authorization middleware"
+ ]
+
+ retrieved_snippets = []
+ try:
+ await vector_db.initialize()
+ for query in key_queries:
+ results = await vector_db.search_hybrid(query, top_k=2)
+ for r in results:
+ snippet = r.get("content", "")[:400]
+ file_path = r.get("file", "unknown")
+ if snippet and snippet not in [s.split("]")[1] if "]" in s else s for s in retrieved_snippets]:
+ retrieved_snippets.append(f"[{file_path}]\n{snippet}")
+ except Exception as e:
+ print(f"⚠️ 向量检索失败: {e}")
+
+ code_snippets_section = "\n\n".join(retrieved_snippets[:8]) if retrieved_snippets else ""
+
+ # === P1: 依赖文件解析 ===
+ dep_files = ["requirements.txt", "pyproject.toml", "package.json", "go.mod", "Cargo.toml", "pom.xml", "build.gradle"]
+ dependencies_info = ""
+
+ # 获取 file_list(regenerate_only 模式下需要重新获取)
+ if regenerate_only:
+ try:
+ temp_file_list = await get_repo_structure(repo_url)
+ except:
+ temp_file_list = []
+ else:
+ temp_file_list = file_list if 'file_list' in dir() else []
+
+ for dep_file in dep_files:
+ matching = [f for f in temp_file_list if f.endswith(dep_file)]
+ for f in matching[:1]: # 只取第一个匹配
+ try:
+ content = await get_file_content(repo_url, f)
+ if content:
+ dependencies_info += f"\n[{f}]\n{content[:800]}\n"
+ except:
+ pass
+
+ # 构建增强的上下文
+ enhanced_context = f"""
+ {context_summary[:12000]}
+
+ [Key Code Snippets (Retrieved by Semantic Search)]
+ {code_snippets_section}
+
+ [Project Dependencies]
+ {dependencies_info if dependencies_info else "No dependency file found."}
+ """
+
+ repo_map_injection = f"""
+ [Project Repo Map (Structure)]
+ {file_tree_str}
+ """
+
+ # === 根据语言选择 Prompt ===
+ if language == "zh":
+ # --- 中文 Prompt ---
+ system_role = "你是一位务实的技术专家。目标是为开发者创建一个'3页纸'架构概览,让他们能在5分钟内看懂这个仓库。重点关注架构和数据流,不要纠结细节。"
+ analysis_user_content = f"""
+ [角色]
+ 你是一位务实的技术专家(Tech Lead)。
+
+ [输入数据]
+ {repo_map_injection}
+
+ 分析的文件: {list(visited_files)}
+
+ [代码知识库与关键片段]
+ {enhanced_context}
+
+ [严格限制]
+ 1. **不进行代码审查**: 不要列出 Bug、缺失功能或改进建议。
+ 2. **不评价**: 不要评价代码质量,只描述它**如何工作**。
+ 3. **语调**: 专业、结构化、描述性。使用中文回答。
+ 4. **不要废话**: 不要写"安全性"、"未来规划"等未请求的章节。
+
+ [输出格式要求 (Markdown)]
+
+ # 项目分析报告
+
+ ## 1. 执行摘要 (Executive Summary)
+ - **用途**: (这个项目具体解决什么问题?1-2句话)
+ - **核心功能**: (列出Top 3功能点)
+ - **技术栈**: (语言、框架、数据库、关键库)
+
+ ## 2. 系统架构 (Mermaid)
+ 创建一个 `graph TD` 图。
+ - 展示高层组件 (如 Client, API Server, Database, Worker, External Service)。
+ - 在连线上标注数据流 (如 "HTTP", "SQL")。
+ - **风格**: 保持概念清晰简单,节点数量控制在 8 个以内。
+
+ **⚠️ Mermaid 语法严格要求 (v10.x)**:
+ 1. **所有节点文本必须用双引号包裹**: `A["用户界面"]` ✓, `A[用户界面]` ✗
+ 2. **所有连线标签必须用双引号包裹**: `-->|"HTTP请求"|` ✓, `-->|HTTP请求|` ✗
+ 3. **禁止使用特殊字符**: 不要在文本中使用 `
`, `/`, `(`, `)`, `&`, `<`, `>` 等
+ 4. **使用简短英文ID**: 节点ID用简短英文如 `A`, `B`, `Client`, `API`
+ 5. **subgraph 标题也需引号**: `subgraph "核心服务"` ✓
+ 6. **数据库节点**: 使用 `[("数据库")]` 格式
+
+ - **正确示例**:
+ ```mermaid
+ graph TD
+ Client["客户端"] -->|"HTTP请求"| API["API网关"]
+ API --> Service["业务服务"]
+ Service --> DB[("数据库")]
+ Service -->|"调用"| External["外部服务"]
+ ```
+
+ ## 3. 核心逻辑分析 (Table)
+ (总结关键模块,不要列出所有文件,只列最重要的)
+
+ | 组件/文件 | 职责 (它做什么?) | 关键设计模式/逻辑 |
+ | :--- | :--- | :--- |
+ | 例如 `auth_service.py` | 处理JWT颁发与验证 | 单例模式, 路由装饰器 |
+ | ... | ... | ... |
+
+ ## 4. 🔬 核心方法深度解析
+ (精选 3-5 个最关键的 `.py` 文件。针对每个文件,列出驱动逻辑的 Top 2-3 个方法)
+
+ ### 4.1 `[文件名]`
+ * **`[方法名]`**: [解释它做什么以及为什么重要,不要贴代码]
+ * **`[方法名]`**: [解释...]
+
+ ## 5. 主要工作流 (Mermaid)
+ 选择**一个最重要**的业务流程 (Happy Path)。
+ 创建一个 `sequenceDiagram`。
+ - 参与者应该是高层概念 (如 User, API, DB),不要用具体变量名。
+
+ **⚠️ sequenceDiagram 语法要求**:
+ 1. **participant 别名格式**: `participant API as "API服务"` ✓
+ 2. **消息文本用双引号**: `User->>API: "发起请求"` ✓
+ 3. **避免特殊字符**: 不要在消息中使用 `/`, `&`, `<`, `>` 等
+
+ - **正确示例**:
+ ```mermaid
+ sequenceDiagram
+ participant User as "用户"
+ participant API as "API服务"
+ participant DB as "数据库"
+ User->>API: "发起请求"
+ API->>DB: "查询数据"
+ DB-->>API: "返回结果"
+ API-->>User: "响应数据"
+ ```
+
+ ## 6. 快速开始 (Quick Start)
+ - **前置条件**: (如 Docker, Python 3.9+, .env 配置)
+ - **入口**: (如何启动主逻辑?如 `python main.py`)
+ """
+ else:
+ analysis_user_content = f"""
+ [Role]
+ You are a **Pragmatic Tech Lead**. Your goal is to create a **"3-Pages" Architecture Overview** for a developer who wants to understand this repo in 5 minutes.
+ [Input Data]
+ {repo_map_injection}
+
+ Files analyzed: {list(visited_files)}
+
+ [Code Knowledge & Key Snippets]
+ {enhanced_context}
+
+ [Strict Constraints]
+ 1. **NO Code Review**: Do NOT list bugs, issues, missing features, or recommendations.
+ 2. **NO Critique**: Do not judge the code quality. Focus on HOW it works.
+ 3. **Tone**: Professional, descriptive, and structural.
+ 4. **NO "FLUFF"**: Do NOT add unrequested sections like "Security", "Scalability", "Data Models", "Future Enhancements", etc.
+
+ [Required Output Format (Markdown)]
+
+ # Project Analysis Report
+
+ ## 1. Executive Summary
+ - **Purpose**: (What specific problem does this project solve? 1-2 sentences)
+ - **Key Features**: (Bullet points of top 3 features)
+ - **Tech Stack**: (List languages, frameworks, databases, and key libs)
+
+ ## 2. System Architecture
+ Create a `graph TD` diagram.
+ - Show high-level components (e.g., Client, API Server, Database, Worker, External Service).
+ - Label the edges with data flow (e.g., "HTTP", "SQL").
+ - **Style**: Keep it simple and conceptual. Limit to 8 nodes max.
+
+ **⚠️ Mermaid Syntax Rules (v10.x - MUST FOLLOW)**:
+ 1. **Wrap ALL node text in double quotes**: `A["User Client"]` ✓, `A[User Client]` ✗
+ 2. **Wrap ALL edge labels in double quotes**: `-->|"HTTP Request"|` ✓, `-->|HTTP Request|` ✗
+ 3. **NO special characters in text**: Avoid `/`, `()`, `&`, `<>`, `
` in labels
+ 4. **Use short alphanumeric IDs**: e.g., `A`, `B`, `Client`, `API`, `DB`
+ 5. **Subgraph titles need quotes**: `subgraph "Core Services"` ✓
+ 6. **Database node format**: Use `[("Database")]` for cylinder shape
+
+ - **Correct Example**:
+ ```mermaid
+ graph TD
+ Client["User Client"] -->|"HTTP Request"| API["API Gateway"]
+ API --> Service["Business Service"]
+ Service --> DB[("Database")]
+ Service -->|"Calls"| External["External API"]
+ ```
+
+ ## 3. Core Logic Analysis
+ (Create a Markdown Table to summarize key modules. Do not list every file, only the most important ones.)
+
+ | Component/File | Responsibility (What does it do?) | Key Design Pattern / Logic |
+ | :--- | :--- | :--- |
+ | e.g. `auth_service.py` | Handles JWT issuance and verification | Singleton, Decorator for routes |
+ | ... | ... | ... |
+
+ ## 4. Core Methods Deep Dive
+ (Select the 3-5 most critical `.py` files. For each, list the top 2-3 methods that drive the logic.)
+
+ ### 4.1 `[Filename, e.g., agent_service.py]`
+ * **`[Method Name]`**: [Explanation of what it does and why it matters. No code.]
+ * **`[Method Name]`**: [Explanation...]
+
+ ### 4.2 `[Filename, e.g., vector_service.py]`
+ * **`[Method Name]`**: [Explanation...]
+ * ...
+
+ ## 5. Main Workflow (Mermaid)
+ Select the **Single Most Important** business flow (The "Happy Path").
+ Create a `sequenceDiagram`.
+ - Participants should be high-level (e.g., User, API, DB), not specific variable names.
+
+ **⚠️ sequenceDiagram Syntax Rules**:
+ 1. **Wrap participant aliases in quotes**: `participant API as "API Server"` ✓
+ 2. **Wrap message text in quotes**: `User->>API: "Send Request"` ✓
+ 3. **NO special characters**: Avoid `/`, `&`, `<`, `>` in messages
+
+ - **Correct Example**:
+ ```mermaid
+ sequenceDiagram
+ participant User as "User"
+ participant API as "API Server"
+ participant DB as "Database"
+ User->>API: "Send Request"
+ API->>DB: "Query Data"
+ DB-->>API: "Return Result"
+ API-->>User: "Send Response"
+ ```
+
+ ## 6. Quick Start Guide
+ - **Prerequisites**: (e.g. Docker, Python 3.9+, .env file)
+ - **Entry Point**: (How to run the main logic? e.g. `python main.py` or `uvicorn`)
+
+ """
+
+ # === 增加 timeout 防止长文本生成时断连 ===
+ report_messages = [
+ {"role": "system", "content": "You are a pragmatic Tech Lead. Focus on architecture and data flow, not implementation details."},
+ {"role": "user", "content": analysis_user_content}
+ ]
+
+ stream_start_time = time.time()
+ stream = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=report_messages,
+ stream=True,
+ timeout=settings.LLM_TIMEOUT # 使用统一配置
+ )
+
+ # === TTFT & Token Tracking ===
+ first_token_received = False
+ ttft_ms = None
+ generated_text = ""
+ completion_tokens_estimate = 0
+
+ # === 增加 try-except 捕获流式传输中断 ===
+ try:
+ async for chunk in stream:
+ if chunk.choices[0].delta.content:
+ content = chunk.choices[0].delta.content
+
+ # 记录 TTFT (首 Token 时间)
+ if not first_token_received:
+ ttft_ms = (time.time() - stream_start_time) * 1000
+ tracing_service.record_ttft(
+ ttft_ms=ttft_ms,
+ model=settings.default_model_name,
+ metadata={"step": "report_generation"}
+ )
+ first_token_received = True
+
+ generated_text += content
+ completion_tokens_estimate += 1 # 粗略估计每个 chunk 约 1 token
+ yield json.dumps({"step": "report_chunk", "chunk": content})
+ except (httpx.ReadError, httpx.ConnectError) as e:
+ yield json.dumps({"step": "error", "message": f"⚠️ Network Timeout during generation: {str(e)}"})
+ return
+
+ # 流结束后记录完整的 LLM 生成信息
+ total_latency_ms = (time.time() - stream_start_time) * 1000
+ tracing_service.record_llm_generation(
+ model=settings.default_model_name,
+ prompt_messages=report_messages,
+ generated_text=generated_text,
+ ttft_ms=ttft_ms,
+ total_latency_ms=total_latency_ms,
+ completion_tokens=completion_tokens_estimate,
+ is_streaming=True,
+ metadata={"step": "report_generation", "generated_chars": len(generated_text)}
+ )
+
+ # === 保存报告 (按语言存储,异步避免阻塞) ===
+ await vector_db.save_report(generated_text, language)
+
+ yield json.dumps({"step": "finish", "message": "✅ Analysis Complete!"})
+
+ except Exception as e:
+ # === 全局异常捕获 ===
+ import traceback
+ traceback.print_exc()
+
+ # 提取友好的错误信息
+ error_msg = str(e)
+ if "401" in error_msg:
+ ui_msg = "❌ GitHub Token Invalid. Please check your settings."
+ elif "403" in error_msg:
+ ui_msg = "❌ GitHub API Rate Limit Exceeded. Try again later or add a Token."
+ elif "404" in error_msg:
+ ui_msg = "❌ Repository Not Found. Check the URL."
+ elif "Timeout" in error_msg or "ConnectError" in error_msg:
+ ui_msg = "❌ Network Timeout. LLM or GitHub is not responding."
+ else:
+ ui_msg = f"💥 System Error: {error_msg}"
+
+ yield json.dumps({"step": "error", "message": ui_msg})
+ return # 终止流
\ No newline at end of file
diff --git a/app/services/auto_evaluation_service.py b/app/services/auto_evaluation_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebd205cee8dd4df99a846ae3b968d6b8d8c625aa
--- /dev/null
+++ b/app/services/auto_evaluation_service.py
@@ -0,0 +1,481 @@
+# 文件路径: app/services/auto_evaluation_service.py
+"""
+自动评估服务 - Phase 1
+在后台异步进行评估,不阻塞用户请求
+
+工作流程:
+ 1. 用户调用 /chat 或 /analyze
+ 2. 获得立即响应
+ 3. 后台异步执行评估
+ 4. 评估结果存储到 evaluation/sft_data/
+"""
+
+import asyncio
+import json
+import os
+from datetime import datetime
+from typing import Optional
+from dataclasses import dataclass
+
+from evaluation.evaluation_framework import (
+ EvaluationEngine,
+ EvaluationResult,
+ DataRoutingEngine,
+ DataQualityTier
+)
+from evaluation.utils import is_chatty_query, has_code_indicators
+from app.services.tracing_service import tracing_service
+
+
+@dataclass
+class EvaluationConfig:
+ """
+ 自动评估配置
+
+ 数据路由阈值说明(与 data_router.py 一致):
+ - score > 0.9 → Gold → positive_samples.jsonl
+ - score > 0.6 → Silver → positive_samples.jsonl
+ - score > 0.4 → Bronze → negative_samples.jsonl
+ - score <= 0.4 → Rejected → 不存储
+ """
+ enabled: bool = True # 是否启用自动评估
+ use_ragas: bool = False # 是否使用 Ragas 进行 sanity check
+ custom_weight: float = 0.7 # custom_eval 的权重
+ ragas_weight: float = 0.3 # ragas_eval 的权重
+ diff_threshold: float = 0.2 # 差异阈值(超过则标记 needs_review)
+ min_quality_score: float = 0.4 # 最低质量分数(<=0.4 才拒绝)
+ async_evaluation: bool = True # 是否异步执行(推荐 True)
+ min_query_length: int = 10 # 最小 query 长度
+ min_answer_length: int = 100 # 最小 answer 长度
+ require_repo_url: bool = True # 是否要求有仓库 URL
+ require_code_in_context: bool = True # 是否要求上下文包含代码
+
+
+class AutoEvaluationService:
+ """自动评估服务"""
+
+ def __init__(
+ self,
+ eval_engine: EvaluationEngine,
+ data_router: DataRoutingEngine,
+ config: EvaluationConfig = None
+ ):
+ self.eval_engine = eval_engine
+ self.data_router = data_router
+ self.config = config or EvaluationConfig()
+ self.needs_review_queue: list = [] # 需要人工审查的样本队列
+ self._evaluated_keys: set = set() # 防重复评估(session_id:query_hash)
+
+ # 被过滤数据的记录文件
+ self.skipped_samples_file = "evaluation/sft_data/skipped_samples.jsonl"
+ os.makedirs(os.path.dirname(self.skipped_samples_file), exist_ok=True)
+
+ def _record_skipped(self, reason: str, query: str, session_id: str,
+ repo_url: str = "", context_len: int = 0, answer_len: int = 0) -> None:
+ """记录被跳过的样本(供日后分析)"""
+ record = {
+ "timestamp": datetime.now().isoformat(),
+ "reason": reason,
+ "session_id": session_id,
+ "query": query[:200] if query else "",
+ "repo_url": repo_url,
+ "context_length": context_len,
+ "answer_length": answer_len
+ }
+ try:
+ with open(self.skipped_samples_file, 'a', encoding='utf-8') as f:
+ f.write(json.dumps(record, ensure_ascii=False) + '\n')
+ except Exception as e:
+ print(f" ⚠️ 记录跳过样本失败: {e}")
+
+ def _validate_input(
+ self,
+ query: str,
+ retrieved_context: str,
+ generated_answer: str,
+ session_id: str,
+ repo_url: str
+ ) -> tuple[bool, Optional[str]]:
+ """
+ 验证输入是否满足评估条件
+
+ Returns:
+ (is_valid, skip_reason) - 如果有效返回 (True, None),否则返回 (False, reason)
+ """
+ context_len = len(retrieved_context) if retrieved_context else 0
+ answer_len = len(generated_answer) if generated_answer else 0
+
+ # Query 验证
+ if not query or not query.strip():
+ self._record_skipped("query_empty", query or "", session_id, repo_url, context_len, answer_len)
+ return False, "query 为空"
+
+ if len(query.strip()) < self.config.min_query_length:
+ self._record_skipped("query_too_short", query, session_id, repo_url, context_len, answer_len)
+ return False, f"query 太短 ({len(query)} < {self.config.min_query_length})"
+
+ if is_chatty_query(query):
+ self._record_skipped("chatty_query", query, session_id, repo_url, context_len, answer_len)
+ return False, f"闲聊/无效 query: {query[:30]}"
+
+ # Repo URL 验证
+ if self.config.require_repo_url and not repo_url:
+ self._record_skipped("missing_repo_url", query, session_id, repo_url, context_len, answer_len)
+ return False, "缺少 repo_url"
+
+ # Answer 验证
+ if not generated_answer or len(generated_answer.strip()) < self.config.min_answer_length:
+ self._record_skipped("answer_too_short", query, session_id, repo_url, context_len, answer_len)
+ return False, f"回答太短 ({answer_len} < {self.config.min_answer_length})"
+
+ # Context 验证
+ if self.config.require_code_in_context and not has_code_indicators(retrieved_context):
+ self._record_skipped("no_code_in_context", query, session_id, repo_url, context_len, answer_len)
+ return False, "上下文中未检测到代码"
+
+ return True, None
+
+ def _check_duplicate(self, query: str, session_id: str) -> bool:
+ """检查是否重复评估,返回 True 表示是重复的"""
+ import hashlib
+ query_hash = hashlib.md5(query.encode()).hexdigest()[:8]
+ eval_key = f"{session_id}:{query_hash}"
+
+ if eval_key in self._evaluated_keys:
+ return True
+
+ self._evaluated_keys.add(eval_key)
+
+ # 限制缓存大小,防止内存泄漏
+ if len(self._evaluated_keys) > 1000:
+ self._evaluated_keys = set(list(self._evaluated_keys)[-500:])
+
+ return False
+
+ async def auto_evaluate(
+ self,
+ query: str,
+ retrieved_context: str,
+ generated_answer: str,
+ session_id: str = "auto",
+ repo_url: str = "",
+ language: str = "en"
+ ) -> Optional[str]:
+ """
+ 自动评估单个查询-回答对
+
+ Returns:
+ 质量等级 (gold/silver/bronze/rejected/needs_review) 或 None
+ """
+ # 输入验证
+ is_valid, skip_reason = self._validate_input(
+ query, retrieved_context, generated_answer, session_id, repo_url
+ )
+ if not is_valid:
+ print(f" ⚠️ [AutoEval] 跳过: {skip_reason}")
+ return None
+
+ # 防重复评估
+ if self._check_duplicate(query, session_id):
+ print(f" ⏭️ [AutoEval] 跳过重复评估: {query[:30]}...")
+ return None
+
+ start_time = datetime.now()
+
+ try:
+ # Step 1: 自定义评估
+ print(f"📊 [AutoEval] 开始评估: {query[:50]}...")
+
+ custom_metrics = await self.eval_engine.evaluate_generation(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer
+ )
+ custom_score = custom_metrics.overall_score()
+
+ print(f" ✓ Custom Score: {custom_score:.3f}")
+ print(f" - Faithfulness: {custom_metrics.faithfulness:.3f}")
+ print(f" - Answer Relevance: {custom_metrics.answer_relevance:.3f}")
+ print(f" - Completeness: {custom_metrics.answer_completeness:.3f}")
+
+ # Step 2: Ragas Sanity Check (如果启用)
+ ragas_score = None
+ ragas_details = None
+
+ if self.config.use_ragas:
+ try:
+ ragas_score, ragas_details = await self._ragas_eval(
+ query=query,
+ context=retrieved_context,
+ answer=generated_answer
+ )
+ print(f" ✓ Ragas Score: {ragas_score:.3f}")
+ if ragas_details:
+ print(f" - {ragas_details}")
+ except Exception as e:
+ print(f" ⚠️ Ragas 评估失败: {e}")
+ # Ragas 失败不应该中断主流程
+
+ # ============================================================
+ # Step 3: 混合评估 + 异常检测
+ # ============================================================
+ final_score, quality_status = self._compute_final_score(
+ custom_score=custom_score,
+ ragas_score=ragas_score
+ )
+
+ print(f" ✓ Final Score: {final_score:.3f} | Status: {quality_status}")
+
+ # ============================================================
+ # Step 4: 构建评估结果并存储
+ # ============================================================
+ eval_result = EvaluationResult(
+ session_id=session_id,
+ query=query,
+ repo_url=repo_url,
+ timestamp=start_time,
+ language=language,
+ generation_metrics=custom_metrics,
+ notes=f"ragas_score={ragas_score:.3f}" if ragas_score else ""
+ )
+
+ # 设置综合得分
+ eval_result.overall_score = final_score
+
+ # 根据状态和得分确定质量等级
+ print(f" [DEBUG] quality_status={quality_status}, final_score={final_score:.3f}, threshold={self.config.min_quality_score}")
+
+ if quality_status == "needs_review":
+ eval_result.data_quality_tier = DataQualityTier.BRONZE
+ eval_result.notes += " | needs_review=true"
+ # 加入审查队列
+ self.needs_review_queue.append({
+ "eval_result": eval_result,
+ "custom_score": custom_score,
+ "ragas_score": ragas_score,
+ "diff": abs(custom_score - (ragas_score or custom_score)),
+ "timestamp": start_time.isoformat()
+ })
+ print(f" ⚠️ 需要人工审查 (needs_review),暂存队列")
+ # 同时也路由到数据存储,便于后续分析
+ self.data_router.route_sample(eval_result)
+ elif final_score > self.config.min_quality_score:
+ # score > 0.4: 路由到 positive (>0.6) 或 negative (0.4-0.6)
+ print(f" ✓ 路由到 data_router (score {final_score:.2f} > {self.config.min_quality_score})")
+ self.data_router.route_sample(eval_result)
+ else:
+ # score <= 0.4: 质量太差,直接拒绝
+ eval_result.data_quality_tier = DataQualityTier.REJECTED
+ print(f" ❌ 评分过低 ({final_score:.2f} <= {self.config.min_quality_score}),拒绝存储")
+
+ # 记录到 tracing
+ tracing_service.add_event("auto_evaluation_completed", {
+ "query": query[:100],
+ "custom_score": custom_score,
+ "ragas_score": ragas_score,
+ "final_score": final_score,
+ "status": quality_status,
+ "quality_tier": eval_result.data_quality_tier.value
+ })
+
+ print(f" ✅ 评估完成\n")
+
+ return eval_result.data_quality_tier.value
+
+ except Exception as e:
+ print(f" ❌ 自动评估异常: {e}")
+ import traceback
+ traceback.print_exc()
+ return None
+
+ async def auto_evaluate_async(
+ self,
+ query: str,
+ retrieved_context: str,
+ generated_answer: str,
+ session_id: str = "auto",
+ repo_url: str = "",
+ language: str = "en"
+ ) -> None:
+ """
+ 异步版本 - 不阻塞主流程
+
+ 在后台执行评估,不等待结果
+ """
+ if not self.config.async_evaluation:
+ # 同步模式(不推荐在生产环境)
+ await self.auto_evaluate(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer,
+ session_id=session_id,
+ repo_url=repo_url,
+ language=language
+ )
+ else:
+ # 异步模式(推荐)- 在后台执行
+ asyncio.create_task(
+ self._eval_task(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer,
+ session_id=session_id,
+ repo_url=repo_url,
+ language=language
+ )
+ )
+
+ async def _eval_task(
+ self,
+ query: str,
+ retrieved_context: str,
+ generated_answer: str,
+ session_id: str,
+ repo_url: str,
+ language: str
+ ) -> None:
+ """后台评估任务包装"""
+ try:
+ await asyncio.sleep(0.1) # 让用户请求先返回
+ await self.auto_evaluate(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer,
+ session_id=session_id,
+ repo_url=repo_url,
+ language=language
+ )
+ except Exception as e:
+ print(f"❌ Background eval task failed: {e}")
+
+ def _compute_final_score(
+ self,
+ custom_score: float,
+ ragas_score: Optional[float]
+ ) -> tuple[float, str]:
+ """
+ 计算最终得分和状态
+
+ Returns:
+ (final_score, status)
+ status: "normal" / "needs_review" / "high_confidence"
+ """
+
+ if ragas_score is None:
+ # 没有 Ragas 分数,直接用 custom 分数
+ return custom_score, "normal"
+
+ # 计算差异
+ diff = abs(custom_score - ragas_score)
+
+ # 判断异常
+ if diff > self.config.diff_threshold:
+ # 差异过大,标记为需要审查
+ return custom_score, "needs_review"
+
+ # 混合评分
+ final_score = (
+ self.config.custom_weight * custom_score +
+ self.config.ragas_weight * ragas_score
+ )
+
+ # 两者都高分 → 高置信度
+ if custom_score > 0.75 and ragas_score > 0.75:
+ status = "high_confidence"
+ else:
+ status = "normal"
+
+ return final_score, status
+
+ async def _ragas_eval(
+ self,
+ query: str,
+ context: str,
+ answer: str
+ ) -> tuple[Optional[float], Optional[str]]:
+ """
+ 使用 Ragas 进行 sanity check
+
+ Returns:
+ (score, details)
+ """
+ try:
+ from ragas.metrics import faithfulness, answer_relevancy
+ from ragas import evaluate
+
+ # 构造 Ragas 数据集
+ dataset_dict = {
+ "question": [query],
+ "contexts": [[context]],
+ "answer": [answer]
+ }
+
+ # 执行评估
+ result = evaluate(
+ dataset=dataset_dict,
+ metrics=[faithfulness, answer_relevancy]
+ )
+
+ # 提取分数
+ faithfulness_score = result["faithfulness"][0] if "faithfulness" in result else 0.5
+ relevancy_score = result["answer_relevancy"][0] if "answer_relevancy" in result else 0.5
+
+ # 平均得分
+ ragas_score = (faithfulness_score + relevancy_score) / 2
+
+ details = f"Ragas: faithfulness={faithfulness_score:.3f}, relevancy={relevancy_score:.3f}"
+
+ return ragas_score, details
+
+ except ImportError:
+ print("⚠️ Ragas 未安装,跳过 sanity check")
+ return None, None
+ except Exception as e:
+ print(f"⚠️ Ragas 评估异常: {e}")
+ return None, None
+
+ def get_review_queue(self) -> list:
+ """获取需要审查的样本列表"""
+ return self.needs_review_queue
+
+ def clear_review_queue(self) -> None:
+ """清空审查队列"""
+ self.needs_review_queue.clear()
+
+ def approve_sample(self, index: int) -> None:
+ """人工批准某个样本"""
+ if 0 <= index < len(self.needs_review_queue):
+ item = self.needs_review_queue[index]
+ # 直接存储到评估结果
+ self.data_router.route_sample(item["eval_result"])
+ print(f"✅ 样本 {index} 已批准")
+
+ def reject_sample(self, index: int) -> None:
+ """人工拒绝某个样本"""
+ if 0 <= index < len(self.needs_review_queue):
+ print(f"❌ 样本 {index} 已拒绝")
+ self.needs_review_queue.pop(index)
+
+
+# 全局实例
+auto_eval_service: Optional[AutoEvaluationService] = None
+
+
+def init_auto_evaluation_service(
+ eval_engine: EvaluationEngine,
+ data_router: DataRoutingEngine,
+ config: EvaluationConfig = None
+) -> AutoEvaluationService:
+ """初始化自动评估服务"""
+ global auto_eval_service
+ auto_eval_service = AutoEvaluationService(
+ eval_engine=eval_engine,
+ data_router=data_router,
+ config=config
+ )
+ return auto_eval_service
+
+
+def get_auto_evaluation_service() -> Optional[AutoEvaluationService]:
+ """获取自动评估服务实例"""
+ return auto_eval_service
diff --git a/app/services/chat_service.py b/app/services/chat_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..99c4904a4a98a745527e9a37ecfadf708ff502f6
--- /dev/null
+++ b/app/services/chat_service.py
@@ -0,0 +1,601 @@
+# 文件路径: app/services/chat_service.py
+import json
+import asyncio
+import re
+import time
+from dataclasses import dataclass, field
+from typing import Dict, Optional, AsyncGenerator, List, Set
+from app.core.config import settings
+from app.utils.llm_client import client
+from app.services.vector_service import store_manager
+from app.services.github_service import get_file_content
+from app.services.chunking_service import UniversalChunker, ChunkingConfig
+from app.services.tracing_service import tracing_service
+from app.utils.session import get_conversation_memory, ConversationMemory
+
+
+# ============================================================
+# 配置类 - 解耦所有可调参数
+# ============================================================
+
+@dataclass
+class ChatConfig:
+ """Chat 服务配置 - 集中管理所有参数"""
+ # JIT 动态加载配置
+ max_jit_rounds: int = 2 # 最大 JIT 轮数
+ max_files_per_round: int = 3 # 每轮最多加载文件数
+
+ # LLM 配置
+ temperature_thinking: float = 0.1 # 思考阶段温度
+ temperature_final: float = 0.2 # 最终回答温度
+ max_tokens: int = 4096 # 最大 token 数
+
+ # 检索配置
+ retrieval_top_k: int = 6 # RAG 检索 top-k
+ context_max_chars: int = 2000 # 单文档最大字符数
+
+ # 对话上下文配置
+ max_history_turns: int = 6 # 保留最近 N 轮对话
+ summary_threshold: int = 10 # 超过 N 轮开始压缩
+
+ # 调试配置
+ show_debug_info: bool = False # 是否显示调试信息
+
+
+# 全局配置实例
+chat_config = ChatConfig()
+
+
+@dataclass
+class ChatResult:
+ """聊天结果 - 用于后续自动评估"""
+ answer: str # 最终回答
+ retrieved_context: str # 检索到的上下文
+ generation_latency_ms: float # 生成耗时
+ retrieval_latency_ms: float = 0 # 检索耗时
+
+
+# === 评估数据存储 (供 main.py 获取) ===
+# 存储每个 session 的评估数据,key 为 session_id
+_eval_data_store: Dict[str, ChatResult] = {}
+
+def get_eval_data(session_id: str) -> Optional[ChatResult]:
+ """获取指定 session 的评估数据"""
+ return _eval_data_store.get(session_id)
+
+def clear_eval_data(session_id: str) -> None:
+ """清除指定 session 的评估数据"""
+ if session_id in _eval_data_store:
+ del _eval_data_store[session_id]
+
+
+# [Fix 2] 使用 Config 对象初始化,而非直接传参
+# 之前的写法: chunker = UniversalChunker(min_chunk_size=100)
+# 现在的写法:
+chunker = UniversalChunker(config=ChunkingConfig(min_chunk_size=100))
+
+# === 新增:简单的中文检测 ===
+def is_chinese_query(text: str) -> bool:
+ """检测字符串中是否包含中文字符"""
+ for char in text:
+ if '\u4e00' <= char <= '\u9fff':
+ return True
+ return False
+
+# === 优化 2:查询重写 (解决中英文检索不匹配问题) ===
+async def _rewrite_query(user_query: str):
+ """
+ 使用 LLM 将用户的自然语言(可能是中文)转换为 3-5 个代码搜索关键词(英文)。
+ """
+ prompt = f"""
+ You are a Code Search Expert.
+ Task: Convert the user's query into 3-5 English keywords for code search (BM25/Vector).
+
+ User Query: "{user_query}"
+
+ Rules:
+ 1. Output ONLY a JSON list of strings.
+ 2. Translate concepts to technical terms (e.g., "鉴权" -> "auth", "login", "middleware").
+ 3. Keep it short.
+
+ Example Output: ["authentication", "login_handler", "jwt_verify"]
+ """
+ try:
+ response = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=[{"role": "user", "content": prompt}],
+ temperature=0.1,
+ max_tokens=100
+ )
+ content = response.choices[0].message.content
+ # 简单清洗
+ content = re.sub(r"^```(json)?|```$", "", content.strip(), flags=re.MULTILINE).strip()
+ keywords = json.loads(content)
+ if isinstance(keywords, list):
+ return " ".join(keywords) # 返回空格分隔的字符串供 BM25 使用
+ return user_query
+ except Exception as e:
+ print(f"⚠️ Query Rewrite Failed: {e}")
+ return user_query # 降级:直接用原句
+
+async def process_chat_stream(user_query: str, session_id: str):
+ """
+ 处理聊天流 - 支持多轮 JIT 动态加载文件 + 对话上下文记忆
+
+ 流程:
+ 1. 获取对话记忆,构建上下文
+ 2. 初始检索 RAG 上下文
+ 3. LLM 思考并回答,可能请求文件
+ 4. 如果请求文件,加载后继续对话 (最多 max_jit_rounds 轮)
+ 5. 最终生成答案并保存到对话记忆
+ """
+ vector_db = store_manager.get_store(session_id)
+ cfg = chat_config # 使用全局配置
+
+ # === 获取对话记忆 ===
+ memory = get_conversation_memory(session_id)
+ memory.add_user_message(user_query) # 立即记录用户消息
+
+ # 检查是否需要摘要压缩
+ if memory.needs_summarization():
+ yield "> 📝 *Compressing conversation history...*\n\n"
+ await _compress_conversation_history(memory)
+
+ # === 评估数据收集变量 ===
+ collected_context = ""
+ collected_response = ""
+ collected_retrieval_latency = 0.0
+ collected_generation_latency = 0.0
+
+ # === JIT 状态跟踪 ===
+ all_loaded_files: Set[str] = set() # 所有已加载的文件
+ all_failed_files: Set[str] = set() # 所有失败的文件
+ jit_round = 0 # 当前 JIT 轮数
+
+ # === 语言环境检测 ===
+ use_chinese = is_chinese_query(user_query)
+
+ # UI 提示语
+ ui_msgs = _get_ui_messages(use_chinese)
+
+ # === 步骤 0: 查询重写 ===
+ search_query = await _rewrite_query(user_query)
+ yield f"{ui_msgs['thinking']}`{search_query}`...\n\n"
+
+ # === 步骤 1: 初始 RAG 检索 ===
+ retrieval_start = time.time()
+ relevant_docs = await vector_db.search_hybrid(search_query, top_k=cfg.retrieval_top_k)
+ retrieval_latency_ms = (time.time() - retrieval_start) * 1000
+ collected_retrieval_latency = retrieval_latency_ms
+ tracing_service.add_event("retrieval_completed", {
+ "latency_ms": retrieval_latency_ms,
+ "documents_retrieved": len(relevant_docs) if relevant_docs else 0
+ })
+
+ rag_context = _build_context(relevant_docs, cfg.context_max_chars)
+ collected_context = rag_context
+
+ # === 步骤 2: 构建初始 Prompt ===
+ global_context = vector_db.global_context or {}
+ file_tree = global_context.get("file_tree", "(File tree not available.)")
+ agent_summary = global_context.get("summary", "")
+
+ # 获取对话历史上下文
+ conversation_context = _build_conversation_context(memory)
+
+ system_instruction = _build_system_prompt(
+ file_tree=file_tree,
+ agent_summary=agent_summary,
+ rag_context=rag_context,
+ use_chinese=use_chinese,
+ is_final_round=False,
+ conversation_context=conversation_context
+ )
+
+ augmented_user_query = f"""
+ {user_query}
+
+ (System Note: Priority 1: Answer using context. Priority 2: Use ONLY if critical info is missing.)
+ """
+
+ if not client:
+ yield "❌ LLM Error: Client not initialized"
+ return
+
+ # 初始化对话历史
+ messages = [
+ {"role": "system", "content": system_instruction},
+ {"role": "user", "content": augmented_user_query}
+ ]
+
+ try:
+ generation_start = time.time()
+
+ # === 多轮 JIT 循环 ===
+ while jit_round <= cfg.max_jit_rounds:
+ is_final_round = (jit_round == cfg.max_jit_rounds)
+
+ # 如果是最终轮,更新系统提示禁用工具
+ if is_final_round and jit_round > 0:
+ # 更新系统消息,告知这是最后一轮
+ messages[0] = {"role": "system", "content": _build_system_prompt(
+ file_tree=file_tree,
+ agent_summary=agent_summary,
+ rag_context=collected_context,
+ use_chinese=use_chinese,
+ is_final_round=True,
+ failed_files=list(all_failed_files)
+ )}
+
+ # LLM 流式生成
+ stream = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=messages,
+ stream=True,
+ temperature=cfg.temperature_final if is_final_round else cfg.temperature_thinking,
+ max_tokens=cfg.max_tokens
+ )
+
+ buffer = ""
+ round_response = ""
+ requested_files: Set[str] = set()
+
+ async for chunk in stream:
+ content = chunk.choices[0].delta.content or ""
+ if not content:
+ continue
+
+ buffer += content
+ round_response += content
+ collected_response += content
+
+ # 检测 tool_code 标签
+ if "" in buffer:
+ matches = re.findall(r"\s*(.*?)\s*", buffer, re.DOTALL)
+ for f in matches:
+ clean_f = f.strip().replace("'", "").replace('"', "").replace("`", "")
+ # 过滤已加载和已失败的文件
+ if clean_f and clean_f not in all_loaded_files and clean_f not in all_failed_files:
+ requested_files.add(clean_f)
+ yield content
+ buffer = ""
+ else:
+ yield content
+
+ # 处理缓冲区残留
+ if "" in buffer:
+ matches = re.findall(r"\s*(.*?)\s*", buffer, re.DOTALL)
+ for f in matches:
+ clean_f = f.strip().replace("'", "").replace('"', "").replace("`", "")
+ if clean_f and clean_f not in all_loaded_files and clean_f not in all_failed_files:
+ requested_files.add(clean_f)
+
+ # === 判断是否需要继续 JIT ===
+ if not requested_files or is_final_round:
+ # 没有新文件请求,或已达最大轮数,结束循环
+ break
+
+ # === JIT 文件加载 ===
+ jit_round += 1
+
+ # 限制每轮文件数
+ files_to_load = list(requested_files)[:cfg.max_files_per_round]
+ file_list_str = ", ".join([f"`{f}`" for f in files_to_load])
+
+ yield f"\n\n> 🔍 **[JIT Round {jit_round}/{cfg.max_jit_rounds}]** {ui_msgs['action_short']}{file_list_str}...\n\n"
+
+ if not vector_db.repo_url:
+ yield ui_msgs['error_url']
+ break
+
+ # 加载文件
+ round_loaded_docs = []
+ round_failed_files = []
+
+ for file_path in files_to_load:
+ if file_path in vector_db.indexed_files:
+ docs = vector_db.get_documents_by_file(file_path)
+ round_loaded_docs.extend(docs)
+ all_loaded_files.add(file_path)
+ yield f"> ✅ Loaded: `{file_path}`\n"
+ else:
+ success = await _download_and_index(vector_db, file_path)
+ if success:
+ docs = vector_db.get_documents_by_file(file_path)
+ round_loaded_docs.extend(docs)
+ all_loaded_files.add(file_path)
+ yield f"> ✅ Downloaded: `{file_path}`\n"
+ else:
+ round_failed_files.append(file_path)
+ all_failed_files.add(file_path)
+ yield f"> ⚠️ Failed: `{file_path}`\n"
+
+ # 构建后续消息
+ if round_loaded_docs:
+ new_context = _build_context(round_loaded_docs, cfg.context_max_chars)
+ collected_context += f"\n\n[JIT Round {jit_round} Context]\n{new_context}"
+
+ # 构建状态消息
+ status_msg = _build_jit_status_message(
+ loaded_count=len(round_loaded_docs),
+ failed_files=round_failed_files,
+ remaining_rounds=cfg.max_jit_rounds - jit_round,
+ use_chinese=use_chinese
+ )
+
+ context_section = f"\n\n[New Code Context]\n{_build_context(round_loaded_docs, cfg.context_max_chars)}" if round_loaded_docs else ""
+
+ # 更新对话历史,继续对话
+ messages.append({"role": "assistant", "content": round_response})
+ messages.append({"role": "user", "content": f"{status_msg}{context_section}\n\nPlease continue your analysis."})
+
+ yield "\n\n" # 分隔符
+
+ # === 生成完成 ===
+ generation_latency_ms = (time.time() - generation_start) * 1000
+ collected_generation_latency = generation_latency_ms
+
+ tracing_service.add_event("generation_completed", {
+ "latency_ms": generation_latency_ms,
+ "jit_rounds": jit_round,
+ "files_loaded": len(all_loaded_files),
+ "files_failed": len(all_failed_files)
+ })
+
+ # === 保存助手回复到对话记忆 ===
+ memory.add_assistant_message(collected_response)
+
+ # 存储评估数据
+ _eval_data_store[session_id] = ChatResult(
+ answer=collected_response,
+ retrieved_context=collected_context,
+ generation_latency_ms=collected_generation_latency,
+ retrieval_latency_ms=collected_retrieval_latency
+ )
+ print(f"📦 [EvalData] Session {session_id}: {len(collected_context)} chars context, {len(collected_response)} chars answer, {jit_round} JIT rounds, {memory.get_turn_count()} turns")
+
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ error_msg = str(e)
+ # 即使出错也保存部分回复
+ if collected_response:
+ memory.add_assistant_message(collected_response + f"\n\n[Error: {error_msg}]")
+ tracing_service.add_event("generation_error", {
+ "error": error_msg,
+ "error_type": type(e).__name__,
+ "jit_round": jit_round
+ })
+ yield f"\n\n❌ System Error: {error_msg}"
+
+
+# ============================================================
+# 辅助函数
+# ============================================================
+
+def _get_ui_messages(use_chinese: bool) -> Dict[str, str]:
+ """获取 UI 消息(根据语言)"""
+ if use_chinese:
+ return {
+ "thinking": "> 🧠 **思考中:** 正在检索相关代码: ",
+ "action_short": "正在读取文件: ",
+ "error_url": "> ⚠️ 错误: 仓库链接丢失。\n",
+ }
+ else:
+ return {
+ "thinking": "> 🧠 **Thinking:** Searching for code related to: ",
+ "action_short": "Retrieving files: ",
+ "error_url": "> ⚠️ Error: Repository URL lost.\n",
+ }
+
+
+def _build_system_prompt(
+ file_tree: str,
+ agent_summary: str,
+ rag_context: str,
+ use_chinese: bool,
+ is_final_round: bool,
+ failed_files: List[str] = None,
+ conversation_context: str = ""
+) -> str:
+ """构建系统提示词"""
+ lang_instruction = (
+ "IMPORTANT: The user is asking in Chinese. You MUST reply in Simplified Chinese (简体中文)."
+ if use_chinese else "Reply in English."
+ )
+
+ if is_final_round:
+ tool_instruction = """
+ [INSTRUCTIONS - FINAL ROUND]
+ This is your FINAL response. You MUST provide a complete answer NOW.
+ - DO NOT request any more files
+ - DO NOT use tags
+ - Synthesize all available context and give your best answer
+ - If some files were not accessible, explain what information is missing and provide the best possible answer with what you have
+ """
+ if failed_files:
+ tool_instruction += f"\n Note: The following files could not be accessed: {', '.join(failed_files)}"
+ else:
+ tool_instruction = """
+ [INSTRUCTIONS]
+ 1. **CHECK CONTEXT FIRST**: Look at the [Current Code Context]. Does it contain the answer?
+ 2. **IF YES**: Answer directly. DO NOT use tools.
+ 3. **IF NO**: Request missing files using tags: path/to/file
+ """
+
+ # 添加对话历史上下文
+ conversation_section = ""
+ if conversation_context:
+ conversation_section = f"""
+ [Previous Conversation]
+ {conversation_context}
+ """
+
+ return f"""
+ You are a Senior GitHub Repository Analyst.
+ {lang_instruction}
+
+ [Global Context - Repo Map]
+ {file_tree}
+
+ [Agent Analysis Summary]
+ {agent_summary}
+ {conversation_section}
+ [Current Code Context (Retrieved)]
+ {rag_context}
+ {tool_instruction}
+ """
+
+
+def _build_conversation_context(memory: ConversationMemory) -> str:
+ """
+ 构建对话历史上下文字符串
+
+ 只包含最近几轮对话的摘要,用于 system prompt
+ """
+ messages = memory.get_context_messages()
+
+ if len(messages) <= 2:
+ # 只有当前轮,不需要历史
+ return ""
+
+ # 排除最后一条(当前用户消息)
+ history_messages = messages[:-1]
+
+ if not history_messages:
+ return ""
+
+ context_parts = []
+ for msg in history_messages[-6:]: # 最多 6 条(3 轮)
+ role = "User" if msg["role"] == "user" else "Assistant"
+ # 截断过长的内容
+ content = msg["content"][:500]
+ if len(msg["content"]) > 500:
+ content += "..."
+ context_parts.append(f"{role}: {content}")
+
+ return "\n".join(context_parts)
+
+
+async def _compress_conversation_history(memory: ConversationMemory) -> None:
+ """
+ 压缩对话历史 - 使用 LLM 生成摘要
+ """
+ messages_to_summarize = memory.get_messages_to_summarize()
+
+ if not messages_to_summarize:
+ return
+
+ # 构建摘要请求
+ conversation_text = "\n".join([
+ f"{'User' if m['role'] == 'user' else 'Assistant'}: {m['content'][:300]}"
+ for m in messages_to_summarize
+ ])
+
+ prompt = f"""Summarize the following conversation in 2-3 sentences, focusing on:
+1. What questions were asked
+2. Key information discovered
+3. Important conclusions
+
+Conversation:
+{conversation_text}
+
+Summary (be concise):"""
+
+ try:
+ response = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=[{"role": "user", "content": prompt}],
+ temperature=0.3,
+ max_tokens=200
+ )
+ summary = response.choices[0].message.content.strip()
+
+ # 保存摘要
+ end_idx = len(memory._messages) - chat_config.max_history_turns * 2
+ memory.set_summary(summary, end_idx)
+
+ print(f"📝 Conversation compressed: {len(messages_to_summarize)} messages -> summary")
+ except Exception as e:
+ print(f"⚠️ Failed to compress conversation: {e}")
+
+
+def _build_jit_status_message(
+ loaded_count: int,
+ failed_files: List[str],
+ remaining_rounds: int,
+ use_chinese: bool
+) -> str:
+ """构建 JIT 状态消息"""
+ if use_chinese:
+ if loaded_count > 0 and not failed_files:
+ return f"系统通知: 成功加载 {loaded_count} 个文件。"
+ elif loaded_count > 0 and failed_files:
+ failed_list = ", ".join(failed_files)
+ return f"系统通知: 加载了 {loaded_count} 个文件,但以下文件无法访问: {failed_list}。"
+ else:
+ failed_list = ", ".join(failed_files)
+ if remaining_rounds > 0:
+ return f"系统通知: 文件 ({failed_list}) 无法访问。你还有 {remaining_rounds} 次机会请求其他文件,或者基于现有上下文回答。"
+ else:
+ return f"系统通知: 文件 ({failed_list}) 无法访问。请基于现有上下文给出最佳回答。"
+ else:
+ if loaded_count > 0 and not failed_files:
+ return f"System Notification: Successfully loaded {loaded_count} files."
+ elif loaded_count > 0 and failed_files:
+ failed_list = ", ".join(failed_files)
+ return f"System Notification: Loaded {loaded_count} files, but the following could not be accessed: {failed_list}."
+ else:
+ failed_list = ", ".join(failed_files)
+ if remaining_rounds > 0:
+ return f"System Notification: Files ({failed_list}) could not be accessed. You have {remaining_rounds} more attempts to request other files, or answer based on available context."
+ else:
+ return f"System Notification: Files ({failed_list}) could not be accessed. Please provide the best possible answer based on existing context."
+
+async def _download_and_index(vector_db, file_path):
+ """下载并索引文件"""
+ try:
+ content = await get_file_content(vector_db.repo_url, file_path)
+ if not content: return False
+
+ chunks = await asyncio.to_thread(chunker.chunk_file, content, file_path)
+ if not chunks:
+ chunks = [{
+ "content": content,
+ "metadata": {"file": file_path, "type": "text", "name": "root", "class": ""}
+ }]
+
+ documents = [c["content"] for c in chunks]
+ metadatas = []
+ for c in chunks:
+ meta = c["metadata"]
+ metadatas.append({
+ "file": meta["file"],
+ "type": meta["type"],
+ "name": meta.get("name", ""),
+ "class": meta.get("class") or ""
+ })
+ await vector_db.add_documents(documents, metadatas)
+ return True
+ except Exception as e:
+ print(f"Download Error: {e}")
+ return False
+
+
+def _build_context(docs: List[Dict], max_chars: int = 2000) -> str:
+ """构建上下文字符串"""
+ if not docs:
+ return "(No relevant code snippets found yet)"
+
+ context = ""
+ for doc in docs:
+ file_info = doc.get('file', 'unknown')
+ metadata = doc.get('metadata', {})
+
+ if 'class' in metadata and metadata['class']:
+ file_info += f" (Class: {metadata['class']})"
+
+ content = doc.get('content', '')[:max_chars]
+ context += f"\n--- File: {file_info} ---\n{content}\n"
+
+ return context
\ No newline at end of file
diff --git a/app/services/chunking_service.py b/app/services/chunking_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0d7ca53a6931808f38441437cb145b7642bb98c
--- /dev/null
+++ b/app/services/chunking_service.py
@@ -0,0 +1,372 @@
+import ast
+import re
+import os
+from dataclasses import dataclass
+
+# --- 配置类 ---
+@dataclass
+class ChunkingConfig:
+ """
+ 统一管理切分服务的配置参数
+ """
+ min_chunk_size: int = 50 # 最小分块阈值 (chars)
+ max_chunk_size: int = 2000 # 最大分块阈值 (chars)
+ fallback_line_size: int = 100 # 兜底策略的行数 (lines)
+ max_context_chars: int = 500 # 允许注入到每个Chunk的上下文最大长度
+ # 超过此长度则不再注入,避免冗余内容撑爆 Token
+
+class UniversalChunker:
+ def __init__(self, config: ChunkingConfig = None):
+ # 如果未传入配置,使用默认配置
+ self.config = config if config else ChunkingConfig()
+
+ def chunk_file(self, content: str, file_path: str):
+ if not content:
+ return []
+
+ ext = os.path.splitext(file_path)[1].lower()
+
+ if ext == '.py':
+ return self._chunk_python(content, file_path)
+
+ # 2. C-Style 语言优化
+ elif ext in ['.java', '.js', '.ts', '.jsx', '.tsx', '.go', '.cpp', '.c', '.h', '.cs', '.php', '.rs']:
+ return self._chunk_c_style(content, file_path)
+
+ else:
+ return self._fallback_chunking(content, file_path)
+
+ def _chunk_python(self, content, file_path):
+ """
+ 分级注入策略
+ """
+ chunks = []
+ try:
+ tree = ast.parse(content)
+ except SyntaxError:
+ return self._fallback_chunking(content, file_path)
+
+ import_nodes = []
+ other_nodes = []
+ function_class_chunks = []
+
+ # A. 遍历与分类
+ for node in tree.body:
+ if isinstance(node, ast.ClassDef):
+ class_code = ast.get_source_segment(content, node)
+ if not class_code: continue
+ if len(class_code) <= self.config.max_chunk_size:
+ function_class_chunks.append(self._create_chunk(
+ class_code, file_path, "class", node.name, node.lineno, node.name
+ ))
+ else:
+ # function_class_chunks 包含了从大类中拆分出的方法
+ function_class_chunks.extend(
+ self._chunk_large_python_class(node, content, file_path)
+ )
+
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ func_code = ast.get_source_segment(content, node)
+ if func_code and len(func_code) >= self.config.min_chunk_size:
+ function_class_chunks.append(self._create_chunk(
+ func_code, file_path, "function", node.name, node.lineno
+ ))
+
+ else:
+ segment = ast.get_source_segment(content, node)
+ if segment and len(segment.strip()) > 0:
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
+ import_nodes.append(segment)
+ else:
+ other_nodes.append(segment)
+
+ # B. 决策准备
+ has_core_code = len(function_class_chunks) > 0
+ others_text = "\n".join(other_nodes).strip()
+ should_inject_others = len(others_text) <= self.config.max_context_chars
+
+ # C. 构建 Context Header
+ context_parts = []
+ # 1. Import 永远注入
+ if import_nodes:
+ context_parts.append("\n".join(import_nodes))
+ # 2. Globals 按需注入
+ if others_text and should_inject_others:
+ context_parts.append(others_text)
+
+ full_header = "\n".join(context_parts).strip()
+ if full_header:
+ full_header = f"# --- Context ---\n{full_header}\n# ---------------\n"
+
+ # D. 注入 Header 到核心 Chunk (函数/类)
+ # 此时 function_class_chunks 已经包含了大类拆分出来的方法
+ # 这里的循环会给它们都加上 Import/Global Context
+ for chunk in function_class_chunks:
+ chunk["content"] = full_header + chunk["content"]
+
+ # E. 处理溢出 (仅当有核心代码时,才独立存储溢出的 Globals)
+ if has_core_code and others_text and not should_inject_others:
+ chunks.append(self._create_chunk(
+ others_text, file_path, "global_context", "globals", 1
+ ))
+
+ # F. 纯脚本兜底
+ if not has_core_code:
+ # 这是一个纯脚本文件 (只有 Import 和 顶层逻辑)
+ full_script = (("\n".join(import_nodes) + "\n") if import_nodes else "") + others_text
+ if full_script.strip():
+ # 如果脚本太长,不要硬切成一个大块,而是走 Fallback 按行切分
+ if len(full_script) > self.config.max_chunk_size * 1.5: # 1.5倍宽容度
+ return self._fallback_chunking(content, file_path)
+ else:
+ chunks.append(self._create_chunk(
+ full_script, file_path, "script", "main", 1
+ ))
+
+ chunks.extend(function_class_chunks)
+
+ if not chunks and len(content.strip()) > 0:
+ return self._fallback_chunking(content, file_path)
+
+ return chunks
+
+ def _chunk_large_python_class(self, class_node, content, file_path):
+ chunks = []
+ class_name = class_node.name
+ docstring = ast.get_docstring(class_node) or ""
+
+ # === 尝试收集类级别的变量定义 ===
+ class_vars = []
+ for node in class_node.body:
+ # 如果是赋值语句,且在方法定义之前 (通常 AST 是有序的)
+ if isinstance(node, (ast.Assign, ast.AnnAssign)):
+ seg = ast.get_source_segment(content, node)
+ if seg: class_vars.append(seg)
+ # 一旦遇到函数,就停止收集变量,避免把乱七八糟的逻辑也收进去
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ break
+
+ vars_text = "\n ".join(class_vars)
+ if vars_text:
+ vars_text = "\n " + vars_text # 缩进对齐
+
+ # 将变量拼接到 Header 中
+ context_header = f"class {class_name}:{vars_text}\n \"\"\"{docstring}\"\"\"\n # ... (Parent Context)\n"
+
+ for node in class_node.body:
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ method_code = ast.get_source_segment(content, node)
+ if not method_code: continue
+
+ full_chunk_content = context_header + "\n" + method_code
+ chunks.append(self._create_chunk(
+ full_chunk_content, file_path, "method", node.name, node.lineno, class_name
+ ))
+ return chunks
+
+ def _chunk_c_style(self, content, file_path):
+ """
+ 解决宏干扰、全局变量丢失、跨行函数头问题
+ """
+ chunks = []
+ if not content: return []
+
+ # === 1. 定义正则 Token ===
+ # 使用 Named Groups 避免 startswith 的模糊匹配
+ # 顺序至关重要:长匹配优先
+ token_pattern = re.compile(
+ r'(?P/\*.*?\*/)|' # 块注释
+ r'(?P//[^\n]*)|' # 行注释
+ r'(?P"(?:\\.|[^"\\])*")|' # 双引号字符串
+ r'(?P\'(?:\\.|[^\'\\])*\')|' # 单引号字符
+ r'(?P`(?:\\.|[^`\\])*`)|' # 反引号模板 (JS/Go)
+ r'(?P^\s*#.*(?:\\\n.*)*)|' # 宏定义 (支持跨行)
+ r'(?P\{)|' # 开括号
+ r'(?P\})|' # 闭括号
+ r'(?P;)', # 分号 (用于分割全局变量和函数头)
+ re.DOTALL | re.MULTILINE
+ )
+
+ # 全局上下文收集器
+ global_context_parts = []
+
+ last_index = 0 # 上一个 Token 结束位置
+ block_start_index = 0 # 当前 Block (函数/类) 的签名开始位置
+
+ brace_balance = 0
+ in_structural_block = False # 是否在最外层的类/函数块内
+
+ # 暂存当前块的前置文本 (从上一个块结束 到 当前块开始)
+ # 这段文本里可能混杂着:全局变量、Import、以及当前函数的签名
+ pending_pre_text_start = 0
+
+ # 扫描
+ for match in token_pattern.finditer(content):
+ kind = match.lastgroup
+ start, end = match.span()
+
+ # 跳过非结构化 Token (注释、字符串、宏)
+ if kind in ('BLOCK_COMMENT', 'LINE_COMMENT', 'STRING', 'CHAR', 'TEMPLATE', 'MACRO'):
+ continue
+
+ # 忽略括号 () 和 [],只认 {}。
+ # C-style 语言只有 {} 定义 Scope Body。忽略 () [] 是为了防止 if(a[i]){...} 误判。
+ # 只要 regex 不匹配 () [],它们就被视为普通文本,不会影响 brace_balance。
+ if kind == 'BRACE_OPEN':
+ if brace_balance == 0:
+ # === 发现一个新的顶层 Block ===
+ in_structural_block = True
+
+ # 1. 分析 "空隙文本" (从上一个块结束 到 这个 { 之前)
+ gap_text = content[pending_pre_text_start:start]
+
+ # [策略] 拆分 Global Context 和 Signature
+ # 寻找最后一个分号 ';' 或 '}' (在 gap_text 内部的逻辑结束点)
+ # 倒序查找比较安全。
+ # 如果找不到,说明整段 gap 都是签名 (e.g. void foo() {)
+ # 如果找到,分号前是 Global,分号后是 Signature
+ split_idx = gap_text.rfind(';')
+ if split_idx != -1:
+ # 分号前:归入全局上下文
+ global_part = gap_text[:split_idx+1].strip()
+ if global_part:
+ global_context_parts.append(global_part)
+ # 分号后:是当前函数的签名
+ # 自动处理了跨行函数头,因为 gap_text 包含换行
+ block_signature_start = pending_pre_text_start + split_idx + 1
+ else:
+ # 没有分号,假设全是签名 (e.g. 紧接着上一个块,或者是文件开头)
+ # 但要小心 include/import 等没有分号的语句 (Python 思维在 C 里不适用,C 几乎都有分号)
+ # Go 语言除外 (Go 没分号)。这里做一个简单的 heuristic:
+ # 如果是 Go/JS/TS,可能没有分号。暂且全部视为 Signature,
+ # 除非它看起来像 import。
+ # 这是一个 trade-off。
+ block_signature_start = pending_pre_text_start
+
+ # 记录当前 Block 真正的“视觉开始点” (包含签名)
+ block_start_index = block_signature_start
+
+ brace_balance += 1
+
+ elif kind == 'BRACE_CLOSE':
+ brace_balance -= 1
+
+ if brace_balance == 0 and in_structural_block:
+ # === 顶层 Block 结束 ===
+ in_structural_block = False
+
+ # 提取完整代码块 (Signature + Body)
+ # 范围:block_start_index -> end
+ full_block_text = content[block_start_index:end]
+
+ # 小块合并策略
+ # 如果块太小 (e.g. Getter/Setter),暂不生成 Chunk
+ # 架构决策:为了代码完整性,工业界 RAG 通常不建议丢弃小块,
+ # 尤其是 Getter/Setter 可能包含关键字段名。
+ # 这里我们生成 Chunk,但后续入库时可以由 Embedding 模型决定权重。
+
+ # 提取元数据
+ meta = self._extract_c_style_metadata(full_block_text)
+ start_line = content.count('\n', 0, block_start_index) + 1
+
+ chunks.append(self._create_chunk(
+ full_block_text, # 暂时不加 Global Header,最后统一加
+ file_path, meta["type"], meta["name"], start_line
+ ))
+
+ # 更新游标:下一个块的前置文本从这里开始
+ pending_pre_text_start = end
+
+ # === 循环结束后的收尾 ===
+ # 处理文件末尾的剩余文本 (Tail)
+ tail_text = content[pending_pre_text_start:].strip()
+ if tail_text:
+ global_context_parts.append(tail_text)
+
+ # === Global Context 重排序 ===
+ # 目标顺序: Includes > Macros (#define) > Others (Typedefs/Vars)
+ # 简单策略:基于字符串内容的优先级排序
+
+ def context_priority(text):
+ text = text.strip()
+ if text.startswith("#include") or text.startswith("import") or text.startswith("using"):
+ return 0 # 最高优先级
+ if text.startswith("#define") or text.startswith("#macro"):
+ return 1 # 宏定义
+ if text.startswith("typedef") or text.startswith("enum") or text.startswith("struct"):
+ return 2 # 类型定义
+ return 3 # 普通全局变量和其他
+
+ # 稳定排序
+ global_context_parts.sort(key=context_priority)
+
+ # === 组装与注入 ===
+ full_global_context = "\n".join(global_context_parts).strip()
+
+ should_inject = len(full_global_context) <= self.config.max_context_chars
+
+ context_header = ""
+ if full_global_context and should_inject:
+ context_header = f"/* --- Global Context --- */\n{full_global_context}\n/* ---------------------- */\n"
+
+ for chunk in chunks:
+ chunk["content"] = context_header + chunk["content"]
+
+ if (full_global_context and not should_inject) or (not chunks and full_global_context):
+ chunks.insert(0, self._create_chunk(
+ full_global_context, file_path, "global_context", "header", 1
+ ))
+
+ if not chunks:
+ return self._fallback_chunking(content, file_path)
+
+ return chunks
+
+ def _extract_c_style_metadata(self, code_block):
+ """
+ 从包含签名的代码块中提取元数据 (支持多行签名)
+ """
+ # 截取到第一个 { 为止
+ header_part = code_block.split('{')[0]
+ # 压缩多余空白,变成单行以便正则匹配
+ header_clean = " ".join(header_part.split())
+
+ # 1. Class/Struct/Interface
+ type_pattern = re.compile(r'\b(class|struct|interface|enum|record|type)\s+([a-zA-Z0-9_]+)')
+ match = type_pattern.search(header_clean)
+ if match:
+ return {"type": "class", "name": match.group(2)}
+
+ # 2. Function
+ # 匹配: 单词 + (
+ # 排除关键字: if, for, while, switch, catch, return
+ func_pattern = re.compile(r'\b([a-zA-Z0-9_]+)\s*\(')
+ for match in func_pattern.finditer(header_clean):
+ name = match.group(1)
+ if name not in {'if', 'for', 'while', 'switch', 'catch', 'return', 'sizeof'}:
+ return {"type": "function", "name": name}
+
+ return {"type": "code_block", "name": "anonymous"}
+
+ def _fallback_chunking(self, content, file_path):
+ """兜底策略:使用 Config 中的行数设置"""
+ chunks = []
+ lines = content.split('\n')
+ chunk_size = self.config.fallback_line_size
+
+ for i in range(0, len(lines), chunk_size):
+ chunk_content = "\n".join(lines[i:i+chunk_size])
+ chunks.append(self._create_chunk(chunk_content, file_path, "text_chunk", f"chunk_{i}", i+1))
+ return chunks
+
+ def _create_chunk(self, content, file_path, type_, name, start_line, class_name=""):
+ return {
+ "content": content,
+ "metadata": {
+ "file": file_path,
+ "type": type_,
+ "name": name,
+ "start_line": start_line,
+ "class": class_name
+ }
+ }
\ No newline at end of file
diff --git a/app/services/github_service.py b/app/services/github_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..78d5a2a82e81bc78058d7c88e08b10160e4e2a41
--- /dev/null
+++ b/app/services/github_service.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+"""
+GitHub 服务层
+
+职责:
+- 提供业务级别的 GitHub 操作
+- 封装底层客户端,提供简洁 API
+- 保持向后兼容的函数签名
+"""
+
+import logging
+from typing import List, Optional, Dict
+
+from app.utils.github_client import (
+ GitHubClient,
+ GitHubRepo,
+ GitHubFile,
+ FileFilter,
+ GitHubError,
+ GitHubNotFoundError,
+ get_github_client,
+ parse_repo_url,
+)
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 服务类
+# ============================================================
+
+class GitHubService:
+ """
+ GitHub 服务
+
+ 提供高层业务操作,内部使用异步客户端。
+
+ 使用示例:
+ ```python
+ service = GitHubService()
+
+ # 获取仓库文件列表
+ files = await service.get_repo_structure("https://github.com/owner/repo")
+
+ # 获取文件内容
+ content = await service.get_file_content(
+ "https://github.com/owner/repo",
+ "src/main.py"
+ )
+
+ # 批量获取文件
+ contents = await service.get_files_content(
+ "https://github.com/owner/repo",
+ ["README.md", "src/main.py", "requirements.txt"]
+ )
+ ```
+ """
+
+ def __init__(self, client: Optional[GitHubClient] = None):
+ self._client = client
+
+ @property
+ def client(self) -> GitHubClient:
+ """获取客户端 (延迟初始化)"""
+ if self._client is None:
+ self._client = get_github_client()
+ return self._client
+
+ async def _get_repo_from_url(self, repo_url: str) -> GitHubRepo:
+ """从 URL 获取仓库对象"""
+ parsed = parse_repo_url(repo_url)
+ if not parsed:
+ raise ValueError(f"无效的 GitHub URL: {repo_url}")
+
+ owner, name = parsed
+ return await self.client.get_repo(owner, name)
+
+ async def get_repo_structure(
+ self,
+ repo_url: str,
+ file_filter: Optional[FileFilter] = None
+ ) -> List[str]:
+ """
+ 获取仓库文件列表
+
+ Args:
+ repo_url: GitHub 仓库 URL
+ file_filter: 自定义文件过滤器
+
+ Returns:
+ 文件路径列表
+ """
+ repo = await self._get_repo_from_url(repo_url)
+ files = await self.client.get_repo_tree(repo, file_filter)
+ return [f.path for f in files]
+
+ async def get_file_content(
+ self,
+ repo_url: str,
+ file_path: str
+ ) -> Optional[str]:
+ """
+ 获取单个文件内容
+
+ Args:
+ repo_url: GitHub 仓库 URL
+ file_path: 文件路径
+
+ Returns:
+ 文件内容,失败返回 None
+ """
+ repo = await self._get_repo_from_url(repo_url)
+ return await self.client.get_file_content(repo, file_path)
+
+ async def get_files_content(
+ self,
+ repo_url: str,
+ file_paths: List[str]
+ ) -> Dict[str, Optional[str]]:
+ """
+ 批量获取文件内容 (并发)
+
+ Args:
+ repo_url: GitHub 仓库 URL
+ file_paths: 文件路径列表
+
+ Returns:
+ {path: content} 字典
+ """
+ repo = await self._get_repo_from_url(repo_url)
+ return await self.client.get_files_content(repo, file_paths, show_progress=True)
+
+ async def get_repo_info(self, repo_url: str) -> GitHubRepo:
+ """
+ 获取仓库基本信息
+
+ Args:
+ repo_url: GitHub 仓库 URL
+
+ Returns:
+ GitHubRepo 对象
+ """
+ return await self._get_repo_from_url(repo_url)
+
+
+# ============================================================
+# 全局服务实例
+# ============================================================
+
+_github_service: Optional[GitHubService] = None
+
+
+def get_github_service() -> GitHubService:
+ """获取 GitHub 服务单例"""
+ global _github_service
+ if _github_service is None:
+ _github_service = GitHubService()
+ return _github_service
+
+
+# ============================================================
+# 兼容旧接口 (同步风格的函数签名,但返回协程)
+# ============================================================
+
+# 保留 parse_repo_url 的旧签名兼容
+def parse_repo_url_compat(url: str) -> Optional[str]:
+ """
+ 解析 GitHub URL (兼容旧接口)
+
+ Returns:
+ "owner/repo" 字符串,无效返回 None
+ """
+ result = parse_repo_url(url)
+ if result:
+ return f"{result[0]}/{result[1]}"
+ return None
+
+
+async def get_repo_structure(repo_url: str) -> List[str]:
+ """
+ 获取仓库文件列表 (兼容旧接口)
+
+ 注意: 这是一个异步函数,需要 await 调用
+ """
+ service = get_github_service()
+ return await service.get_repo_structure(repo_url)
+
+
+async def get_file_content(repo_url: str, file_path: str) -> Optional[str]:
+ """
+ 获取文件内容 (兼容旧接口)
+
+ 注意: 这是一个异步函数,需要 await 调用
+ """
+ service = get_github_service()
+ return await service.get_file_content(repo_url, file_path)
+
+
+# 导出
+__all__ = [
+ "GitHubService",
+ "get_github_service",
+ "get_repo_structure",
+ "get_file_content",
+ "parse_repo_url_compat",
+ "GitHubError",
+ "GitHubNotFoundError",
+ "FileFilter",
+ "GitHubRepo",
+]
\ No newline at end of file
diff --git a/app/services/tracing_service.py b/app/services/tracing_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..05969c0fb5acede0581b938617293361a768ad64
--- /dev/null
+++ b/app/services/tracing_service.py
@@ -0,0 +1,549 @@
+# 文件路径: app/services/tracing_service.py
+"""
+Langfuse集成模块 - 用于端到端追踪和观测
+
+核心能力:
+1. 自动捕获每一步的延迟、Token成本、输入输出
+2. 记录完整的调用链路: Query -> Rewrite -> Retrieval -> Generation
+3. 记录Tool调用和参数
+4. 集成到评估流程
+
+Langfuse支持:
+- 本地部署 (docker run ... langfuse)
+- 云端托管 (app.langfuse.com)
+
+Author: Dexter
+Date: 2025-01-27
+"""
+
+import time
+import json
+import os
+from typing import Dict, Any, Optional, List, Callable
+from functools import wraps
+from datetime import datetime
+from dataclasses import dataclass
+
+
+# ============================================================================
+# 第一部分: Langfuse客户端初始化 (可选)
+# ============================================================================
+
+LANGFUSE_IMPORT_ERROR = None
+_LANGFUSE_ENABLED_ENV = os.getenv("LANGFUSE_ENABLED", "true").strip().lower()
+_LANGFUSE_ENABLED = _LANGFUSE_ENABLED_ENV not in {"0", "false", "no", "off"}
+
+if _LANGFUSE_ENABLED:
+ try:
+ from langfuse import Langfuse
+ from langfuse.decorators import observe, langfuse_context
+ LANGFUSE_AVAILABLE = True
+ except Exception as e:
+ LANGFUSE_IMPORT_ERROR = e
+ LANGFUSE_AVAILABLE = False
+else:
+ LANGFUSE_AVAILABLE = False
+
+
+@dataclass
+class TracingConfig:
+ """追踪配置"""
+ enabled: bool = True
+ backend: str = "langfuse" # "langfuse" or "local"
+ langfuse_host: str = os.getenv("LANGFUSE_HOST", "http://localhost:3000")
+ langfuse_public_key: str = os.getenv("LANGFUSE_PUBLIC_KEY", "")
+ langfuse_secret_key: str = os.getenv("LANGFUSE_SECRET_KEY", "")
+ capture_token_usage: bool = True
+ capture_latency: bool = True
+ local_log_dir: str = "logs/traces"
+
+
+class TracingService:
+ """
+ 统一的追踪服务
+ 支持Langfuse和本地日志两种后端
+ """
+
+ def __init__(self, config: TracingConfig = None):
+ self.config = config or TracingConfig()
+ self.langfuse_client = None
+ self.current_trace_id = None
+
+ if self.config.enabled and self.config.backend == "langfuse":
+ if not LANGFUSE_AVAILABLE:
+ print("⚠️ Langfuse not installed. Install with: pip install langfuse. Falling back to local logging.")
+ self.config.backend = "local"
+ else:
+ try:
+ self.langfuse_client = Langfuse(
+ host=self.config.langfuse_host,
+ public_key=self.config.langfuse_public_key,
+ secret_key=self.config.langfuse_secret_key,
+ enabled=True,
+ debug=False
+ )
+ print("✅ Langfuse client initialized successfully")
+ except Exception as e:
+ print(f"⚠️ Langfuse initialization failed: {e}. Falling back to local logging.")
+ self.config.backend = "local"
+
+ # 创建本地日志目录
+ os.makedirs(self.config.local_log_dir, exist_ok=True)
+
+ def start_trace(self, trace_name: str, session_id: str, metadata: Dict = None) -> str:
+ """启动一个新的追踪链"""
+ import uuid
+ trace_id = str(uuid.uuid4())
+ self.current_trace_id = trace_id
+
+ if self.langfuse_client:
+ self.langfuse_client.trace(
+ name=trace_name,
+ input=metadata or {},
+ session_id=session_id
+ )
+ print(f"📍 Trace started: {trace_id}")
+ else:
+ self._log_locally("trace_start", {
+ "trace_id": trace_id,
+ "name": trace_name,
+ "session_id": session_id,
+ "metadata": metadata,
+ "timestamp": datetime.now().isoformat()
+ })
+
+ return trace_id
+
+ def record_span(
+ self,
+ span_name: str,
+ operation: str,
+ input_data: Any,
+ output_data: Any,
+ latency_ms: float,
+ token_usage: Dict[str, int] = None,
+ metadata: Dict = None
+ ) -> None:
+ """记录一个操作的跨度"""
+
+ span_record = {
+ "span_name": span_name,
+ "operation": operation,
+ "latency_ms": latency_ms,
+ "timestamp": datetime.now().isoformat(),
+ "token_usage": token_usage or {},
+ "metadata": metadata or {}
+ }
+
+ if self.langfuse_client:
+ try:
+ # Langfuse:记录到云端
+ self.langfuse_client.span(
+ name=span_name,
+ input=input_data,
+ output=output_data,
+ metadata={
+ "operation": operation,
+ "latency_ms": latency_ms,
+ **(token_usage or {}),
+ **(metadata or {})
+ }
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record span to Langfuse: {e}")
+
+ # 本地日志
+ self._log_locally("span", span_record)
+
+ def record_tool_call(
+ self,
+ tool_name: str,
+ parameters: Dict,
+ result: Any,
+ latency_ms: float,
+ success: bool,
+ error: str = None
+ ) -> None:
+ """记录工具调用"""
+
+ tool_record = {
+ "tool_name": tool_name,
+ "parameters": parameters,
+ "result": str(result)[:500] if result else None,
+ "latency_ms": latency_ms,
+ "success": success,
+ "error": error,
+ "timestamp": datetime.now().isoformat()
+ }
+
+ if self.langfuse_client:
+ try:
+ self.langfuse_client.event(
+ name=f"tool_call:{tool_name}",
+ input=parameters,
+ output=result,
+ metadata={
+ "latency_ms": latency_ms,
+ "success": success,
+ "error": error
+ }
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record tool call: {e}")
+
+ self._log_locally("tool_call", tool_record)
+
+ def record_retrieval_debug(
+ self,
+ query: str,
+ retrieved_files: List[str],
+ vector_scores: List[float],
+ bm25_scores: List[float],
+ latency_ms: float
+ ) -> None:
+ """记录检索过程的调试信息"""
+
+ retrieval_record = {
+ "query": query,
+ "retrieved_count": len(retrieved_files),
+ "files": retrieved_files,
+ "vector_scores": vector_scores,
+ "bm25_scores": bm25_scores,
+ "latency_ms": latency_ms,
+ "timestamp": datetime.now().isoformat()
+ }
+
+ if self.langfuse_client:
+ try:
+ self.langfuse_client.event(
+ name="retrieval_debug",
+ input={"query": query},
+ output={"files": retrieved_files},
+ metadata=retrieval_record
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record retrieval debug: {e}")
+
+ self._log_locally("retrieval", retrieval_record)
+
+ def record_llm_generation(
+ self,
+ model: str,
+ prompt_messages: List[Dict],
+ generated_text: str,
+ ttft_ms: float = None,
+ total_latency_ms: float = None,
+ prompt_tokens: int = None,
+ completion_tokens: int = None,
+ total_tokens: int = None,
+ is_streaming: bool = False,
+ metadata: Dict = None
+ ) -> None:
+ """
+ 记录 LLM 生成的完整信息,包括 Token 消耗和 TTFT
+
+ Args:
+ model: 模型名称 (如 "gpt-4", "claude-3")
+ prompt_messages: 发送给 LLM 的消息列表
+ generated_text: 生成的文本(可截断)
+ ttft_ms: Time To First Token,首 token 延迟(毫秒)
+ total_latency_ms: 总生成延迟(毫秒)
+ prompt_tokens: 输入 token 数
+ completion_tokens: 输出 token 数
+ total_tokens: 总 token 数
+ is_streaming: 是否流式输出
+ metadata: 额外元数据
+ """
+ llm_record = {
+ "model": model,
+ "is_streaming": is_streaming,
+ "prompt_preview": str(prompt_messages)[:500], # 截断避免日志过大
+ "generated_preview": generated_text[:500] if generated_text else "",
+ "generated_length": len(generated_text) if generated_text else 0,
+ # Token 统计
+ "token_usage": {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ "total_tokens": total_tokens
+ },
+ # 延迟统计
+ "latency": {
+ "ttft_ms": ttft_ms, # Time To First Token
+ "total_ms": total_latency_ms,
+ "tokens_per_second": round(completion_tokens / (total_latency_ms / 1000), 2)
+ if completion_tokens and total_latency_ms and total_latency_ms > 0 else None
+ },
+ "timestamp": datetime.now().isoformat(),
+ "metadata": metadata or {}
+ }
+
+ if self.langfuse_client:
+ try:
+ self.langfuse_client.generation(
+ name="llm_generation",
+ model=model,
+ input=prompt_messages,
+ output=generated_text[:1000] if generated_text else "",
+ usage={
+ "prompt_tokens": prompt_tokens or 0,
+ "completion_tokens": completion_tokens or 0,
+ "total_tokens": total_tokens or 0
+ },
+ metadata={
+ "ttft_ms": ttft_ms,
+ "total_latency_ms": total_latency_ms,
+ "is_streaming": is_streaming,
+ **(metadata or {})
+ }
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record LLM generation to Langfuse: {e}")
+
+ self._log_locally("llm_generation", llm_record)
+
+ def record_ttft(self, ttft_ms: float, model: str = None, metadata: Dict = None) -> None:
+ """
+ 单独记录 TTFT (Time To First Token)
+ 用于流式生成时在收到第一个 token 时立即记录
+
+ Args:
+ ttft_ms: 首 token 延迟(毫秒)
+ model: 模型名称
+ metadata: 额外元数据
+ """
+ ttft_record = {
+ "ttft_ms": ttft_ms,
+ "model": model,
+ "timestamp": datetime.now().isoformat(),
+ "metadata": metadata or {}
+ }
+
+ if self.langfuse_client:
+ try:
+ self.langfuse_client.event(
+ name="ttft",
+ input={},
+ output={"ttft_ms": ttft_ms},
+ metadata=ttft_record
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record TTFT: {e}")
+
+ self._log_locally("ttft", ttft_record)
+
+ def add_event(self, event_name: str, event_data: Dict[str, Any] = None) -> None:
+ """
+ 添加事件记录
+
+ Args:
+ event_name: 事件名称 (如 "repo_map_generated", "file_read_failed" 等)
+ event_data: 事件相关数据
+ """
+ event_record = {
+ "event_name": event_name,
+ "event_data": event_data or {},
+ "timestamp": datetime.now().isoformat()
+ }
+
+ if self.langfuse_client:
+ try:
+ self.langfuse_client.event(
+ name=event_name,
+ input={},
+ output=event_data or {},
+ metadata=event_data or {}
+ )
+ except Exception as e:
+ print(f"⚠️ Failed to record event '{event_name}': {e}")
+
+ self._log_locally("event", event_record)
+
+ def _log_locally(self, log_type: str, data: Dict) -> None:
+ """本地日志记录"""
+ log_file = os.path.join(
+ self.config.local_log_dir,
+ f"{log_type}_{datetime.now().strftime('%Y%m%d')}.jsonl"
+ )
+
+ with open(log_file, 'a', encoding='utf-8') as f:
+ f.write(json.dumps(data, ensure_ascii=False, default=str) + '\n')
+
+ def get_trace_url(self, trace_id: str = None) -> str:
+ """获取Langfuse中该trace的URL (用于前端跳转)"""
+ if not self.langfuse_client or not trace_id:
+ return None
+
+ # Langfuse云端URL格式
+ return f"{self.config.langfuse_host}/traces/{trace_id}"
+
+
+# ============================================================================
+# 第二部分: 装饰器 - 自动追踪
+# ============================================================================
+
+def traced(operation_name: str, capture_args: List[str] = None):
+ """
+ 装饰器: 自动为被装饰函数添加追踪
+
+ 使用示例:
+ @traced("query_rewrite", capture_args=["user_query"])
+ async def rewrite_query(user_query: str):
+ ...
+ """
+
+ def decorator(func: Callable):
+ @wraps(func)
+ async def async_wrapper(*args, **kwargs):
+ start_time = time.time()
+
+ # 捕获输入参数
+ input_data = {}
+ if capture_args:
+ for arg_name in capture_args:
+ if arg_name in kwargs:
+ input_data[arg_name] = kwargs[arg_name]
+
+ try:
+ result = await func(*args, **kwargs)
+ latency_ms = (time.time() - start_time) * 1000
+
+ # 记录跨度
+ tracing_service.record_span(
+ span_name=operation_name,
+ operation=func.__name__,
+ input_data=input_data,
+ output_data={"success": True},
+ latency_ms=latency_ms
+ )
+
+ return result
+ except Exception as e:
+ latency_ms = (time.time() - start_time) * 1000
+ tracing_service.record_span(
+ span_name=operation_name,
+ operation=func.__name__,
+ input_data=input_data,
+ output_data={"error": str(e)},
+ latency_ms=latency_ms,
+ metadata={"error": True}
+ )
+ raise
+
+ @wraps(func)
+ def sync_wrapper(*args, **kwargs):
+ start_time = time.time()
+
+ input_data = {}
+ if capture_args:
+ for arg_name in capture_args:
+ if arg_name in kwargs:
+ input_data[arg_name] = kwargs[arg_name]
+
+ try:
+ result = func(*args, **kwargs)
+ latency_ms = (time.time() - start_time) * 1000
+
+ tracing_service.record_span(
+ span_name=operation_name,
+ operation=func.__name__,
+ input_data=input_data,
+ output_data={"success": True},
+ latency_ms=latency_ms
+ )
+
+ return result
+ except Exception as e:
+ latency_ms = (time.time() - start_time) * 1000
+ tracing_service.record_span(
+ span_name=operation_name,
+ operation=func.__name__,
+ input_data=input_data,
+ output_data={"error": str(e)},
+ latency_ms=latency_ms,
+ metadata={"error": True}
+ )
+ raise
+
+ # 判断是async还是sync
+ if asyncio.iscoroutinefunction(func):
+ return async_wrapper
+ else:
+ return sync_wrapper
+
+ return decorator
+
+
+# ============================================================================
+# 第三部分: 全局实例
+# ============================================================================
+
+tracing_config = TracingConfig(
+ enabled=True,
+ backend="langfuse" if LANGFUSE_AVAILABLE else "local"
+)
+
+tracing_service = TracingService(config=tracing_config)
+
+
+# ============================================================================
+# 第四部分: 集成示例 (如何在agent_service.py中使用)
+# ============================================================================
+
+"""
+在你的agent_service.py中添加:
+
+1. 导入追踪服务:
+ from app.services.tracing_service import tracing_service
+
+2. 在agent_stream函数开始:
+ trace_id = tracing_service.start_trace(
+ trace_name="github_agent_analysis",
+ session_id=session_id,
+ metadata={"repo_url": repo_url, "language": language}
+ )
+
+3. 在generate_repo_map函数周围:
+ start_time = time.time()
+ file_tree_str, mapped_files = await generate_repo_map(repo_url, file_list, limit=limit)
+ latency_ms = (time.time() - start_time) * 1000
+
+ tracing_service.record_span(
+ span_name="generate_repo_map",
+ operation="repo_mapping",
+ input_data={"file_count": len(file_list), "limit": limit},
+ output_data={"files_in_map": len(mapped_files)},
+ latency_ms=latency_ms
+ )
+
+4. 在process_single_file中记录检索:
+ tracing_service.record_retrieval_debug(
+ query=search_query,
+ retrieved_files=valid_files,
+ vector_scores=vector_scores,
+ bm25_scores=bm25_scores,
+ latency_ms=search_latency
+ )
+
+5. 工具调用记录:
+ start_time = time.time()
+ try:
+ result = get_file_content(repo_url, file_path)
+ tracing_service.record_tool_call(
+ tool_name="get_file_content",
+ parameters={"file_path": file_path},
+ result=result[:100] if result else None,
+ latency_ms=(time.time() - start_time) * 1000,
+ success=True
+ )
+ except Exception as e:
+ tracing_service.record_tool_call(
+ tool_name="get_file_content",
+ parameters={"file_path": file_path},
+ result=None,
+ latency_ms=(time.time() - start_time) * 1000,
+ success=False,
+ error=str(e)
+ )
+"""
+
+import asyncio
diff --git a/app/services/vector_service.py b/app/services/vector_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d2ecd56908d3730c34098766964c0822f217c7e
--- /dev/null
+++ b/app/services/vector_service.py
@@ -0,0 +1,676 @@
+# -*- coding: utf-8 -*-
+"""
+向量服务层 - Qdrant 版
+
+特性:
+1. 混合搜索 - Qdrant 向量 + BM25 关键词,RRF 融合
+2. 异步原生 - 全链路异步
+3. 会话隔离 - 每个 session 独立集合
+4. 状态持久化 - 仓库信息、BM25 索引缓存
+"""
+
+import asyncio
+import json
+import logging
+import os
+import pickle
+import re
+import tempfile
+import time
+from dataclasses import dataclass, field
+from typing import List, Dict, Any, Optional, Set
+
+from rank_bm25 import BM25Okapi
+
+from app.core.config import settings
+from app.storage.base import Document, SearchResult, CollectionStats
+from app.storage.qdrant_store import QdrantVectorStore, QdrantConfig, get_qdrant_factory
+from app.utils.embedding import get_embedding_service, EmbeddingConfig
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 使用统一配置
+# ============================================================
+
+from app.core.config import vector_config as config
+
+# 确保目录存在
+os.makedirs(config.context_dir, exist_ok=True)
+
+# === 向后兼容导出 (供 main.py 使用) ===
+vector_config = config # 兼容旧名称
+CONTEXT_DIR = config.context_dir
+QDRANT_DIR = config.data_dir # Qdrant 数据目录
+
+
+# ============================================================
+# Embedding 服务
+# ============================================================
+
+_embedding_service = None
+
+def get_embedding():
+ """获取 Embedding 服务单例"""
+ global _embedding_service
+ if _embedding_service is None:
+ emb_config = EmbeddingConfig(
+ api_base_url=config.embedding_api_url,
+ model_name=config.embedding_model,
+ batch_size=config.embedding_batch_size,
+ max_text_length=config.embedding_max_length,
+ max_concurrent_batches=config.embedding_concurrency,
+ )
+ _embedding_service = get_embedding_service(emb_config)
+ return _embedding_service
+
+
+# ============================================================
+# 向量存储服务
+# ============================================================
+
+class VectorStore:
+ """
+ 向量存储服务
+
+ 整合 Qdrant 向量搜索和 BM25 关键词搜索
+
+ 使用示例:
+ ```python
+ store = VectorStore("session_123")
+ await store.initialize()
+
+ # 重置 (分析新仓库时)
+ await store.reset()
+
+ # 添加文档
+ await store.add_documents(documents, metadatas)
+
+ # 混合搜索
+ results = await store.search_hybrid("how does auth work?")
+
+ await store.close()
+ ```
+ """
+
+ def __init__(self, session_id: str):
+ self.session_id = self._sanitize_id(session_id)
+ self.collection_name = f"repo_{self.session_id}"
+
+ # Qdrant 存储
+ self._qdrant: Optional[QdrantVectorStore] = None
+
+ # BM25 索引 (内存)
+ self._bm25: Optional[BM25Okapi] = None
+ self._doc_store: List[Document] = []
+ self._indexed_files: Set[str] = set()
+
+ # 上下文
+ self.repo_url: Optional[str] = None
+ self.global_context: Dict[str, Any] = {}
+
+ # 文件路径
+ self._context_file = os.path.join(config.context_dir, f"{self.session_id}.json")
+ self._cache_file = os.path.join(config.context_dir, f"{self.session_id}_bm25.pkl")
+
+ self._initialized = False
+
+ @staticmethod
+ def _sanitize_id(session_id: str) -> str:
+ """清理 session ID"""
+ clean = re.sub(r'[^a-zA-Z0-9_-]', '', session_id)
+ if not clean:
+ raise ValueError("Invalid session_id")
+ return clean
+
+ async def initialize(self) -> None:
+ """初始化存储"""
+ if self._initialized:
+ return
+
+ # 初始化 Qdrant
+ factory = get_qdrant_factory()
+ self._qdrant = factory.create(self.collection_name)
+ await self._qdrant.initialize()
+
+ # 加载本地状态
+ await self._load_state()
+
+ self._initialized = True
+ logger.debug(f"✅ VectorStore 初始化: {self.session_id}")
+
+ async def close(self) -> None:
+ """关闭连接"""
+ if self._qdrant:
+ await self._qdrant.close()
+ self._qdrant = None
+ self._initialized = False
+
+ async def _load_state(self) -> None:
+ """加载状态"""
+ # 1. 加载上下文 JSON
+ if os.path.exists(self._context_file):
+ try:
+ with open(self._context_file, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+ self.repo_url = data.get("repo_url")
+ self.global_context = data.get("global_context", {})
+ except Exception as e:
+ logger.warning(f"加载上下文失败: {e}")
+
+ # 2. 尝试加载 BM25 缓存
+ cache_loaded = False
+ if os.path.exists(self._cache_file):
+ try:
+ with open(self._cache_file, 'rb') as f:
+ cache = pickle.load(f)
+ if isinstance(cache, dict) and cache.get("version") == config.cache_version:
+ self._bm25 = cache.get("bm25")
+ self._doc_store = cache.get("doc_store", [])
+ self._indexed_files = cache.get("indexed_files", set())
+ cache_loaded = True
+ logger.debug(f"📦 BM25 缓存命中: {len(self._doc_store)} 文档")
+ except Exception as e:
+ logger.warning(f"BM25 缓存损坏: {e}")
+ os.remove(self._cache_file)
+
+ # 3. 缓存未命中: 从 Qdrant 重建
+ if not cache_loaded and self._qdrant:
+ await self._rebuild_bm25_index()
+
+ async def _rebuild_bm25_index(self) -> None:
+ """从 Qdrant 重建 BM25 索引"""
+ logger.info(f"🔄 重建 BM25 索引: {self.session_id}")
+
+ documents = await self._qdrant.get_all_documents()
+
+ if documents:
+ self._doc_store = documents
+ self._indexed_files = {doc.file_path for doc in documents if doc.file_path}
+
+ tokenized = [self._tokenize(doc.content) for doc in documents]
+ if tokenized:
+ self._bm25 = BM25Okapi(tokenized)
+
+ self._save_bm25_cache()
+ logger.info(f"✅ BM25 索引重建完成: {len(documents)} 文档")
+
+ def _save_bm25_cache(self) -> None:
+ """保存 BM25 缓存 (原子写入)"""
+ if not self._doc_store:
+ return
+
+ try:
+ fd, tmp_path = tempfile.mkstemp(dir=config.context_dir)
+ with os.fdopen(fd, 'wb') as f:
+ pickle.dump({
+ "version": config.cache_version,
+ "bm25": self._bm25,
+ "doc_store": self._doc_store,
+ "indexed_files": self._indexed_files,
+ }, f)
+
+ if os.path.exists(self._cache_file):
+ os.remove(self._cache_file)
+ os.rename(tmp_path, self._cache_file)
+
+ except Exception as e:
+ logger.error(f"保存 BM25 缓存失败: {e}")
+
+ def _tokenize(self, text: str) -> List[str]:
+ """分词"""
+ return [
+ t.lower() for t in re.split(config.tokenize_regex, text)
+ if t.strip()
+ ]
+
+ async def save_context(self, repo_url: str, context_data: Dict[str, Any]) -> None:
+ """保存仓库上下文 (异步,不阻塞事件循环)"""
+ self.repo_url = repo_url
+ self.global_context = context_data
+ await asyncio.to_thread(self._write_context_file, {
+ "repo_url": repo_url,
+ "global_context": context_data,
+ })
+
+ def _write_context_file(self, updates: Dict[str, Any]) -> None:
+ """写入上下文文件 (同步,供线程池调用)"""
+ try:
+ existing = {}
+ if os.path.exists(self._context_file):
+ with open(self._context_file, 'r', encoding='utf-8') as f:
+ existing = json.load(f)
+ existing.update(updates)
+ with open(self._context_file, 'w', encoding='utf-8') as f:
+ json.dump(existing, f, ensure_ascii=False, indent=2)
+ except Exception as e:
+ logger.error(f"写入上下文失败: {e}")
+
+ async def save_report(self, report: str, language: str = "en") -> None:
+ """保存技术报告 (异步,不阻塞事件循环)"""
+ await asyncio.to_thread(self._write_report, report, language)
+
+ def _write_report(self, report: str, language: str) -> None:
+ """写入报告 (同步,供线程池调用)"""
+ try:
+ existing = {}
+ if os.path.exists(self._context_file):
+ with open(self._context_file, 'r', encoding='utf-8') as f:
+ existing = json.load(f)
+
+ if "reports" not in existing:
+ existing["reports"] = {}
+ existing["reports"][language] = report
+ existing["report"] = report
+ existing["report_language"] = language
+
+ with open(self._context_file, 'w', encoding='utf-8') as f:
+ json.dump(existing, f, ensure_ascii=False, indent=2)
+ logger.info(f"📝 报告已保存: {self.session_id} ({language})")
+ except Exception as e:
+ logger.error(f"保存报告失败: {e}")
+
+ def get_report(self, language: str = "en") -> Optional[str]:
+ """
+ 获取指定语言的报告
+
+ Args:
+ language: 语言代码 ('en', 'zh')
+
+ Returns:
+ 报告内容,不存在返回 None
+ """
+ context = self.load_context()
+ if not context:
+ return None
+
+ # 优先从 reports 字典获取
+ reports = context.get("reports", {})
+ if language in reports:
+ return reports[language]
+
+ # 兼容旧格式:如果只有 report 字段且语言匹配
+ if "report" in context:
+ stored_lang = context.get("report_language", "en")
+ if stored_lang == language:
+ return context["report"]
+
+ return None
+
+ def get_available_languages(self) -> List[str]:
+ """获取已有报告的语言列表"""
+ context = self.load_context()
+ if not context:
+ return []
+
+ reports = context.get("reports", {})
+ return list(reports.keys())
+
+ def load_context(self) -> Optional[Dict[str, Any]]:
+ """
+ 加载仓库上下文
+
+ Returns:
+ 包含 repo_url, global_context, report 等的字典,不存在返回 None
+ """
+ if not os.path.exists(self._context_file):
+ return None
+
+ try:
+ with open(self._context_file, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+
+ # 恢复内存状态
+ self.repo_url = data.get("repo_url")
+ self.global_context = data.get("global_context", {})
+
+ return data
+ except Exception as e:
+ logger.error(f"加载上下文失败: {e}")
+ return None
+
+ def has_index(self) -> bool:
+ """检查是否已有索引"""
+ context = self.load_context()
+ return context is not None and context.get("repo_url") is not None
+
+ async def reset(self) -> None:
+ """重置存储 (分析新仓库时调用)"""
+ await self.initialize()
+
+ # 删除 Qdrant 集合
+ if self._qdrant:
+ await self._qdrant.delete_collection()
+ await self._qdrant.initialize()
+
+ # 清理本地文件
+ for f in [self._context_file, self._cache_file]:
+ if os.path.exists(f):
+ os.remove(f)
+
+ # 重置内存状态
+ self._bm25 = None
+ self._doc_store = []
+ self._indexed_files = set()
+ self.repo_url = None
+ self.global_context = {}
+
+ logger.info(f"🗑️ 重置存储: {self.session_id}")
+
+ # 兼容旧接口
+ def reset_collection(self) -> None:
+ """同步重置 (兼容旧代码)"""
+ asyncio.get_event_loop().run_until_complete(self.reset())
+
+ async def add_documents(
+ self,
+ documents: List[str],
+ metadatas: List[Dict[str, Any]]
+ ) -> int:
+ """
+ 添加文档
+
+ Args:
+ documents: 文档内容列表
+ metadatas: 元数据列表
+
+ Returns:
+ 成功添加的数量
+ """
+ if not documents:
+ return 0
+
+ await self.initialize()
+
+ # 1. 批量获取 Embedding
+ logger.info(f"📊 Embedding: {len(documents)} 个文档")
+ embedding_service = get_embedding()
+ embeddings = await embedding_service.embed_batch(documents, show_progress=True)
+
+ # 过滤无效的
+ valid_indices = [i for i, emb in enumerate(embeddings) if emb]
+ if not valid_indices:
+ logger.error("所有 Embedding 都失败了")
+ return 0
+
+ # 2. 构建 Document 对象
+ docs = []
+ for i in valid_indices:
+ doc_id = f"{metadatas[i].get('file', 'unknown')}_{len(self._doc_store) + len(docs)}"
+ doc = Document(
+ id=doc_id,
+ content=documents[i],
+ metadata=metadatas[i],
+ )
+ docs.append(doc)
+
+ valid_embeddings = [embeddings[i] for i in valid_indices]
+
+ # 3. 写入 Qdrant
+ added = await self._qdrant.add_documents(docs, valid_embeddings)
+
+ # 4. 更新 BM25 索引 (放入线程池,避免阻塞)
+ self._doc_store.extend(docs)
+ self._indexed_files.update(doc.file_path for doc in docs)
+
+ await asyncio.to_thread(self._rebuild_bm25_sync)
+
+ return added
+
+ def _rebuild_bm25_sync(self) -> None:
+ """重建 BM25 索引 (同步,用于线程池)"""
+ tokenized = [self._tokenize(doc.content) for doc in self._doc_store]
+ self._bm25 = BM25Okapi(tokenized)
+ self._save_bm25_cache()
+
+ async def embed_text(self, text: str) -> List[float]:
+ """获取文本 Embedding"""
+ embedding_service = get_embedding()
+ return await embedding_service.embed_text(text)
+
+ async def search_hybrid(
+ self,
+ query: str,
+ top_k: int = None
+ ) -> List[Dict[str, Any]]:
+ """
+ 混合搜索 (向量 + BM25,RRF 融合)
+
+ Args:
+ query: 查询文本
+ top_k: 返回数量
+
+ Returns:
+ 搜索结果列表
+ """
+ await self.initialize()
+
+ top_k = top_k or config.default_top_k
+ candidate_k = top_k * config.search_oversample
+
+ # 1. 向量搜索
+ vector_results: List[SearchResult] = []
+ query_embedding = await self.embed_text(query)
+
+ if query_embedding and self._qdrant:
+ vector_results = await self._qdrant.search(
+ query_embedding,
+ top_k=candidate_k
+ )
+
+ # 2. BM25 搜索
+ bm25_results: List[SearchResult] = []
+ if self._bm25 and self._doc_store:
+ tokens = self._tokenize(query)
+ if not tokens:
+ tokens = [""]
+
+ try:
+ scores = self._bm25.get_scores(tokens)
+ top_indices = sorted(
+ range(len(scores)),
+ key=lambda i: scores[i],
+ reverse=True
+ )[:candidate_k]
+
+ for idx in top_indices:
+ if scores[idx] > 0:
+ doc = self._doc_store[idx]
+ bm25_results.append(SearchResult(
+ document=doc,
+ score=scores[idx],
+ source="bm25",
+ ))
+ except Exception as e:
+ logger.error(f"BM25 搜索失败: {e}")
+
+ # 3. RRF 融合
+ fused = self._rrf_fusion(vector_results, bm25_results)
+
+ # 4. 格式化输出 (兼容旧接口)
+ results = []
+ for item in fused[:top_k]:
+ doc = item.document
+ results.append({
+ "id": doc.id,
+ "content": doc.content,
+ "file": doc.file_path,
+ "metadata": doc.metadata,
+ "score": item.score,
+ })
+
+ return results
+
+ def _rrf_fusion(
+ self,
+ vector_results: List[SearchResult],
+ bm25_results: List[SearchResult]
+ ) -> List[SearchResult]:
+ """RRF (Reciprocal Rank Fusion) 融合"""
+ k = config.rrf_k
+ fused: Dict[str, Dict] = {}
+
+ # 向量结果
+ for rank, result in enumerate(vector_results):
+ doc_id = result.document.id
+ if doc_id not in fused:
+ fused[doc_id] = {"result": result, "score": 0}
+ fused[doc_id]["score"] += config.rrf_weight_vector / (k + rank + 1)
+
+ # BM25 结果
+ for rank, result in enumerate(bm25_results):
+ doc_id = result.document.id
+ if doc_id not in fused:
+ fused[doc_id] = {"result": result, "score": 0}
+ fused[doc_id]["score"] += config.rrf_weight_bm25 / (k + rank + 1)
+
+ # 排序
+ sorted_items = sorted(
+ fused.values(),
+ key=lambda x: x["score"],
+ reverse=True
+ )
+
+ return [
+ SearchResult(
+ document=item["result"].document,
+ score=item["score"],
+ source="hybrid",
+ )
+ for item in sorted_items
+ ]
+
+ def get_documents_by_file(self, file_path: str) -> List[Dict[str, Any]]:
+ """根据文件路径获取文档 (兼容旧接口)"""
+ docs = [
+ doc for doc in self._doc_store
+ if doc.file_path == file_path
+ ]
+
+ result = []
+ for doc in sorted(docs, key=lambda d: d.metadata.get("start_line", 0)):
+ result.append({
+ "id": doc.id,
+ "content": doc.content,
+ "file": doc.file_path,
+ "metadata": doc.metadata,
+ "score": 1.0,
+ })
+
+ return result
+
+ @property
+ def indexed_files(self) -> Set[str]:
+ """已索引的文件"""
+ return self._indexed_files
+
+
+# ============================================================
+# 管理器 - LRU Cache + 过期清理
+# ============================================================
+
+class SessionEntry:
+ """Session 条目 - 包含存储实例和访问时间"""
+ __slots__ = ('store', 'last_access', 'created_at')
+
+ def __init__(self, store: VectorStore):
+ self.store = store
+ self.last_access = time.time()
+ self.created_at = time.time()
+
+ def touch(self) -> None:
+ """更新访问时间"""
+ self.last_access = time.time()
+
+
+class VectorStoreManager:
+ """
+ 向量存储管理器 - LRU Cache 实现
+
+ 特性:
+ 1. LRU 淘汰 - 超过 max_count 时淘汰最久未访问的内存中的 session
+ 2. 仓库数据永久存储 - 不清理仓库索引和报告
+ 3. 线程安全 - 使用 asyncio.Lock
+ """
+
+ def __init__(self, max_count: int = None):
+ self._max_count = max_count or config.session_max_count
+ self._sessions: Dict[str, SessionEntry] = {}
+ self._lock = asyncio.Lock()
+
+ def get_store(self, session_id: str) -> VectorStore:
+ """
+ 获取或创建存储实例 (同步接口,兼容现有代码)
+
+ 会触发 LRU 淘汰检查
+ """
+ if session_id in self._sessions:
+ entry = self._sessions[session_id]
+ entry.touch()
+ # 移动到最后(模拟 LRU)
+ self._sessions.pop(session_id)
+ self._sessions[session_id] = entry
+ return entry.store
+
+ # 创建新 session
+ store = VectorStore(session_id)
+ entry = SessionEntry(store)
+ self._sessions[session_id] = entry
+
+ # 检查是否需要 LRU 淘汰(异步执行)
+ if len(self._sessions) > self._max_count:
+ asyncio.create_task(self._evict_lru())
+
+ logger.info(f"📦 Session 创建: {session_id} (总数: {len(self._sessions)})")
+ return store
+
+ async def _evict_lru(self) -> None:
+ """淘汰最久未访问的 session"""
+ async with self._lock:
+ while len(self._sessions) > self._max_count:
+ # 找到最久未访问的
+ oldest_id = min(
+ self._sessions.keys(),
+ key=lambda k: self._sessions[k].last_access
+ )
+ entry = self._sessions.pop(oldest_id)
+ await entry.store.close()
+ logger.info(f"🗑️ LRU 淘汰: {oldest_id}")
+
+ async def close_session(self, session_id: str) -> None:
+ """关闭指定 session"""
+ async with self._lock:
+ if session_id in self._sessions:
+ entry = self._sessions.pop(session_id)
+ await entry.store.close()
+ logger.info(f"🔒 Session 关闭: {session_id}")
+
+ async def close_all(self) -> None:
+ """关闭所有连接"""
+ async with self._lock:
+ for session_id, entry in list(self._sessions.items()):
+ await entry.store.close()
+ self._sessions.clear()
+ logger.info("🔒 所有 Session 已关闭")
+
+ def get_stats(self) -> Dict[str, Any]:
+ """获取管理器统计信息"""
+ now = time.time()
+ sessions_info = []
+ for sid, entry in self._sessions.items():
+ sessions_info.append({
+ "session_id": sid,
+ "age_hours": round((now - entry.created_at) / 3600, 2),
+ "idle_minutes": round((now - entry.last_access) / 60, 2),
+ })
+
+ return {
+ "total_sessions": len(self._sessions),
+ "max_sessions": self._max_count,
+ "sessions": sorted(sessions_info, key=lambda x: x["idle_minutes"], reverse=True)
+ }
+
+
+# 全局管理器
+store_manager = VectorStoreManager()
diff --git a/app/storage/__init__.py b/app/storage/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f981e63cf1d56e2e6c50a14120ac7685908b42a
--- /dev/null
+++ b/app/storage/__init__.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+"""
+存储层模块
+
+提供向量存储的抽象和实现
+"""
+
+from app.storage.base import (
+ Document,
+ SearchResult,
+ CollectionStats,
+ StorageBackend,
+ BaseVectorStore,
+)
+from app.storage.qdrant_store import (
+ QdrantConfig,
+ QdrantVectorStore,
+ QdrantStoreFactory,
+ get_qdrant_factory,
+)
+
+__all__ = [
+ # 基础类型
+ "Document",
+ "SearchResult",
+ "CollectionStats",
+ "StorageBackend",
+ "BaseVectorStore",
+ # Qdrant
+ "QdrantConfig",
+ "QdrantVectorStore",
+ "QdrantStoreFactory",
+ "get_qdrant_factory",
+]
diff --git a/app/storage/base.py b/app/storage/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1dc2576c90e12b1ca9401c59d0e85cfc171430d
--- /dev/null
+++ b/app/storage/base.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+"""
+向量存储抽象层
+
+设计原则:
+1. 接口与实现分离 - 易于切换存储后端
+2. 异步优先 - 所有 I/O 操作都是异步的
+3. 类型安全 - 完整的类型注解
+4. 可观测 - 内置指标收集
+"""
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import List, Dict, Any, Optional, Set
+from enum import Enum
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 数据模型
+# ============================================================
+
+@dataclass
+class Document:
+ """文档数据模型"""
+ id: str
+ content: str
+ metadata: Dict[str, Any] = field(default_factory=dict)
+ embedding: Optional[List[float]] = None
+
+ @property
+ def file_path(self) -> str:
+ return self.metadata.get("file", "")
+
+ def to_dict(self) -> Dict[str, Any]:
+ return {
+ "id": self.id,
+ "content": self.content,
+ "metadata": self.metadata,
+ }
+
+
+@dataclass
+class SearchResult:
+ """搜索结果"""
+ document: Document
+ score: float
+ source: str = "vector" # "vector" | "bm25" | "hybrid"
+
+ def to_dict(self) -> Dict[str, Any]:
+ return {
+ "id": self.document.id,
+ "content": self.document.content,
+ "file": self.document.file_path,
+ "metadata": self.document.metadata,
+ "score": self.score,
+ "source": self.source,
+ }
+
+
+@dataclass
+class CollectionStats:
+ """集合统计信息"""
+ name: str
+ document_count: int
+ indexed_files: Set[str] = field(default_factory=set)
+ vector_dimension: int = 0
+
+
+class StorageBackend(Enum):
+ """存储后端类型"""
+ QDRANT = "qdrant"
+ CHROMA = "chroma" # 保留兼容性
+
+
+# ============================================================
+# 抽象基类
+# ============================================================
+
+class BaseVectorStore(ABC):
+ """
+ 向量存储抽象基类
+
+ 所有存储后端必须实现这些方法
+ """
+
+ @abstractmethod
+ async def initialize(self) -> None:
+ """初始化存储连接"""
+ pass
+
+ @abstractmethod
+ async def close(self) -> None:
+ """关闭连接"""
+ pass
+
+ @abstractmethod
+ async def add_documents(
+ self,
+ documents: List[Document],
+ embeddings: List[List[float]]
+ ) -> int:
+ """
+ 添加文档
+
+ Args:
+ documents: 文档列表
+ embeddings: 对应的嵌入向量
+
+ Returns:
+ 成功添加的文档数量
+ """
+ pass
+
+ @abstractmethod
+ async def search(
+ self,
+ query_embedding: List[float],
+ top_k: int = 10,
+ filter_conditions: Optional[Dict[str, Any]] = None
+ ) -> List[SearchResult]:
+ """
+ 向量相似度搜索
+
+ Args:
+ query_embedding: 查询向量
+ top_k: 返回数量
+ filter_conditions: 过滤条件
+
+ Returns:
+ 搜索结果列表
+ """
+ pass
+
+ @abstractmethod
+ async def delete_collection(self) -> bool:
+ """删除当前集合"""
+ pass
+
+ @abstractmethod
+ async def get_stats(self) -> CollectionStats:
+ """获取集合统计信息"""
+ pass
+
+ @abstractmethod
+ async def get_documents_by_file(self, file_path: str) -> List[Document]:
+ """根据文件路径获取文档"""
+ pass
+
+
+class BaseVectorStoreFactory(ABC):
+ """向量存储工厂基类"""
+
+ @abstractmethod
+ def create(self, collection_name: str) -> BaseVectorStore:
+ """创建存储实例"""
+ pass
diff --git a/app/storage/qdrant_store.py b/app/storage/qdrant_store.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd810506bbb8d4db7ba831695da4943efd8002b1
--- /dev/null
+++ b/app/storage/qdrant_store.py
@@ -0,0 +1,578 @@
+# -*- coding: utf-8 -*-
+"""
+Qdrant 向量存储实现
+
+特性:
+1. 异步原生 - 使用 qdrant-client AsyncQdrantClient
+2. 高性能 - 批量 upsert、HNSW 索引、payload 索引
+3. 混合搜索 - 向量 + 稀疏向量 (FastEmbed)
+4. 连接池 - gRPC 长连接复用
+5. 可观测 - 完整的日志和指标
+"""
+
+import asyncio
+import logging
+import os
+from dataclasses import dataclass
+from typing import List, Dict, Any, Optional, Set
+from contextlib import asynccontextmanager
+
+from qdrant_client import AsyncQdrantClient, models
+from qdrant_client.models import (
+ Distance,
+ VectorParams,
+ PointStruct,
+ Filter,
+ FieldCondition,
+ MatchValue,
+ PayloadSchemaType,
+)
+
+from app.storage.base import (
+ BaseVectorStore,
+ Document,
+ SearchResult,
+ CollectionStats,
+)
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 配置
+# ============================================================
+
+@dataclass
+class QdrantConfig:
+ """
+ Qdrant 配置
+
+ 支持三种模式:
+ - local: 本地嵌入式 (开发/单进程)
+ - server: Qdrant Server (多 Worker 生产环境)
+ - cloud: Qdrant Cloud (托管服务)
+
+ 环境变量:
+ - QDRANT_MODE: "local" | "server" | "cloud"
+ - QDRANT_URL: 服务器地址 (server/cloud 模式)
+ - QDRANT_API_KEY: API 密钥 (cloud 模式必需)
+ - QDRANT_LOCAL_PATH: 本地存储路径 (local 模式)
+ """
+ # 模式: "local" | "server" | "cloud"
+ mode: str = "local"
+
+ # Server/Cloud 模式配置
+ url: Optional[str] = None
+ host: str = "localhost"
+ port: int = 6333
+ grpc_port: int = 6334
+ prefer_grpc: bool = True
+ api_key: Optional[str] = None
+
+ # Local 模式配置
+ local_path: str = "data/qdrant_db"
+
+ # 向量配置
+ vector_size: int = 1024 # BGE-M3 维度
+ distance: Distance = Distance.COSINE
+
+ # 索引配置
+ hnsw_m: int = 16 # HNSW 图的边数
+ hnsw_ef_construct: int = 100 # 构建时的搜索深度
+
+ # 批量操作
+ batch_size: int = 100
+
+ # 超时
+ timeout: float = 30.0
+
+ @classmethod
+ def from_env(cls) -> "QdrantConfig":
+ """从环境变量加载配置"""
+ mode = os.getenv("QDRANT_MODE", "local").lower()
+
+ return cls(
+ mode=mode,
+ url=os.getenv("QDRANT_URL"),
+ host=os.getenv("QDRANT_HOST", "localhost"),
+ port=int(os.getenv("QDRANT_PORT", "6333")),
+ grpc_port=int(os.getenv("QDRANT_GRPC_PORT", "6334")),
+ api_key=os.getenv("QDRANT_API_KEY"),
+ local_path=os.getenv("QDRANT_LOCAL_PATH", "data/qdrant_db"),
+ vector_size=int(os.getenv("QDRANT_VECTOR_SIZE", "1024")),
+ prefer_grpc=os.getenv("QDRANT_PREFER_GRPC", "true").lower() == "true",
+ )
+
+ @property
+ def is_local(self) -> bool:
+ return self.mode == "local"
+
+ @property
+ def is_server(self) -> bool:
+ return self.mode == "server"
+
+ @property
+ def is_cloud(self) -> bool:
+ return self.mode == "cloud"
+
+ def validate(self) -> None:
+ """验证配置"""
+ if self.is_cloud and not self.api_key:
+ raise ValueError("QDRANT_API_KEY is required for cloud mode")
+ if (self.is_server or self.is_cloud) and not (self.url or self.host):
+ raise ValueError("QDRANT_URL or QDRANT_HOST is required for server/cloud mode")
+
+
+# ============================================================
+# 全局共享客户端单例
+# ============================================================
+
+_shared_client: Optional[AsyncQdrantClient] = None
+_shared_config: Optional[QdrantConfig] = None
+_client_lock = asyncio.Lock()
+
+
+async def get_shared_client(config: Optional[QdrantConfig] = None) -> AsyncQdrantClient:
+ """
+ 获取共享的 Qdrant 客户端单例
+
+ 支持三种模式:
+ - local: 本地嵌入式存储 (单进程,开发环境)
+ - server: Qdrant Server (多 Worker,Docker 部署)
+ - cloud: Qdrant Cloud (托管服务)
+ """
+ global _shared_client, _shared_config
+
+ async with _client_lock:
+ if _shared_client is None:
+ _shared_config = config or QdrantConfig.from_env()
+ _shared_config.validate()
+
+ if _shared_config.is_local:
+ # Local 模式: 嵌入式存储
+ os.makedirs(_shared_config.local_path, exist_ok=True)
+ _shared_client = AsyncQdrantClient(
+ path=_shared_config.local_path,
+ timeout=_shared_config.timeout,
+ )
+ logger.info(f"📦 Qdrant 本地模式: {_shared_config.local_path}")
+
+ elif _shared_config.is_server:
+ # Server 模式: 连接 Qdrant Server
+ if _shared_config.url:
+ _shared_client = AsyncQdrantClient(
+ url=_shared_config.url,
+ prefer_grpc=_shared_config.prefer_grpc,
+ timeout=_shared_config.timeout,
+ )
+ logger.info(f"🌐 Qdrant Server 模式: {_shared_config.url}")
+ else:
+ _shared_client = AsyncQdrantClient(
+ host=_shared_config.host,
+ port=_shared_config.port,
+ grpc_port=_shared_config.grpc_port,
+ prefer_grpc=_shared_config.prefer_grpc,
+ timeout=_shared_config.timeout,
+ )
+ logger.info(f"🌐 Qdrant Server 模式: {_shared_config.host}:{_shared_config.port}")
+
+ else:
+ # Cloud 模式: 连接 Qdrant Cloud
+ _shared_client = AsyncQdrantClient(
+ url=_shared_config.url,
+ api_key=_shared_config.api_key,
+ timeout=_shared_config.timeout,
+ )
+ logger.info(f"☁️ Qdrant Cloud 模式: {_shared_config.url}")
+
+ return _shared_client
+
+ return _shared_client
+
+
+async def close_shared_client() -> None:
+ """关闭共享客户端"""
+ global _shared_client
+ if _shared_client is not None:
+ await _shared_client.close()
+ _shared_client = None
+ logger.info("🔒 Qdrant 共享客户端已关闭")
+
+
+# ============================================================
+# Qdrant 存储实现
+# ============================================================
+
+class QdrantVectorStore(BaseVectorStore):
+ """
+ Qdrant 向量存储
+
+ 使用示例:
+ ```python
+ config = QdrantConfig.from_env()
+ store = QdrantVectorStore("my_collection", config)
+
+ await store.initialize()
+
+ # 添加文档
+ docs = [Document(id="1", content="hello", metadata={"file": "a.py"})]
+ embeddings = [[0.1, 0.2, ...]]
+ await store.add_documents(docs, embeddings)
+
+ # 搜索
+ results = await store.search(query_embedding, top_k=5)
+
+ await store.close()
+ ```
+ """
+
+ # Payload 字段名常量
+ FIELD_CONTENT = "content"
+ FIELD_FILE = "file"
+ FIELD_METADATA = "metadata"
+
+ def __init__(
+ self,
+ collection_name: str,
+ config: Optional[QdrantConfig] = None
+ ):
+ self.collection_name = self._sanitize_name(collection_name)
+ self.config = config or QdrantConfig.from_env()
+ self._initialized = False
+
+ @staticmethod
+ def _sanitize_name(name: str) -> str:
+ """清理集合名称"""
+ import re
+ clean = re.sub(r'[^a-zA-Z0-9_-]', '_', name)
+ return clean[:63] if clean else "default"
+
+ async def _get_client(self) -> AsyncQdrantClient:
+ """获取共享客户端 (解决 Qdrant Local 并发访问问题)"""
+ return await get_shared_client(self.config)
+
+ async def initialize(self) -> None:
+ """初始化集合"""
+ if self._initialized:
+ return
+
+ client = await self._get_client()
+
+ # 检查集合是否存在
+ collections = await client.get_collections()
+ exists = any(c.name == self.collection_name for c in collections.collections)
+
+ if not exists:
+ # 创建集合
+ await client.create_collection(
+ collection_name=self.collection_name,
+ vectors_config=VectorParams(
+ size=self.config.vector_size,
+ distance=self.config.distance,
+ hnsw_config=models.HnswConfigDiff(
+ m=self.config.hnsw_m,
+ ef_construct=self.config.hnsw_ef_construct,
+ ),
+ ),
+ # 启用 payload 索引以加速过滤
+ optimizers_config=models.OptimizersConfigDiff(
+ indexing_threshold=0, # 立即索引
+ ),
+ )
+
+ # 创建 payload 索引
+ await client.create_payload_index(
+ collection_name=self.collection_name,
+ field_name=self.FIELD_FILE,
+ field_schema=PayloadSchemaType.KEYWORD,
+ )
+
+ logger.info(f"✅ 创建集合: {self.collection_name}")
+ else:
+ logger.debug(f"📂 集合已存在: {self.collection_name}")
+
+ self._initialized = True
+
+ async def close(self) -> None:
+ """
+ 关闭连接 (使用共享客户端时不实际关闭)
+
+ 注意: 由于使用共享客户端,单个 Store 的 close() 不会关闭客户端。
+ 全局关闭请使用 close_shared_client()
+ """
+ self._initialized = False
+ logger.debug(f"🔌 Store 已关闭: {self.collection_name}")
+
+ async def add_documents(
+ self,
+ documents: List[Document],
+ embeddings: List[List[float]]
+ ) -> int:
+ """批量添加文档"""
+ if not documents or not embeddings:
+ return 0
+
+ if len(documents) != len(embeddings):
+ raise ValueError(f"文档数量 ({len(documents)}) 与向量数量 ({len(embeddings)}) 不匹配")
+
+ await self.initialize()
+ client = await self._get_client()
+
+ # 过滤空向量
+ valid_pairs = [
+ (doc, emb) for doc, emb in zip(documents, embeddings)
+ if emb and len(emb) == self.config.vector_size
+ ]
+
+ if not valid_pairs:
+ logger.warning("没有有效的文档向量对")
+ return 0
+
+ # 构建 Points
+ points = []
+ for doc, embedding in valid_pairs:
+ point = PointStruct(
+ id=self._generate_point_id(doc.id),
+ vector=embedding,
+ payload={
+ self.FIELD_CONTENT: doc.content,
+ self.FIELD_FILE: doc.file_path,
+ self.FIELD_METADATA: doc.metadata,
+ "doc_id": doc.id,
+ },
+ )
+ points.append(point)
+
+ # 批量 upsert
+ total_added = 0
+ batch_size = self.config.batch_size
+
+ for i in range(0, len(points), batch_size):
+ batch = points[i:i + batch_size]
+ try:
+ await client.upsert(
+ collection_name=self.collection_name,
+ points=batch,
+ wait=True,
+ )
+ total_added += len(batch)
+ except Exception as e:
+ logger.error(f"批次 {i // batch_size + 1} 写入失败: {e}")
+
+ logger.info(f"✅ 写入 {total_added}/{len(points)} 个文档到 {self.collection_name}")
+ return total_added
+
+ def _generate_point_id(self, doc_id: str) -> int:
+ """生成数值型 Point ID (Qdrant 要求)"""
+ import hashlib
+ hash_bytes = hashlib.sha256(doc_id.encode()).digest()
+ # 取前 8 字节转为正整数
+ return int.from_bytes(hash_bytes[:8], byteorder='big') & 0x7FFFFFFFFFFFFFFF
+
+ async def search(
+ self,
+ query_embedding: List[float],
+ top_k: int = 10,
+ filter_conditions: Optional[Dict[str, Any]] = None
+ ) -> List[SearchResult]:
+ """向量相似度搜索"""
+ if not query_embedding:
+ return []
+
+ await self.initialize()
+ client = await self._get_client()
+
+ # 构建过滤器
+ query_filter = None
+ if filter_conditions:
+ must_conditions = []
+ for field, value in filter_conditions.items():
+ must_conditions.append(
+ FieldCondition(
+ key=field,
+ match=MatchValue(value=value),
+ )
+ )
+ query_filter = Filter(must=must_conditions)
+
+ try:
+ # 使用 query_points (qdrant-client >= 1.7.0)
+ results = await client.query_points(
+ collection_name=self.collection_name,
+ query=query_embedding,
+ limit=top_k,
+ query_filter=query_filter,
+ with_payload=True,
+ score_threshold=0.0,
+ )
+
+ search_results = []
+ for hit in results.points:
+ payload = hit.payload or {}
+ doc = Document(
+ id=payload.get("doc_id", str(hit.id)),
+ content=payload.get(self.FIELD_CONTENT, ""),
+ metadata=payload.get(self.FIELD_METADATA, {}),
+ )
+ search_results.append(SearchResult(
+ document=doc,
+ score=hit.score,
+ source="vector",
+ ))
+
+ return search_results
+
+ except Exception as e:
+ logger.error(f"搜索失败: {e}")
+ return []
+
+ async def delete_collection(self) -> bool:
+ """删除集合"""
+ try:
+ client = await self._get_client()
+ await client.delete_collection(self.collection_name)
+ self._initialized = False
+ logger.info(f"🗑️ 删除集合: {self.collection_name}")
+ return True
+ except Exception as e:
+ logger.error(f"删除集合失败: {e}")
+ return False
+
+ async def get_stats(self) -> CollectionStats:
+ """获取集合统计"""
+ await self.initialize()
+ client = await self._get_client()
+
+ try:
+ info = await client.get_collection(self.collection_name)
+
+ # 获取所有唯一文件
+ indexed_files: Set[str] = set()
+ scroll_result = await client.scroll(
+ collection_name=self.collection_name,
+ limit=10000,
+ with_payload=[self.FIELD_FILE],
+ )
+
+ for point in scroll_result[0]:
+ if point.payload:
+ file_path = point.payload.get(self.FIELD_FILE)
+ if file_path:
+ indexed_files.add(file_path)
+
+ return CollectionStats(
+ name=self.collection_name,
+ document_count=info.points_count or 0,
+ indexed_files=indexed_files,
+ vector_dimension=self.config.vector_size,
+ )
+ except Exception as e:
+ logger.error(f"获取统计失败: {e}")
+ return CollectionStats(name=self.collection_name, document_count=0)
+
+ async def get_documents_by_file(self, file_path: str) -> List[Document]:
+ """根据文件路径获取文档"""
+ await self.initialize()
+ client = await self._get_client()
+
+ try:
+ scroll_result = await client.scroll(
+ collection_name=self.collection_name,
+ scroll_filter=Filter(
+ must=[
+ FieldCondition(
+ key=self.FIELD_FILE,
+ match=MatchValue(value=file_path),
+ )
+ ]
+ ),
+ limit=1000,
+ with_payload=True,
+ )
+
+ documents = []
+ for point in scroll_result[0]:
+ payload = point.payload or {}
+ doc = Document(
+ id=payload.get("doc_id", str(point.id)),
+ content=payload.get(self.FIELD_CONTENT, ""),
+ metadata=payload.get(self.FIELD_METADATA, {}),
+ )
+ documents.append(doc)
+
+ # 按行号排序
+ documents.sort(key=lambda d: d.metadata.get("start_line", 0))
+ return documents
+
+ except Exception as e:
+ logger.error(f"获取文件文档失败: {e}")
+ return []
+
+ async def get_all_documents(self) -> List[Document]:
+ """获取所有文档 (用于 BM25 索引构建)"""
+ await self.initialize()
+ client = await self._get_client()
+
+ documents = []
+ offset = None
+
+ try:
+ while True:
+ scroll_result = await client.scroll(
+ collection_name=self.collection_name,
+ limit=1000,
+ offset=offset,
+ with_payload=True,
+ )
+
+ points, next_offset = scroll_result
+
+ for point in points:
+ payload = point.payload or {}
+ doc = Document(
+ id=payload.get("doc_id", str(point.id)),
+ content=payload.get(self.FIELD_CONTENT, ""),
+ metadata=payload.get(self.FIELD_METADATA, {}),
+ )
+ documents.append(doc)
+
+ if next_offset is None:
+ break
+ offset = next_offset
+
+ return documents
+
+ except Exception as e:
+ logger.error(f"获取所有文档失败: {e}")
+ return []
+
+
+# ============================================================
+# 工厂
+# ============================================================
+
+class QdrantStoreFactory:
+ """Qdrant 存储工厂"""
+
+ def __init__(self, config: Optional[QdrantConfig] = None):
+ self.config = config or QdrantConfig.from_env()
+
+ def create(self, collection_name: str) -> QdrantVectorStore:
+ """创建存储实例"""
+ return QdrantVectorStore(collection_name, self.config)
+
+ async def get_client(self) -> AsyncQdrantClient:
+ """获取共享的 Qdrant 客户端"""
+ return await get_shared_client(self.config)
+
+
+# 全局工厂实例
+_qdrant_factory: Optional[QdrantStoreFactory] = None
+
+
+def get_qdrant_factory(config: Optional[QdrantConfig] = None) -> QdrantStoreFactory:
+ """获取工厂单例"""
+ global _qdrant_factory
+ if _qdrant_factory is None:
+ _qdrant_factory = QdrantStoreFactory(config)
+ return _qdrant_factory
diff --git a/app/utils/embedding.py b/app/utils/embedding.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bcaedaaf3fdadfa4e0eb877a24a8d2698ffba0c
--- /dev/null
+++ b/app/utils/embedding.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+"""
+Embedding 服务 - 并发优化版
+
+特性:
+1. 并发批量请求 - 使用 asyncio.gather 并行处理多个批次
+2. 信号量控制 - 限制最大并发数,避免 API 限流
+3. 重试机制 - 使用 tenacity 处理临时性错误
+4. 智能分批 - 根据 token 数量动态调整批次大小
+"""
+
+import asyncio
+import logging
+from typing import List, Optional
+from dataclasses import dataclass
+
+from openai import AsyncOpenAI
+
+from app.core.config import settings
+from app.utils.retry import llm_retry, is_retryable_error
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class EmbeddingConfig:
+ """Embedding 服务配置"""
+ # API 配置
+ api_base_url: str = "https://api.siliconflow.cn/v1"
+ model_name: str = "BAAI/bge-m3"
+
+ # 批处理配置
+ batch_size: int = 50 # 每批文本数量
+ max_text_length: int = 8000 # 单个文本最大字符数
+
+ # 并发控制
+ max_concurrent_batches: int = 5 # 最大并发批次数
+
+ # 超时配置
+ timeout: int = 60 # 单次请求超时 (秒)
+
+
+class EmbeddingService:
+ """
+ 高性能 Embedding 服务
+
+ 使用示例:
+ ```python
+ service = EmbeddingService()
+
+ # 单文本
+ embedding = await service.embed_text("Hello world")
+
+ # 批量文本 (自动并发优化)
+ texts = ["text1", "text2", ..., "text100"]
+ embeddings = await service.embed_batch(texts)
+ ```
+ """
+
+ def __init__(self, config: Optional[EmbeddingConfig] = None):
+ self.config = config or EmbeddingConfig()
+
+ # 初始化 OpenAI 客户端 (SiliconFlow 兼容 OpenAI 协议)
+ self._client = AsyncOpenAI(
+ api_key=settings.SILICON_API_KEY,
+ base_url=self.config.api_base_url,
+ timeout=self.config.timeout
+ )
+
+ # 并发信号量
+ self._semaphore = asyncio.Semaphore(self.config.max_concurrent_batches)
+
+ # 统计信息
+ self._stats = {
+ "total_requests": 0,
+ "successful_requests": 0,
+ "failed_requests": 0,
+ "total_texts": 0,
+ "retried_requests": 0
+ }
+
+ def _preprocess_text(self, text: str) -> str:
+ """预处理文本: 移除换行、截断长度"""
+ text = text.replace("\n", " ").strip()
+ if len(text) > self.config.max_text_length:
+ text = text[:self.config.max_text_length]
+ return text
+
+ @llm_retry
+ async def _embed_single_batch(self, texts: List[str]) -> List[List[float]]:
+ """
+ 处理单个批次的 Embedding 请求 (带重试)
+
+ Args:
+ texts: 预处理后的文本列表
+
+ Returns:
+ embedding 向量列表
+ """
+ self._stats["total_requests"] += 1
+
+ response = await self._client.embeddings.create(
+ input=texts,
+ model=self.config.model_name
+ )
+
+ self._stats["successful_requests"] += 1
+ return [item.embedding for item in response.data]
+
+ async def _embed_batch_with_semaphore(
+ self,
+ batch_texts: List[str],
+ batch_index: int
+ ) -> tuple[int, List[List[float]]]:
+ """
+ 带信号量控制的批次处理
+
+ Returns:
+ (batch_index, embeddings) - 返回索引用于结果排序
+ """
+ async with self._semaphore:
+ try:
+ embeddings = await self._embed_single_batch(batch_texts)
+ logger.debug(f"✅ 批次 {batch_index} 完成: {len(batch_texts)} 文本")
+ return (batch_index, embeddings)
+ except Exception as e:
+ self._stats["failed_requests"] += 1
+ logger.error(f"❌ 批次 {batch_index} 失败: {type(e).__name__}: {e}")
+ raise
+
+ async def embed_text(self, text: str) -> List[float]:
+ """
+ 获取单个文本的 Embedding
+
+ Args:
+ text: 输入文本
+
+ Returns:
+ embedding 向量,失败返回空列表
+ """
+ try:
+ processed = self._preprocess_text(text)
+ if not processed:
+ return []
+
+ self._stats["total_texts"] += 1
+ embeddings = await self._embed_single_batch([processed])
+ return embeddings[0] if embeddings else []
+ except Exception as e:
+ logger.error(f"embed_text 失败: {e}")
+ return []
+
+ async def embed_batch(
+ self,
+ texts: List[str],
+ show_progress: bool = False
+ ) -> List[List[float]]:
+ """
+ 批量获取 Embedding (并发优化)
+
+ Args:
+ texts: 文本列表
+ show_progress: 是否显示进度日志
+
+ Returns:
+ embedding 向量列表 (与输入顺序一致)
+ 失败的文本对应空列表
+ """
+ if not texts:
+ return []
+
+ # 预处理所有文本
+ processed_texts = [self._preprocess_text(t) for t in texts]
+ self._stats["total_texts"] += len(texts)
+
+ # 分批
+ batch_size = self.config.batch_size
+ batches = [
+ processed_texts[i:i + batch_size]
+ for i in range(0, len(processed_texts), batch_size)
+ ]
+
+ total_batches = len(batches)
+ if show_progress:
+ logger.info(
+ f"📊 Embedding: {len(texts)} 文本 → {total_batches} 批次 "
+ f"(并发: {self.config.max_concurrent_batches})"
+ )
+
+ # 并发执行所有批次
+ tasks = [
+ self._embed_batch_with_semaphore(batch, idx)
+ for idx, batch in enumerate(batches)
+ ]
+
+ # 收集结果
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+
+ # 按批次索引排序并合并结果
+ embeddings = []
+ for result in sorted(results, key=lambda x: x[0] if isinstance(x, tuple) else float('inf')):
+ if isinstance(result, tuple):
+ batch_idx, batch_embeddings = result
+ embeddings.extend(batch_embeddings)
+ else:
+ # 异常情况: 填充空向量
+ # 找出这个批次有多少文本
+ failed_batch_size = batch_size # 保守估计
+ embeddings.extend([[] for _ in range(failed_batch_size)])
+ logger.warning(f"批次失败,填充 {failed_batch_size} 个空向量")
+
+ # 确保返回数量与输入一致
+ if len(embeddings) < len(texts):
+ embeddings.extend([[] for _ in range(len(texts) - len(embeddings))])
+ elif len(embeddings) > len(texts):
+ embeddings = embeddings[:len(texts)]
+
+ if show_progress:
+ success_count = sum(1 for e in embeddings if e)
+ logger.info(f"✅ Embedding 完成: {success_count}/{len(texts)} 成功")
+
+ return embeddings
+
+ def get_stats(self) -> dict:
+ """获取统计信息"""
+ return self._stats.copy()
+
+ def reset_stats(self):
+ """重置统计信息"""
+ for key in self._stats:
+ self._stats[key] = 0
+
+
+# 全局单例
+_embedding_service: Optional[EmbeddingService] = None
+
+
+def get_embedding_service(config: Optional[EmbeddingConfig] = None) -> EmbeddingService:
+ """获取 Embedding 服务单例"""
+ global _embedding_service
+ if _embedding_service is None:
+ _embedding_service = EmbeddingService(config)
+ return _embedding_service
+
+
+# 便捷函数
+async def embed_text(text: str) -> List[float]:
+ """快捷方式: 获取单个文本的 Embedding"""
+ return await get_embedding_service().embed_text(text)
+
+
+async def embed_batch(texts: List[str], show_progress: bool = False) -> List[List[float]]:
+ """快捷方式: 批量获取 Embedding"""
+ return await get_embedding_service().embed_batch(texts, show_progress)
diff --git a/app/utils/github_client.py b/app/utils/github_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..6dde8d9981e0c1893d3d253d12d66b0ae10c77d2
--- /dev/null
+++ b/app/utils/github_client.py
@@ -0,0 +1,478 @@
+# -*- coding: utf-8 -*-
+"""
+GitHub 异步客户端
+
+设计原则:
+1. 异步非阻塞 - 使用 httpx.AsyncClient
+2. 连接池复用 - 单例模式管理客户端生命周期
+3. 自动重试 - 集成 tenacity 处理瞬时错误
+4. 类型安全 - 完整的类型注解
+5. 可扩展 - 易于添加新的 API 端点
+"""
+
+import asyncio
+import base64
+import logging
+import os
+from dataclasses import dataclass, field
+from typing import List, Optional, Dict, Any, Set
+from contextlib import asynccontextmanager
+
+import httpx
+
+from app.core.config import settings
+from app.utils.retry import llm_retry # 复用已有的重试装饰器
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 数据模型
+# ============================================================
+
+@dataclass
+class GitHubFile:
+ """GitHub 文件信息"""
+ path: str
+ type: str # "blob" | "tree"
+ size: int = 0
+ sha: str = ""
+
+ @property
+ def is_file(self) -> bool:
+ return self.type == "blob"
+
+ @property
+ def is_directory(self) -> bool:
+ return self.type == "tree"
+
+
+@dataclass
+class GitHubRepo:
+ """GitHub 仓库信息"""
+ owner: str
+ name: str
+ default_branch: str = "main"
+ description: str = ""
+ stars: int = 0
+
+ @property
+ def full_name(self) -> str:
+ return f"{self.owner}/{self.name}"
+
+
+@dataclass
+class FileFilter:
+ """文件过滤配置"""
+ ignored_extensions: Set[str] = field(default_factory=lambda: {
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.mp4', '.webp',
+ '.pyc', '.pyo', '.lock', '.zip', '.tar', '.gz', '.pdf', '.woff', '.woff2',
+ '.DS_Store', '.gitignore', '.gitattributes', '.editorconfig'
+ })
+
+ ignored_directories: Set[str] = field(default_factory=lambda: {
+ '.git', '.github', '.vscode', '.idea', '__pycache__',
+ 'node_modules', 'venv', 'env', '.env', 'build', 'dist',
+ 'site-packages', 'migrations', '.next', '.nuxt', 'coverage',
+ 'vendor', 'target', 'out', 'bin', 'obj'
+ })
+
+ max_file_size: int = 500_000 # 500KB
+
+ def should_include(self, file: GitHubFile) -> bool:
+ """判断文件是否应该被包含"""
+ if not file.is_file:
+ return False
+
+ # 检查目录
+ path_parts = file.path.split("/")
+ if any(part in self.ignored_directories for part in path_parts):
+ return False
+
+ # 检查扩展名
+ ext = os.path.splitext(file.path)[1].lower()
+ if ext in self.ignored_extensions:
+ return False
+
+ # 检查文件大小
+ if file.size > self.max_file_size:
+ return False
+
+ return True
+
+
+# ============================================================
+# 异常定义
+# ============================================================
+
+class GitHubError(Exception):
+ """GitHub API 错误基类"""
+ def __init__(self, message: str, status_code: int = 0):
+ self.message = message
+ self.status_code = status_code
+ super().__init__(message)
+
+
+class GitHubAuthError(GitHubError):
+ """认证错误 (401)"""
+ pass
+
+
+class GitHubRateLimitError(GitHubError):
+ """速率限制错误 (403)"""
+ pass
+
+
+class GitHubNotFoundError(GitHubError):
+ """资源不存在 (404)"""
+ pass
+
+
+# ============================================================
+# GitHub 异步客户端
+# ============================================================
+
+class GitHubClient:
+ """
+ GitHub 异步 API 客户端
+
+ 使用示例:
+ ```python
+ async with GitHubClient() as client:
+ repo = await client.get_repo("owner", "repo")
+ files = await client.get_repo_tree(repo)
+ content = await client.get_file_content(repo, "README.md")
+ ```
+ """
+
+ BASE_URL = "https://api.github.com"
+
+ def __init__(
+ self,
+ token: Optional[str] = None,
+ timeout: float = 30.0,
+ max_concurrent_requests: int = 10
+ ):
+ self.token = token or settings.GITHUB_TOKEN
+ self.timeout = timeout
+ self._client: Optional[httpx.AsyncClient] = None
+ self._semaphore = asyncio.Semaphore(max_concurrent_requests)
+
+ @property
+ def _headers(self) -> Dict[str, str]:
+ """构建请求头"""
+ headers = {
+ "Accept": "application/vnd.github.v3+json",
+ "User-Agent": "GitHub-Agent-Demo/1.0"
+ }
+ if self.token:
+ headers["Authorization"] = f"Bearer {self.token}"
+ return headers
+
+ async def _ensure_client(self) -> httpx.AsyncClient:
+ """确保客户端已初始化"""
+ if self._client is None or self._client.is_closed:
+ self._client = httpx.AsyncClient(
+ base_url=self.BASE_URL,
+ headers=self._headers,
+ timeout=httpx.Timeout(self.timeout),
+ follow_redirects=True,
+ limits=httpx.Limits(
+ max_keepalive_connections=20,
+ max_connections=50
+ )
+ )
+ return self._client
+
+ async def close(self):
+ """关闭客户端连接"""
+ if self._client and not self._client.is_closed:
+ await self._client.aclose()
+ self._client = None
+
+ async def __aenter__(self):
+ await self._ensure_client()
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ await self.close()
+
+ def _handle_error(self, response: httpx.Response, context: str = ""):
+ """统一错误处理"""
+ status = response.status_code
+
+ try:
+ data = response.json()
+ message = data.get("message", response.text)
+ except Exception:
+ message = response.text
+
+ error_msg = f"{context}: {message}" if context else message
+
+ if status == 401:
+ raise GitHubAuthError(
+ "GitHub Token 无效或已过期,请检查 .env 配置",
+ status
+ )
+ elif status == 403:
+ if "rate limit" in message.lower():
+ raise GitHubRateLimitError(
+ "GitHub API 请求已达上限,请稍后重试或添加 Token",
+ status
+ )
+ raise GitHubError(error_msg, status)
+ elif status == 404:
+ raise GitHubNotFoundError(error_msg, status)
+ else:
+ raise GitHubError(error_msg, status)
+
+ @llm_retry
+ async def _request(
+ self,
+ method: str,
+ endpoint: str,
+ **kwargs
+ ) -> Dict[str, Any]:
+ """
+ 发送 API 请求 (带重试)
+
+ Args:
+ method: HTTP 方法
+ endpoint: API 端点 (如 /repos/{owner}/{repo})
+ **kwargs: 传递给 httpx 的参数
+
+ Returns:
+ JSON 响应
+ """
+ async with self._semaphore:
+ client = await self._ensure_client()
+ response = await client.request(method, endpoint, **kwargs)
+
+ if response.status_code >= 400:
+ self._handle_error(response, endpoint)
+
+ return response.json()
+
+ async def _request_raw(
+ self,
+ method: str,
+ endpoint: str,
+ **kwargs
+ ) -> httpx.Response:
+ """发送请求并返回原始响应"""
+ async with self._semaphore:
+ client = await self._ensure_client()
+ return await client.request(method, endpoint, **kwargs)
+
+ # --------------------------------------------------------
+ # 仓库相关 API
+ # --------------------------------------------------------
+
+ async def get_repo(self, owner: str, name: str) -> GitHubRepo:
+ """获取仓库信息"""
+ data = await self._request("GET", f"/repos/{owner}/{name}")
+
+ return GitHubRepo(
+ owner=owner,
+ name=name,
+ default_branch=data.get("default_branch", "main"),
+ description=data.get("description", ""),
+ stars=data.get("stargazers_count", 0)
+ )
+
+ async def get_repo_tree(
+ self,
+ repo: GitHubRepo,
+ file_filter: Optional[FileFilter] = None
+ ) -> List[GitHubFile]:
+ """
+ 获取仓库文件树
+
+ Args:
+ repo: 仓库信息
+ file_filter: 文件过滤器 (默认使用标准过滤)
+
+ Returns:
+ 过滤后的文件列表
+ """
+ filter_config = file_filter or FileFilter()
+
+ data = await self._request(
+ "GET",
+ f"/repos/{repo.owner}/{repo.name}/git/trees/{repo.default_branch}",
+ params={"recursive": "1"}
+ )
+
+ files = []
+ for item in data.get("tree", []):
+ file = GitHubFile(
+ path=item["path"],
+ type=item["type"],
+ size=item.get("size", 0),
+ sha=item.get("sha", "")
+ )
+
+ if filter_config.should_include(file):
+ files.append(file)
+
+ logger.info(f"📂 仓库 {repo.full_name}: 共 {len(data.get('tree', []))} 项, 过滤后 {len(files)} 文件")
+ return files
+
+ # --------------------------------------------------------
+ # 文件内容 API
+ # --------------------------------------------------------
+
+ async def get_file_content(
+ self,
+ repo: GitHubRepo,
+ path: str
+ ) -> Optional[str]:
+ """
+ 获取单个文件内容
+
+ Args:
+ repo: 仓库信息
+ path: 文件路径
+
+ Returns:
+ 文件内容 (UTF-8 解码),失败返回 None
+ """
+ try:
+ data = await self._request(
+ "GET",
+ f"/repos/{repo.owner}/{repo.name}/contents/{path}",
+ params={"ref": repo.default_branch}
+ )
+
+ # 处理目录情况
+ if isinstance(data, list):
+ file_names = [f["name"] for f in data]
+ return f"Directory '{path}' contains:\n" + "\n".join(
+ f"- {name}" for name in file_names
+ )
+
+ # 解码文件内容
+ content = data.get("content", "")
+ encoding = data.get("encoding", "base64")
+
+ if encoding == "base64":
+ return base64.b64decode(content).decode("utf-8")
+
+ return content
+
+ except GitHubNotFoundError:
+ logger.warning(f"文件不存在: {path}")
+ return None
+ except UnicodeDecodeError:
+ logger.warning(f"文件无法解码为 UTF-8: {path}")
+ return None
+ except Exception as e:
+ logger.error(f"获取文件失败 {path}: {e}")
+ return None
+
+ async def get_files_content(
+ self,
+ repo: GitHubRepo,
+ paths: List[str],
+ show_progress: bool = False
+ ) -> Dict[str, Optional[str]]:
+ """
+ 批量获取文件内容 (并发优化)
+
+ Args:
+ repo: 仓库信息
+ paths: 文件路径列表
+ show_progress: 是否显示进度
+
+ Returns:
+ {path: content} 字典
+ """
+ if not paths:
+ return {}
+
+ if show_progress:
+ logger.info(f"📥 开始下载 {len(paths)} 个文件 (并发: {self._semaphore._value})")
+
+ # 并发获取所有文件
+ tasks = [
+ self.get_file_content(repo, path)
+ for path in paths
+ ]
+
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+
+ # 组装结果
+ content_map = {}
+ success_count = 0
+
+ for path, result in zip(paths, results):
+ if isinstance(result, Exception):
+ logger.error(f"下载失败 {path}: {result}")
+ content_map[path] = None
+ else:
+ content_map[path] = result
+ if result is not None:
+ success_count += 1
+
+ if show_progress:
+ logger.info(f"✅ 文件下载完成: {success_count}/{len(paths)} 成功")
+
+ return content_map
+
+
+# ============================================================
+# 全局单例管理
+# ============================================================
+
+_github_client: Optional[GitHubClient] = None
+
+
+def get_github_client() -> GitHubClient:
+ """获取 GitHub 客户端单例"""
+ global _github_client
+ if _github_client is None:
+ _github_client = GitHubClient()
+ return _github_client
+
+
+async def close_github_client():
+ """关闭全局客户端 (应用关闭时调用)"""
+ global _github_client
+ if _github_client:
+ await _github_client.close()
+ _github_client = None
+
+
+# ============================================================
+# 便捷函数 (兼容旧接口)
+# ============================================================
+
+def parse_repo_url(url: str) -> Optional[tuple[str, str]]:
+ """
+ 解析 GitHub URL
+
+ Args:
+ url: GitHub 仓库 URL
+
+ Returns:
+ (owner, repo) 元组,无效返回 None
+ """
+ if url.endswith(".git"):
+ url = url[:-4]
+
+ # 支持多种格式
+ # https://github.com/owner/repo
+ # github.com/owner/repo
+ # owner/repo
+
+ parts = url.replace("https://", "").replace("http://", "").split("/")
+
+ if "github.com" in parts:
+ idx = parts.index("github.com")
+ if len(parts) > idx + 2:
+ return (parts[idx + 1], parts[idx + 2])
+ elif len(parts) == 2:
+ # 直接是 owner/repo 格式
+ return (parts[0], parts[1])
+
+ return None
diff --git a/app/utils/llm_client.py b/app/utils/llm_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7e8035de39fc93206953453b05153e6247a0ed6
--- /dev/null
+++ b/app/utils/llm_client.py
@@ -0,0 +1,108 @@
+# 文件路径: app/utils/llm_client.py
+"""
+统一 LLM 客户端入口
+
+支持多个 LLM 供应商,通过 LLM_PROVIDER 环境变量切换:
+- openai: OpenAI (GPT-4, GPT-4o 等)
+- deepseek: DeepSeek (deepseek-chat, deepseek-coder 等)
+- anthropic: Anthropic (Claude 3.5, Claude 3 等)
+- gemini: Google Gemini (gemini-1.5-pro 等)
+
+使用方式 (与原来完全兼容):
+ from app.utils.llm_client import client
+
+ response = await client.chat.completions.create(
+ model=settings.default_model_name,
+ messages=[{"role": "user", "content": "Hello"}],
+ stream=True
+ )
+"""
+
+from app.core.config import settings
+from app.utils.llm_providers import LLMFactory, BaseLLMProvider
+from typing import Optional
+
+# 全局客户端实例
+client: Optional[BaseLLMProvider] = None
+
+def _initialize_client() -> Optional[BaseLLMProvider]:
+ """
+ 初始化 LLM 客户端
+
+ 根据配置的 LLM_PROVIDER 创建对应的客户端实例。
+ """
+ provider = settings.LLM_PROVIDER.lower()
+ api_key = settings.current_api_key
+ base_url = settings.current_base_url
+ model_name = settings.default_model_name
+
+ if not api_key:
+ print(f"❌ 未找到 {provider.upper()}_API_KEY")
+ return None
+
+ try:
+ return LLMFactory.create(
+ provider=provider,
+ api_key=api_key,
+ model_name=model_name,
+ base_url=base_url,
+ temperature=settings.LLM_TEMPERATURE,
+ max_tokens=settings.LLM_MAX_TOKENS,
+ timeout=settings.LLM_TIMEOUT,
+ )
+ except Exception as e:
+ print(f"❌ LLM Client 初始化失败: {e}")
+ return None
+
+
+def get_client() -> Optional[BaseLLMProvider]:
+ """
+ 获取 LLM 客户端实例
+
+ 如果客户端尚未初始化,会自动初始化。
+ """
+ global client
+ if client is None:
+ client = _initialize_client()
+ return client
+
+
+def reinitialize_client(
+ provider: str = None,
+ api_key: str = None,
+ model_name: str = None,
+ base_url: str = None,
+) -> Optional[BaseLLMProvider]:
+ """
+ 重新初始化客户端
+
+ 用于运行时切换 LLM 供应商或模型。
+
+ Args:
+ provider: 新的供应商 (可选)
+ api_key: 新的 API Key (可选)
+ model_name: 新的模型名称 (可选)
+ base_url: 新的 Base URL (可选)
+ """
+ global client
+
+ _provider = provider or settings.LLM_PROVIDER
+ _api_key = api_key or settings.current_api_key
+ _model_name = model_name or settings.default_model_name
+ _base_url = base_url or settings.current_base_url
+
+ try:
+ client = LLMFactory.create(
+ provider=_provider,
+ api_key=_api_key,
+ model_name=_model_name,
+ base_url=_base_url,
+ )
+ return client
+ except Exception as e:
+ print(f"❌ 重新初始化失败: {e}")
+ return None
+
+
+# 自动初始化客户端
+client = _initialize_client()
\ No newline at end of file
diff --git a/app/utils/llm_providers/__init__.py b/app/utils/llm_providers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..18a5736ceaa020eefb735711ca12ec2b675dfedb
--- /dev/null
+++ b/app/utils/llm_providers/__init__.py
@@ -0,0 +1,29 @@
+# 文件路径: app/utils/llm_providers/__init__.py
+"""
+多 LLM 供应商支持模块
+
+支持的供应商:
+- OpenAI (GPT-4, GPT-4o, GPT-3.5-turbo 等)
+- DeepSeek (deepseek-chat, deepseek-coder 等)
+- Anthropic (Claude 3.5, Claude 3 等)
+- Google Gemini (gemini-pro, gemini-1.5-pro 等)
+"""
+
+from .base import BaseLLMProvider, LLMResponse, LLMConfig
+from .openai_provider import OpenAIProvider
+from .deepseek_provider import DeepSeekProvider
+from .anthropic_provider import AnthropicProvider
+from .gemini_provider import GeminiProvider
+from .factory import LLMFactory, get_llm_client
+
+__all__ = [
+ "BaseLLMProvider",
+ "LLMResponse",
+ "LLMConfig",
+ "OpenAIProvider",
+ "DeepSeekProvider",
+ "AnthropicProvider",
+ "GeminiProvider",
+ "LLMFactory",
+ "get_llm_client",
+]
diff --git a/app/utils/llm_providers/anthropic_provider.py b/app/utils/llm_providers/anthropic_provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..d52966310930871612f30a61c3ceaa791dc05304
--- /dev/null
+++ b/app/utils/llm_providers/anthropic_provider.py
@@ -0,0 +1,196 @@
+# 文件路径: app/utils/llm_providers/anthropic_provider.py
+"""
+Anthropic (Claude) LLM 提供商实现
+
+支持模型: claude-3-5-sonnet, claude-3-opus, claude-3-haiku 等
+"""
+
+import uuid
+import time
+from typing import List, AsyncIterator
+
+from .base import (
+ BaseLLMProvider,
+ LLMConfig,
+ LLMMessage,
+ LLMResponse,
+ LLMChoice,
+ LLMUsage,
+ LLMProviderType
+)
+
+
+class AnthropicProvider(BaseLLMProvider):
+ """
+ Anthropic (Claude) API 提供商
+
+ 注意: Anthropic 的消息格式与 OpenAI 略有不同:
+ - system 消息需要单独传递
+ - messages 只包含 user/assistant 轮次
+ """
+
+ def __init__(self, config: LLMConfig):
+ super().__init__(config)
+ try:
+ from anthropic import AsyncAnthropic
+ self._client = AsyncAnthropic(
+ api_key=config.api_key,
+ timeout=config.timeout
+ )
+ self._available = True
+ except ImportError:
+ print("⚠️ anthropic 包未安装,请运行: pip install anthropic")
+ self._client = None
+ self._available = False
+
+ def _extract_system_message(self, messages: List[LLMMessage]) -> tuple:
+ """
+ 提取 system 消息
+
+ Anthropic 需要将 system 消息单独传递,
+ 不能放在 messages 列表中。
+
+ Returns:
+ (system_prompt, filtered_messages)
+ """
+ system_prompt = None
+ filtered_messages = []
+
+ for msg in messages:
+ if msg.role == "system":
+ system_prompt = msg.content
+ else:
+ filtered_messages.append(msg)
+
+ return system_prompt, filtered_messages
+
+ async def chat_completions_create(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """非流式请求"""
+ if not self._available:
+ raise RuntimeError("Anthropic client not available. Please install: pip install anthropic")
+
+ system_prompt, filtered_messages = self._extract_system_message(messages)
+
+ # 转换消息格式
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in filtered_messages
+ ]
+
+ # 构建请求参数
+ request_params = {
+ "model": model,
+ "messages": api_messages,
+ "temperature": temperature,
+ "max_tokens": max_tokens,
+ }
+
+ if system_prompt:
+ request_params["system"] = system_prompt
+
+ response = await self._client.messages.create(**request_params)
+
+ # 转换为统一格式
+ content = ""
+ if response.content:
+ # Anthropic 的 content 是一个 list
+ for block in response.content:
+ if hasattr(block, 'text'):
+ content += block.text
+
+ choices = [
+ LLMChoice(
+ index=0,
+ message=LLMMessage(role="assistant", content=content),
+ finish_reason=response.stop_reason
+ )
+ ]
+
+ usage = LLMUsage(
+ prompt_tokens=response.usage.input_tokens,
+ completion_tokens=response.usage.output_tokens,
+ total_tokens=response.usage.input_tokens + response.usage.output_tokens
+ )
+
+ return LLMResponse(
+ id=response.id,
+ model=response.model,
+ choices=choices,
+ usage=usage,
+ created=int(time.time())
+ )
+
+ async def chat_completions_create_stream(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """流式请求"""
+ if not self._available:
+ raise RuntimeError("Anthropic client not available. Please install: pip install anthropic")
+
+ system_prompt, filtered_messages = self._extract_system_message(messages)
+
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in filtered_messages
+ ]
+
+ request_params = {
+ "model": model,
+ "messages": api_messages,
+ "temperature": temperature,
+ "max_tokens": max_tokens,
+ }
+
+ if system_prompt:
+ request_params["system"] = system_prompt
+
+ response_id = f"msg_{uuid.uuid4().hex[:24]}"
+
+ async with self._client.messages.stream(**request_params) as stream:
+ async for text in stream.text_stream:
+ choices = [
+ LLMChoice(
+ index=0,
+ delta=LLMMessage(role="assistant", content=text),
+ finish_reason=None
+ )
+ ]
+ yield LLMResponse(
+ id=response_id,
+ model=model,
+ choices=choices,
+ created=int(time.time())
+ )
+
+ def validate_connection(self) -> bool:
+ """验证连接"""
+ return self._available and bool(self.config.api_key)
+
+
+def create_anthropic_provider(
+ api_key: str,
+ model_name: str = "claude-3-5-sonnet-20241022",
+ **kwargs
+) -> AnthropicProvider:
+ """工厂函数:创建 Anthropic 提供商"""
+ config = LLMConfig(
+ provider=LLMProviderType.ANTHROPIC,
+ api_key=api_key,
+ model_name=model_name,
+ **kwargs
+ )
+ return AnthropicProvider(config)
diff --git a/app/utils/llm_providers/base.py b/app/utils/llm_providers/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..b36141957b8920a696cb14a01743429d25d4c7df
--- /dev/null
+++ b/app/utils/llm_providers/base.py
@@ -0,0 +1,320 @@
+# 文件路径: app/utils/llm_providers/base.py
+"""
+LLM 提供商基类定义
+
+定义统一的接口规范,所有供应商实现都必须遵循此规范。
+采用适配器模式,将不同供应商的 API 统一为 OpenAI 兼容格式。
+"""
+
+import logging
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import List, Dict, Any, Optional, AsyncIterator, Union
+from enum import Enum
+
+from app.utils.retry import llm_retry, is_retryable_error
+
+# 配置日志
+logger = logging.getLogger("llm_provider")
+
+
+class LLMProviderType(str, Enum):
+ """支持的 LLM 供应商类型"""
+ OPENAI = "openai"
+ DEEPSEEK = "deepseek"
+ ANTHROPIC = "anthropic"
+ GEMINI = "gemini"
+
+
+@dataclass
+class LLMConfig:
+ """LLM 配置"""
+ provider: LLMProviderType
+ api_key: str
+ model_name: str
+ base_url: Optional[str] = None
+ temperature: float = 0.1
+ max_tokens: int = 4096
+ timeout: int = 600
+ extra_params: Dict[str, Any] = field(default_factory=dict)
+
+
+@dataclass
+class LLMMessage:
+ """消息格式 (兼容 OpenAI)"""
+ role: str # "system", "user", "assistant"
+ content: str
+
+
+@dataclass
+class LLMUsage:
+ """Token 使用量"""
+ prompt_tokens: int = 0
+ completion_tokens: int = 0
+ total_tokens: int = 0
+
+
+@dataclass
+class LLMChoice:
+ """响应选项 (兼容 OpenAI)"""
+ index: int
+ message: Optional[LLMMessage] = None
+ delta: Optional[LLMMessage] = None # 流式响应时使用
+ finish_reason: Optional[str] = None
+
+
+@dataclass
+class LLMResponse:
+ """
+ 统一的 LLM 响应格式
+
+ 设计为兼容 OpenAI 的 ChatCompletion 格式,
+ 使得现有代码无需大幅修改即可使用。
+ """
+ id: str
+ model: str
+ choices: List[LLMChoice]
+ usage: Optional[LLMUsage] = None
+ created: int = 0
+
+ @property
+ def content(self) -> str:
+ """便捷方法:获取第一个选项的内容"""
+ if self.choices and self.choices[0].message:
+ return self.choices[0].message.content
+ return ""
+
+
+# 辅助类定义(在 BaseLLMProvider 外部,避免嵌套类问题)
+class _CompletionsNamespace:
+ """模拟 client.chat.completions 命名空间"""
+ def __init__(self, provider: 'BaseLLMProvider'):
+ self._provider = provider
+
+ async def create(
+ self,
+ model: str = None,
+ messages: List[Dict[str, str]] = None,
+ temperature: float = None,
+ max_tokens: int = None,
+ stream: bool = False,
+ timeout: int = None,
+ **kwargs
+ ) -> Union[LLMResponse, AsyncIterator[LLMResponse]]:
+ """
+ 统一的 completions.create 接口
+
+ 兼容 OpenAI SDK 调用方式:
+ response = await client.chat.completions.create(
+ model="gpt-4",
+ messages=[{"role": "user", "content": "Hello"}],
+ stream=True
+ )
+
+ 内置重试机制:
+ - 自动重试网络错误、超时、速率限制
+ - 指数退避策略
+ - 最多重试 3 次
+ """
+ # 合并配置
+ _model = model or self._provider.config.model_name
+ _temperature = temperature if temperature is not None else self._provider.config.temperature
+ _max_tokens = max_tokens or self._provider.config.max_tokens
+ _timeout = timeout or self._provider.config.timeout
+
+ # 转换消息格式
+ _messages = [
+ LLMMessage(role=m["role"], content=m["content"])
+ for m in (messages or [])
+ ]
+
+ if stream:
+ # 流式请求: 返回带重试的异步生成器
+ return self._create_stream_with_retry(
+ messages=_messages,
+ model=_model,
+ temperature=_temperature,
+ max_tokens=_max_tokens,
+ timeout=_timeout,
+ **kwargs
+ )
+ else:
+ # 非流式请求: 使用 tenacity 重试
+ return await self._create_with_retry(
+ messages=_messages,
+ model=_model,
+ temperature=_temperature,
+ max_tokens=_max_tokens,
+ timeout=_timeout,
+ **kwargs
+ )
+
+ @llm_retry
+ async def _create_with_retry(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """带重试的非流式请求"""
+ logger.debug(f"🔄 LLM 请求: model={model}, messages_count={len(messages)}")
+ return await self._provider.chat_completions_create(
+ messages=messages,
+ model=model,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ **kwargs
+ )
+
+ async def _create_stream_with_retry(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ max_retries: int = 3,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """
+ 带重试的流式请求
+
+ 注意: 流式请求的重试策略与非流式不同
+ - 如果在获取流之前失败,可以重试
+ - 如果在流传输过程中失败,需要重新开始
+ """
+ last_error = None
+
+ for attempt in range(1, max_retries + 1):
+ try:
+ logger.debug(f"🔄 LLM 流式请求 (尝试 {attempt}/{max_retries}): model={model}")
+
+ # 获取流生成器
+ stream = self._provider.chat_completions_create_stream(
+ messages=messages,
+ model=model,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ **kwargs
+ )
+
+ # 迭代流并 yield
+ async for chunk in stream:
+ yield chunk
+
+ # 成功完成,退出重试循环
+ return
+
+ except Exception as e:
+ last_error = e
+ if is_retryable_error(e) and attempt < max_retries:
+ wait_time = min(2 ** attempt, 30) # 指数退避
+ logger.warning(
+ f"🔄 LLM 流式请求失败 (尝试 {attempt}/{max_retries}): "
+ f"{type(e).__name__}: {e}. 等待 {wait_time}s 后重试..."
+ )
+ import asyncio
+ await asyncio.sleep(wait_time)
+ else:
+ # 不可重试的错误或已达到最大重试次数
+ logger.error(f"❌ LLM 流式请求最终失败: {type(e).__name__}: {e}")
+ raise
+
+ # 如果走到这里,说明所有重试都失败了
+ if last_error:
+ raise last_error
+
+
+class _ChatNamespace:
+ """模拟 client.chat 命名空间"""
+ def __init__(self, provider: 'BaseLLMProvider'):
+ self._provider = provider
+ self.completions = _CompletionsNamespace(provider)
+
+
+class BaseLLMProvider(ABC):
+ """
+ LLM 提供商抽象基类
+
+ 所有供应商实现都需要继承此类并实现以下方法:
+ - chat_completions_create: 非流式请求
+ - chat_completions_create_stream: 流式请求
+
+ 为了兼容现有代码,提供一个模拟 OpenAI 客户端的 chat.completions 接口。
+ """
+
+ def __init__(self, config: LLMConfig):
+ self.config = config
+ self._client = None
+ # 模拟 OpenAI SDK 的接口结构
+ self.chat = _ChatNamespace(self)
+
+ @abstractmethod
+ async def chat_completions_create(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """
+ 非流式 Chat Completion 请求
+
+ Args:
+ messages: 消息列表
+ model: 模型名称
+ temperature: 温度参数
+ max_tokens: 最大 Token 数
+ timeout: 超时时间
+
+ Returns:
+ LLMResponse: 统一格式的响应
+ """
+ pass
+
+ @abstractmethod
+ async def chat_completions_create_stream(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """
+ 流式 Chat Completion 请求
+
+ Args:
+ messages: 消息列表
+ model: 模型名称
+ temperature: 温度参数
+ max_tokens: 最大 Token 数
+ timeout: 超时时间
+
+ Yields:
+ LLMResponse: 流式响应块
+ """
+ pass
+
+ @abstractmethod
+ def validate_connection(self) -> bool:
+ """验证连接是否正常"""
+ pass
+
+ @property
+ def provider_name(self) -> str:
+ """获取供应商名称"""
+ return self.config.provider.value
+
+ @property
+ def model_name(self) -> str:
+ """获取当前模型名称"""
+ return self.config.model_name
diff --git a/app/utils/llm_providers/deepseek_provider.py b/app/utils/llm_providers/deepseek_provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a49ea5fe0cde0a3e3e8b1cf6aa8e962d287ae65
--- /dev/null
+++ b/app/utils/llm_providers/deepseek_provider.py
@@ -0,0 +1,154 @@
+# 文件路径: app/utils/llm_providers/deepseek_provider.py
+"""
+DeepSeek LLM 提供商实现
+
+DeepSeek API 兼容 OpenAI 协议,因此直接复用 OpenAI SDK。
+支持模型: deepseek-chat, deepseek-coder, deepseek-reasoner 等
+"""
+
+from typing import List, AsyncIterator
+from openai import AsyncOpenAI
+
+from .base import (
+ BaseLLMProvider,
+ LLMConfig,
+ LLMMessage,
+ LLMResponse,
+ LLMChoice,
+ LLMUsage,
+ LLMProviderType
+)
+
+
+# DeepSeek 默认 API 端点
+DEEPSEEK_DEFAULT_BASE_URL = "https://api.deepseek.com"
+
+
+class DeepSeekProvider(BaseLLMProvider):
+ """
+ DeepSeek API 提供商
+
+ DeepSeek 使用 OpenAI 兼容协议,因此可以直接使用 OpenAI SDK。
+ """
+
+ def __init__(self, config: LLMConfig):
+ super().__init__(config)
+ # 确保使用正确的 base_url
+ base_url = config.base_url or DEEPSEEK_DEFAULT_BASE_URL
+ self._client = AsyncOpenAI(
+ api_key=config.api_key,
+ base_url=base_url,
+ timeout=config.timeout
+ )
+
+ async def chat_completions_create(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """非流式请求"""
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ response = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ **kwargs
+ )
+
+ choices = [
+ LLMChoice(
+ index=c.index,
+ message=LLMMessage(role=c.message.role, content=c.message.content),
+ finish_reason=c.finish_reason
+ )
+ for c in response.choices
+ ]
+
+ usage = None
+ if response.usage:
+ usage = LLMUsage(
+ prompt_tokens=response.usage.prompt_tokens,
+ completion_tokens=response.usage.completion_tokens,
+ total_tokens=response.usage.total_tokens
+ )
+
+ return LLMResponse(
+ id=response.id,
+ model=response.model,
+ choices=choices,
+ usage=usage,
+ created=response.created
+ )
+
+ async def chat_completions_create_stream(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """流式请求"""
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ stream = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ stream=True,
+ **kwargs
+ )
+
+ async for chunk in stream:
+ if chunk.choices:
+ delta_content = chunk.choices[0].delta.content or ""
+ choices = [
+ LLMChoice(
+ index=0,
+ delta=LLMMessage(role="assistant", content=delta_content),
+ finish_reason=chunk.choices[0].finish_reason
+ )
+ ]
+ yield LLMResponse(
+ id=chunk.id,
+ model=chunk.model,
+ choices=choices,
+ created=chunk.created
+ )
+
+ def validate_connection(self) -> bool:
+ """验证 API Key 有效性"""
+ return bool(self.config.api_key)
+
+
+def create_deepseek_provider(
+ api_key: str,
+ model_name: str = "deepseek-chat",
+ base_url: str = None,
+ **kwargs
+) -> DeepSeekProvider:
+ """工厂函数:创建 DeepSeek 提供商"""
+ config = LLMConfig(
+ provider=LLMProviderType.DEEPSEEK,
+ api_key=api_key,
+ model_name=model_name,
+ base_url=base_url or DEEPSEEK_DEFAULT_BASE_URL,
+ **kwargs
+ )
+ return DeepSeekProvider(config)
diff --git a/app/utils/llm_providers/factory.py b/app/utils/llm_providers/factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..fca924b65a486ef89bacecf3072dcfc1df3a1764
--- /dev/null
+++ b/app/utils/llm_providers/factory.py
@@ -0,0 +1,171 @@
+# 文件路径: app/utils/llm_providers/factory.py
+"""
+LLM 工厂模块
+
+提供统一的 LLM 客户端创建接口,根据配置自动选择合适的供应商。
+"""
+
+import os
+from typing import Optional
+
+from .base import BaseLLMProvider, LLMConfig, LLMProviderType
+from .openai_provider import OpenAIProvider
+from .deepseek_provider import DeepSeekProvider, DEEPSEEK_DEFAULT_BASE_URL
+from .anthropic_provider import AnthropicProvider
+from .gemini_provider import GeminiProvider
+
+
+class LLMFactory:
+ """
+ LLM 客户端工厂
+
+ 根据提供商类型创建对应的客户端实例。
+ 支持从环境变量自动配置。
+ """
+
+ # 提供商类到枚举的映射
+ _providers = {
+ LLMProviderType.OPENAI: OpenAIProvider,
+ LLMProviderType.DEEPSEEK: DeepSeekProvider,
+ LLMProviderType.ANTHROPIC: AnthropicProvider,
+ LLMProviderType.GEMINI: GeminiProvider,
+ }
+
+ # 默认模型名称映射
+ _default_models = {
+ LLMProviderType.OPENAI: "gpt-4o-mini",
+ LLMProviderType.DEEPSEEK: "deepseek-chat",
+ LLMProviderType.ANTHROPIC: "claude-3-5-sonnet-20241022",
+ LLMProviderType.GEMINI: "gemini-1.5-flash",
+ }
+
+ # 默认 Base URL 映射
+ _default_base_urls = {
+ LLMProviderType.OPENAI: None, # 使用 SDK 默认
+ LLMProviderType.DEEPSEEK: DEEPSEEK_DEFAULT_BASE_URL,
+ LLMProviderType.ANTHROPIC: None,
+ LLMProviderType.GEMINI: None,
+ }
+
+ @classmethod
+ def create(
+ cls,
+ provider: str,
+ api_key: str,
+ model_name: str = None,
+ base_url: str = None,
+ **kwargs
+ ) -> Optional[BaseLLMProvider]:
+ """
+ 创建 LLM 客户端
+
+ Args:
+ provider: 提供商名称 ("openai", "deepseek", "anthropic", "gemini")
+ api_key: API Key
+ model_name: 模型名称 (可选,使用默认值)
+ base_url: 自定义 API 端点 (可选)
+ **kwargs: 其他配置参数
+
+ Returns:
+ BaseLLMProvider 实例,或 None (如果创建失败)
+ """
+ try:
+ # 解析提供商类型
+ provider_type = LLMProviderType(provider.lower())
+ except ValueError:
+ print(f"❌ 不支持的 LLM 提供商: {provider}")
+ print(f" 支持的提供商: {', '.join([p.value for p in LLMProviderType])}")
+ return None
+
+ if not api_key:
+ print(f"❌ 未提供 {provider} 的 API Key")
+ return None
+
+ # 获取提供商类
+ provider_class = cls._providers.get(provider_type)
+ if not provider_class:
+ print(f"❌ 提供商 {provider} 未实现")
+ return None
+
+ # 构建配置
+ config = LLMConfig(
+ provider=provider_type,
+ api_key=api_key,
+ model_name=model_name or cls._default_models.get(provider_type, "default"),
+ base_url=base_url or cls._default_base_urls.get(provider_type),
+ **kwargs
+ )
+
+ try:
+ client = provider_class(config)
+ if client.validate_connection():
+ print(f"✅ {provider.upper()} Client 初始化成功 (Model: {config.model_name})")
+ return client
+ else:
+ print(f"❌ {provider.upper()} Client 验证失败")
+ return None
+ except Exception as e:
+ print(f"❌ {provider.upper()} Client 初始化失败: {e}")
+ return None
+
+ @classmethod
+ def create_from_env(cls, provider: str = None) -> Optional[BaseLLMProvider]:
+ """
+ 从环境变量创建 LLM 客户端
+
+ 环境变量命名规范:
+ - LLM_PROVIDER: 提供商名称 (可被参数覆盖)
+ - {PROVIDER}_API_KEY: API Key (如 OPENAI_API_KEY, DEEPSEEK_API_KEY)
+ - {PROVIDER}_BASE_URL: 自定义端点 (可选)
+ - MODEL_NAME: 模型名称 (可选)
+
+ Args:
+ provider: 提供商名称 (可选,默认从 LLM_PROVIDER 环境变量读取)
+
+ Returns:
+ BaseLLMProvider 实例
+ """
+ # 确定提供商
+ _provider = provider or os.getenv("LLM_PROVIDER", "deepseek")
+ _provider = _provider.lower()
+
+ # 获取 API Key (支持多种命名方式)
+ key_env_names = [
+ f"{_provider.upper()}_API_KEY",
+ f"{_provider.upper()}API_KEY",
+ ]
+
+ api_key = None
+ for key_name in key_env_names:
+ api_key = os.getenv(key_name)
+ if api_key:
+ break
+
+ if not api_key:
+ print(f"❌ 未找到 {_provider.upper()} API Key")
+ print(f" 请设置环境变量: {key_env_names[0]}")
+ return None
+
+ # 获取可选配置
+ base_url = os.getenv(f"{_provider.upper()}_BASE_URL")
+ model_name = os.getenv("MODEL_NAME")
+
+ return cls.create(
+ provider=_provider,
+ api_key=api_key,
+ model_name=model_name,
+ base_url=base_url
+ )
+
+
+def get_llm_client(provider: str = None) -> Optional[BaseLLMProvider]:
+ """
+ 便捷函数:获取 LLM 客户端
+
+ Args:
+ provider: 提供商名称 (可选)
+
+ Returns:
+ BaseLLMProvider 实例
+ """
+ return LLMFactory.create_from_env(provider)
diff --git a/app/utils/llm_providers/gemini_provider.py b/app/utils/llm_providers/gemini_provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d9e82fd2f5459ba676c61576f23d1a7d82d68f3
--- /dev/null
+++ b/app/utils/llm_providers/gemini_provider.py
@@ -0,0 +1,301 @@
+# 文件路径: app/utils/llm_providers/gemini_provider.py
+"""
+Google Gemini LLM 提供商实现
+
+支持模型: gemini-1.5-pro, gemini-1.5-flash, gemini-pro 等
+"""
+
+import uuid
+import time
+from typing import List, AsyncIterator
+
+from .base import (
+ BaseLLMProvider,
+ LLMConfig,
+ LLMMessage,
+ LLMResponse,
+ LLMChoice,
+ LLMUsage,
+ LLMProviderType
+)
+
+
+class GeminiProvider(BaseLLMProvider):
+ """
+ Google Gemini API 提供商
+
+ 支持两种方式:
+ 1. 使用 google-generativeai SDK (原生)
+ 2. 使用 OpenAI 兼容接口 (通过 AI Studio 或 Vertex AI)
+ """
+
+ def __init__(self, config: LLMConfig):
+ super().__init__(config)
+ self._available = False
+ self._use_openai_compat = config.base_url is not None
+
+ if self._use_openai_compat:
+ # 使用 OpenAI 兼容模式 (推荐)
+ try:
+ from openai import AsyncOpenAI
+ self._client = AsyncOpenAI(
+ api_key=config.api_key,
+ base_url=config.base_url,
+ timeout=config.timeout
+ )
+ self._available = True
+ print(f"✅ Gemini Provider (OpenAI Compatible) initialized")
+ except ImportError:
+ print("⚠️ openai 包未安装")
+ else:
+ # 使用 Google AI SDK (原生模式)
+ try:
+ import google.generativeai as genai
+ genai.configure(api_key=config.api_key)
+ self._genai = genai
+ self._model = genai.GenerativeModel(config.model_name)
+ self._available = True
+ print(f"✅ Gemini Provider (Native SDK) initialized")
+ except ImportError:
+ print("⚠️ google-generativeai 包未安装,请运行: pip install google-generativeai")
+ self._genai = None
+ self._model = None
+
+ def _convert_messages_to_gemini(self, messages: List[LLMMessage]) -> tuple:
+ """
+ 转换消息格式为 Gemini 格式
+
+ Gemini 的消息格式:
+ - 不支持 system 角色,需要将其合并到第一条 user 消息
+ - role: "user" | "model" (不是 "assistant")
+
+ Returns:
+ (history, current_message)
+ """
+ system_content = ""
+ converted = []
+
+ for msg in messages:
+ if msg.role == "system":
+ system_content = msg.content + "\n\n"
+ elif msg.role == "assistant":
+ converted.append({"role": "model", "parts": [msg.content]})
+ else: # user
+ content = msg.content
+ if system_content and len(converted) == 0:
+ content = system_content + content
+ system_content = ""
+ converted.append({"role": "user", "parts": [content]})
+
+ if not converted:
+ return [], ""
+
+ # 最后一条作为当前消息
+ if len(converted) == 1:
+ return [], converted[0]["parts"][0]
+ else:
+ return converted[:-1], converted[-1]["parts"][0]
+
+ async def chat_completions_create(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """非流式请求"""
+ if not self._available:
+ raise RuntimeError("Gemini client not available")
+
+ if self._use_openai_compat:
+ # OpenAI 兼容模式
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ response = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ **kwargs
+ )
+
+ choices = [
+ LLMChoice(
+ index=c.index,
+ message=LLMMessage(role=c.message.role, content=c.message.content),
+ finish_reason=c.finish_reason
+ )
+ for c in response.choices
+ ]
+
+ usage = None
+ if response.usage:
+ usage = LLMUsage(
+ prompt_tokens=response.usage.prompt_tokens,
+ completion_tokens=response.usage.completion_tokens,
+ total_tokens=response.usage.total_tokens
+ )
+
+ return LLMResponse(
+ id=response.id,
+ model=response.model,
+ choices=choices,
+ usage=usage,
+ created=response.created
+ )
+ else:
+ # Native SDK 模式
+ history, current_msg = self._convert_messages_to_gemini(messages)
+
+ generation_config = {
+ "temperature": temperature,
+ "max_output_tokens": max_tokens,
+ }
+
+ chat = self._model.start_chat(history=history)
+ response = await chat.send_message_async(
+ current_msg,
+ generation_config=generation_config
+ )
+
+ content = response.text if response.text else ""
+
+ choices = [
+ LLMChoice(
+ index=0,
+ message=LLMMessage(role="assistant", content=content),
+ finish_reason="stop"
+ )
+ ]
+
+ # Gemini 原生 SDK 的 token 统计
+ usage = None
+ if hasattr(response, 'usage_metadata') and response.usage_metadata:
+ usage = LLMUsage(
+ prompt_tokens=getattr(response.usage_metadata, 'prompt_token_count', 0),
+ completion_tokens=getattr(response.usage_metadata, 'candidates_token_count', 0),
+ total_tokens=getattr(response.usage_metadata, 'total_token_count', 0)
+ )
+
+ return LLMResponse(
+ id=f"gemini-{uuid.uuid4().hex[:12]}",
+ model=model,
+ choices=choices,
+ usage=usage,
+ created=int(time.time())
+ )
+
+ async def chat_completions_create_stream(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """流式请求"""
+ if not self._available:
+ raise RuntimeError("Gemini client not available")
+
+ if self._use_openai_compat:
+ # OpenAI 兼容模式
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ stream = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ stream=True,
+ **kwargs
+ )
+
+ async for chunk in stream:
+ if chunk.choices:
+ delta_content = chunk.choices[0].delta.content or ""
+ choices = [
+ LLMChoice(
+ index=0,
+ delta=LLMMessage(role="assistant", content=delta_content),
+ finish_reason=chunk.choices[0].finish_reason
+ )
+ ]
+ yield LLMResponse(
+ id=chunk.id,
+ model=chunk.model,
+ choices=choices,
+ created=chunk.created
+ )
+ else:
+ # Native SDK 流式
+ history, current_msg = self._convert_messages_to_gemini(messages)
+
+ generation_config = {
+ "temperature": temperature,
+ "max_output_tokens": max_tokens,
+ }
+
+ chat = self._model.start_chat(history=history)
+ response = await chat.send_message_async(
+ current_msg,
+ generation_config=generation_config,
+ stream=True
+ )
+
+ response_id = f"gemini-{uuid.uuid4().hex[:12]}"
+
+ async for chunk in response:
+ if chunk.text:
+ choices = [
+ LLMChoice(
+ index=0,
+ delta=LLMMessage(role="assistant", content=chunk.text),
+ finish_reason=None
+ )
+ ]
+ yield LLMResponse(
+ id=response_id,
+ model=model,
+ choices=choices,
+ created=int(time.time())
+ )
+
+ def validate_connection(self) -> bool:
+ """验证连接"""
+ return self._available and bool(self.config.api_key)
+
+
+def create_gemini_provider(
+ api_key: str,
+ model_name: str = "gemini-1.5-flash",
+ base_url: str = None,
+ **kwargs
+) -> GeminiProvider:
+ """
+ 工厂函数:创建 Gemini 提供商
+
+ Args:
+ api_key: Google AI API Key
+ model_name: 模型名称
+ base_url: OpenAI 兼容端点 (可选)
+ 如果不提供,则使用原生 SDK
+ """
+ config = LLMConfig(
+ provider=LLMProviderType.GEMINI,
+ api_key=api_key,
+ model_name=model_name,
+ base_url=base_url,
+ **kwargs
+ )
+ return GeminiProvider(config)
diff --git a/app/utils/llm_providers/openai_provider.py b/app/utils/llm_providers/openai_provider.py
new file mode 100644
index 0000000000000000000000000000000000000000..981fb92326cfda9f3c92725f06183c7022f6236c
--- /dev/null
+++ b/app/utils/llm_providers/openai_provider.py
@@ -0,0 +1,145 @@
+# 文件路径: app/utils/llm_providers/openai_provider.py
+"""
+OpenAI LLM 提供商实现
+
+支持模型: GPT-4, GPT-4o, GPT-4o-mini, GPT-3.5-turbo 等
+"""
+
+from typing import List, AsyncIterator
+from openai import AsyncOpenAI
+
+from .base import (
+ BaseLLMProvider,
+ LLMConfig,
+ LLMMessage,
+ LLMResponse,
+ LLMChoice,
+ LLMUsage,
+ LLMProviderType
+)
+
+
+class OpenAIProvider(BaseLLMProvider):
+ """OpenAI API 提供商"""
+
+ def __init__(self, config: LLMConfig):
+ super().__init__(config)
+ self._client = AsyncOpenAI(
+ api_key=config.api_key,
+ base_url=config.base_url, # 可选自定义 base_url
+ timeout=config.timeout
+ )
+
+ async def chat_completions_create(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> LLMResponse:
+ """非流式请求"""
+ # 转换消息格式
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ response = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ **kwargs
+ )
+
+ # 转换为统一格式
+ choices = [
+ LLMChoice(
+ index=c.index,
+ message=LLMMessage(role=c.message.role, content=c.message.content),
+ finish_reason=c.finish_reason
+ )
+ for c in response.choices
+ ]
+
+ usage = None
+ if response.usage:
+ usage = LLMUsage(
+ prompt_tokens=response.usage.prompt_tokens,
+ completion_tokens=response.usage.completion_tokens,
+ total_tokens=response.usage.total_tokens
+ )
+
+ return LLMResponse(
+ id=response.id,
+ model=response.model,
+ choices=choices,
+ usage=usage,
+ created=response.created
+ )
+
+ async def chat_completions_create_stream(
+ self,
+ messages: List[LLMMessage],
+ model: str,
+ temperature: float,
+ max_tokens: int,
+ timeout: int,
+ **kwargs
+ ) -> AsyncIterator[LLMResponse]:
+ """流式请求"""
+ api_messages = [
+ {"role": m.role, "content": m.content}
+ for m in messages
+ ]
+
+ stream = await self._client.chat.completions.create(
+ model=model,
+ messages=api_messages,
+ temperature=temperature,
+ max_tokens=max_tokens,
+ timeout=timeout,
+ stream=True,
+ **kwargs
+ )
+
+ async for chunk in stream:
+ if chunk.choices:
+ delta_content = chunk.choices[0].delta.content or ""
+ choices = [
+ LLMChoice(
+ index=0,
+ delta=LLMMessage(role="assistant", content=delta_content),
+ finish_reason=chunk.choices[0].finish_reason
+ )
+ ]
+ yield LLMResponse(
+ id=chunk.id,
+ model=chunk.model,
+ choices=choices,
+ created=chunk.created
+ )
+
+ def validate_connection(self) -> bool:
+ """验证 API Key 有效性"""
+ return bool(self.config.api_key)
+
+
+def create_openai_provider(
+ api_key: str,
+ model_name: str = "gpt-4o-mini",
+ base_url: str = None,
+ **kwargs
+) -> OpenAIProvider:
+ """工厂函数:创建 OpenAI 提供商"""
+ config = LLMConfig(
+ provider=LLMProviderType.OPENAI,
+ api_key=api_key,
+ model_name=model_name,
+ base_url=base_url,
+ **kwargs
+ )
+ return OpenAIProvider(config)
diff --git a/app/utils/repo_lock.py b/app/utils/repo_lock.py
new file mode 100644
index 0000000000000000000000000000000000000000..fcee19f1848524f8d2e05720d1e26ef87cb98a56
--- /dev/null
+++ b/app/utils/repo_lock.py
@@ -0,0 +1,390 @@
+# -*- coding: utf-8 -*-
+"""
+仓库级分布式锁
+
+解决问题:
+1. 同一仓库的并发写入竞争 (两人同时输入同一 URL)
+2. 重新分析时的数据一致性 (用户 A 重分析,用户 B 同时查询)
+
+设计原则:
+- 单进程: asyncio.Lock (内存锁)
+- 多进程: 文件锁 (fcntl/msvcrt)
+- 多节点: 可选 Redis 分布式锁 (生产环境)
+
+使用示例:
+```python
+async with RepoLock.acquire(session_id):
+ # 独占访问该仓库的写操作
+ await vector_store.reset()
+ await vector_store.add_documents(docs)
+```
+"""
+
+import asyncio
+import logging
+import os
+import time
+from abc import ABC, abstractmethod
+from contextlib import asynccontextmanager
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, Optional
+
+logger = logging.getLogger(__name__)
+
+
+# ============================================================
+# 锁配置
+# ============================================================
+
+@dataclass
+class LockConfig:
+ """锁配置"""
+ # 锁类型: "memory" | "file" | "redis"
+ backend: str = os.getenv("LOCK_BACKEND", "file")
+
+ # 文件锁目录
+ lock_dir: str = os.getenv("LOCK_DIR", "data/locks")
+
+ # Redis 配置 (可选)
+ redis_url: str = os.getenv("REDIS_URL", "redis://localhost:6379/0")
+
+ # 锁超时 (秒)
+ lock_timeout: float = float(os.getenv("LOCK_TIMEOUT", "300")) # 5分钟
+
+ # 等待超时 (秒)
+ acquire_timeout: float = float(os.getenv("LOCK_ACQUIRE_TIMEOUT", "60"))
+
+
+# ============================================================
+# 锁后端抽象
+# ============================================================
+
+class LockBackend(ABC):
+ """锁后端接口"""
+
+ @abstractmethod
+ async def acquire(self, key: str, timeout: float) -> bool:
+ """获取锁"""
+ pass
+
+ @abstractmethod
+ async def release(self, key: str) -> None:
+ """释放锁"""
+ pass
+
+ @abstractmethod
+ async def is_locked(self, key: str) -> bool:
+ """检查是否已锁定"""
+ pass
+
+
+# ============================================================
+# 内存锁 (单进程)
+# ============================================================
+
+class MemoryLockBackend(LockBackend):
+ """
+ 内存锁后端 (asyncio.Lock)
+
+ 适用于: 单 Worker 部署
+ """
+
+ def __init__(self):
+ self._locks: Dict[str, asyncio.Lock] = {}
+ self._meta_lock = asyncio.Lock()
+
+ async def _get_lock(self, key: str) -> asyncio.Lock:
+ async with self._meta_lock:
+ if key not in self._locks:
+ self._locks[key] = asyncio.Lock()
+ return self._locks[key]
+
+ async def acquire(self, key: str, timeout: float) -> bool:
+ lock = await self._get_lock(key)
+ try:
+ await asyncio.wait_for(lock.acquire(), timeout=timeout)
+ return True
+ except asyncio.TimeoutError:
+ return False
+
+ async def release(self, key: str) -> None:
+ if key in self._locks:
+ lock = self._locks[key]
+ if lock.locked():
+ lock.release()
+
+ async def is_locked(self, key: str) -> bool:
+ if key not in self._locks:
+ return False
+ return self._locks[key].locked()
+
+
+# ============================================================
+# 文件锁 (多进程,单节点)
+# ============================================================
+
+class FileLockBackend(LockBackend):
+ """
+ 文件锁后端
+
+ 适用于: 多 Worker 单节点部署 (Gunicorn + Qdrant Server)
+
+ 实现:
+ - Windows: msvcrt.locking
+ - Unix: fcntl.flock
+ """
+
+ def __init__(self, lock_dir: str):
+ self._lock_dir = Path(lock_dir)
+ self._lock_dir.mkdir(parents=True, exist_ok=True)
+ self._handles: Dict[str, object] = {}
+ self._memory_locks: Dict[str, asyncio.Lock] = {}
+ self._meta_lock = asyncio.Lock()
+
+ def _get_lock_path(self, key: str) -> Path:
+ # 清理 key,避免路径注入
+ safe_key = "".join(c if c.isalnum() or c in "_-" else "_" for c in key)
+ return self._lock_dir / f"{safe_key}.lock"
+
+ async def _get_memory_lock(self, key: str) -> asyncio.Lock:
+ """同进程内的内存锁,防止同一进程内多个协程竞争文件锁"""
+ async with self._meta_lock:
+ if key not in self._memory_locks:
+ self._memory_locks[key] = asyncio.Lock()
+ return self._memory_locks[key]
+
+ async def acquire(self, key: str, timeout: float) -> bool:
+ # 先获取内存锁
+ mem_lock = await self._get_memory_lock(key)
+ try:
+ await asyncio.wait_for(mem_lock.acquire(), timeout=timeout)
+ except asyncio.TimeoutError:
+ return False
+
+ # 再获取文件锁
+ lock_path = self._get_lock_path(key)
+ start_time = time.time()
+
+ while time.time() - start_time < timeout:
+ try:
+ # 尝试获取文件锁
+ handle = open(lock_path, 'w')
+
+ if os.name == 'nt':
+ # Windows
+ import msvcrt
+ msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
+ else:
+ # Unix
+ import fcntl
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+
+ self._handles[key] = handle
+ logger.debug(f"🔒 文件锁获取成功: {key}")
+ return True
+
+ except (IOError, OSError):
+ # 锁被占用,等待后重试
+ if 'handle' in dir() and handle:
+ handle.close()
+ await asyncio.sleep(0.1)
+
+ # 超时,释放内存锁
+ mem_lock.release()
+ logger.warning(f"⏰ 文件锁获取超时: {key}")
+ return False
+
+ async def release(self, key: str) -> None:
+ if key in self._handles:
+ handle = self._handles.pop(key)
+ try:
+ if os.name == 'nt':
+ import msvcrt
+ try:
+ msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
+ except:
+ pass
+ else:
+ import fcntl
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
+ handle.close()
+ except:
+ pass
+ logger.debug(f"🔓 文件锁已释放: {key}")
+
+ # 释放内存锁
+ if key in self._memory_locks:
+ lock = self._memory_locks[key]
+ if lock.locked():
+ lock.release()
+
+ async def is_locked(self, key: str) -> bool:
+ lock_path = self._get_lock_path(key)
+ if not lock_path.exists():
+ return False
+
+ try:
+ handle = open(lock_path, 'w')
+ if os.name == 'nt':
+ import msvcrt
+ msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
+ msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
+ else:
+ import fcntl
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
+ handle.close()
+ return False
+ except (IOError, OSError):
+ return True
+
+
+# ============================================================
+# Redis 锁 (分布式,多节点)
+# ============================================================
+
+class RedisLockBackend(LockBackend):
+ """
+ Redis 分布式锁后端
+
+ 适用于: 多节点部署 (K8s + Redis)
+
+ 依赖: redis[hiredis]
+ """
+
+ def __init__(self, redis_url: str, lock_timeout: float):
+ self._redis_url = redis_url
+ self._lock_timeout = lock_timeout
+ self._client = None
+ self._locks: Dict[str, object] = {}
+
+ async def _get_client(self):
+ if self._client is None:
+ try:
+ import redis.asyncio as aioredis
+ self._client = await aioredis.from_url(self._redis_url)
+ except ImportError:
+ raise RuntimeError(
+ "Redis 锁需要安装 redis 包: pip install redis[hiredis]"
+ )
+ return self._client
+
+ async def acquire(self, key: str, timeout: float) -> bool:
+ client = await self._get_client()
+ lock_key = f"repo_lock:{key}"
+
+ start_time = time.time()
+ while time.time() - start_time < timeout:
+ # 尝试设置锁
+ acquired = await client.set(
+ lock_key,
+ "locked",
+ nx=True,
+ ex=int(self._lock_timeout)
+ )
+ if acquired:
+ logger.debug(f"🔒 Redis 锁获取成功: {key}")
+ return True
+ await asyncio.sleep(0.1)
+
+ logger.warning(f"⏰ Redis 锁获取超时: {key}")
+ return False
+
+ async def release(self, key: str) -> None:
+ client = await self._get_client()
+ lock_key = f"repo_lock:{key}"
+ await client.delete(lock_key)
+ logger.debug(f"🔓 Redis 锁已释放: {key}")
+
+ async def is_locked(self, key: str) -> bool:
+ client = await self._get_client()
+ lock_key = f"repo_lock:{key}"
+ return await client.exists(lock_key) > 0
+
+
+# ============================================================
+# 统一锁接口
+# ============================================================
+
+class RepoLock:
+ """
+ 仓库级锁 - 统一接口
+
+ 自动根据配置选择后端:
+ - memory: 单进程内存锁 (开发)
+ - file: 文件锁 (多进程单节点)
+ - redis: 分布式锁 (多节点)
+
+ 使用:
+ ```python
+ async with RepoLock.acquire(session_id):
+ # 独占写操作
+ await store.reset()
+ ```
+ """
+
+ _backend: Optional[LockBackend] = None
+ _config: Optional[LockConfig] = None
+
+ @classmethod
+ def _get_backend(cls) -> LockBackend:
+ if cls._backend is None:
+ cls._config = LockConfig()
+
+ if cls._config.backend == "redis":
+ cls._backend = RedisLockBackend(
+ cls._config.redis_url,
+ cls._config.lock_timeout
+ )
+ logger.info("🔐 使用 Redis 分布式锁")
+ elif cls._config.backend == "file":
+ cls._backend = FileLockBackend(cls._config.lock_dir)
+ logger.info(f"🔐 使用文件锁: {cls._config.lock_dir}")
+ else:
+ cls._backend = MemoryLockBackend()
+ logger.info("🔐 使用内存锁 (单进程)")
+
+ return cls._backend
+
+ @classmethod
+ @asynccontextmanager
+ async def acquire(cls, session_id: str, timeout: float = None):
+ """
+ 获取仓库写锁
+
+ Args:
+ session_id: 仓库的 session ID
+ timeout: 获取锁的超时时间 (默认从配置读取)
+
+ Raises:
+ TimeoutError: 获取锁超时
+ """
+ backend = cls._get_backend()
+ config = cls._config or LockConfig()
+ wait_timeout = timeout or config.acquire_timeout
+
+ acquired = await backend.acquire(session_id, wait_timeout)
+ if not acquired:
+ raise TimeoutError(f"无法获取仓库锁: {session_id} (等待 {wait_timeout}s)")
+
+ try:
+ yield
+ finally:
+ await backend.release(session_id)
+
+ @classmethod
+ async def is_locked(cls, session_id: str) -> bool:
+ """检查仓库是否被锁定"""
+ backend = cls._get_backend()
+ return await backend.is_locked(session_id)
+
+ @classmethod
+ async def try_acquire(cls, session_id: str, timeout: float = 0.1):
+ """
+ 尝试获取锁 (非阻塞)
+
+ 用于检测是否有其他用户正在分析同一仓库
+ """
+ backend = cls._get_backend()
+ return await backend.acquire(session_id, timeout)
diff --git a/app/utils/retry.py b/app/utils/retry.py
new file mode 100644
index 0000000000000000000000000000000000000000..5658a61ac40cc9770feaf8f2bb73213ea18fac9a
--- /dev/null
+++ b/app/utils/retry.py
@@ -0,0 +1,198 @@
+# 文件路径: app/utils/retry.py
+"""
+LLM 调用重试机制
+
+使用 tenacity 库实现智能重试策略:
+- 指数退避 (Exponential Backoff)
+- 可重试异常识别
+- 最大重试次数限制
+- 详细日志记录
+"""
+
+import logging
+from typing import Callable, Type, Tuple, Any
+from functools import wraps
+
+from tenacity import (
+ retry,
+ stop_after_attempt,
+ wait_exponential,
+ retry_if_exception_type,
+ before_sleep_log,
+ after_log,
+ RetryError,
+)
+
+# 配置日志
+logger = logging.getLogger("llm_retry")
+logger.setLevel(logging.INFO)
+
+
+# ============================================================================
+# 可重试的异常类型定义
+# ============================================================================
+
+# 网络/临时性错误 - 应该重试
+RETRYABLE_EXCEPTIONS: Tuple[Type[Exception], ...] = (
+ ConnectionError,
+ TimeoutError,
+)
+
+# 尝试导入各 SDK 的异常类型
+try:
+ from openai import (
+ APIConnectionError,
+ APITimeoutError,
+ RateLimitError,
+ InternalServerError,
+ )
+ RETRYABLE_EXCEPTIONS = RETRYABLE_EXCEPTIONS + (
+ APIConnectionError,
+ APITimeoutError,
+ RateLimitError,
+ InternalServerError,
+ )
+except ImportError:
+ pass
+
+try:
+ from anthropic import (
+ APIConnectionError as AnthropicConnectionError,
+ APITimeoutError as AnthropicTimeoutError,
+ RateLimitError as AnthropicRateLimitError,
+ InternalServerError as AnthropicServerError,
+ )
+ RETRYABLE_EXCEPTIONS = RETRYABLE_EXCEPTIONS + (
+ AnthropicConnectionError,
+ AnthropicTimeoutError,
+ AnthropicRateLimitError,
+ AnthropicServerError,
+ )
+except ImportError:
+ pass
+
+try:
+ import httpx
+ RETRYABLE_EXCEPTIONS = RETRYABLE_EXCEPTIONS + (
+ httpx.ConnectError,
+ httpx.ReadTimeout,
+ httpx.ConnectTimeout,
+ )
+except ImportError:
+ pass
+
+
+# ============================================================================
+# 重试配置
+# ============================================================================
+
+class RetryConfig:
+ """重试配置"""
+ MAX_ATTEMPTS: int = 3 # 最大重试次数
+ MIN_WAIT_SECONDS: float = 1.0 # 最小等待时间
+ MAX_WAIT_SECONDS: float = 30.0 # 最大等待时间
+ EXPONENTIAL_MULTIPLIER: float = 2.0 # 指数退避乘数
+
+
+# ============================================================================
+# 重试装饰器
+# ============================================================================
+
+def create_retry_decorator(
+ max_attempts: int = RetryConfig.MAX_ATTEMPTS,
+ min_wait: float = RetryConfig.MIN_WAIT_SECONDS,
+ max_wait: float = RetryConfig.MAX_WAIT_SECONDS,
+):
+ """
+ 创建 LLM 调用重试装饰器
+
+ Args:
+ max_attempts: 最大重试次数
+ min_wait: 最小等待时间 (秒)
+ max_wait: 最大等待时间 (秒)
+
+ Returns:
+ tenacity retry 装饰器
+ """
+ return retry(
+ # 重试条件: 仅对可重试异常进行重试
+ retry=retry_if_exception_type(RETRYABLE_EXCEPTIONS),
+ # 停止条件: 达到最大重试次数
+ stop=stop_after_attempt(max_attempts),
+ # 等待策略: 指数退避
+ wait=wait_exponential(
+ multiplier=RetryConfig.EXPONENTIAL_MULTIPLIER,
+ min=min_wait,
+ max=max_wait,
+ ),
+ # 日志: 重试前记录
+ before_sleep=before_sleep_log(logger, logging.WARNING),
+ # 日志: 重试后记录
+ after=after_log(logger, logging.DEBUG),
+ # 重新抛出最后一个异常
+ reraise=True,
+ )
+
+
+# 默认的重试装饰器实例
+llm_retry = create_retry_decorator()
+
+
+def with_retry(func: Callable) -> Callable:
+ """
+ 为异步函数添加重试能力的装饰器
+
+ Usage:
+ @with_retry
+ async def call_llm(...):
+ ...
+ """
+ @wraps(func)
+ async def wrapper(*args, **kwargs):
+ @llm_retry
+ async def _inner():
+ return await func(*args, **kwargs)
+ return await _inner()
+ return wrapper
+
+
+# ============================================================================
+# 便捷函数
+# ============================================================================
+
+async def retry_async(
+ coro_func: Callable,
+ *args,
+ max_attempts: int = RetryConfig.MAX_ATTEMPTS,
+ **kwargs
+) -> Any:
+ """
+ 带重试的异步调用
+
+ Usage:
+ result = await retry_async(
+ client.chat.completions.create,
+ model="gpt-4",
+ messages=[...]
+ )
+ """
+ decorator = create_retry_decorator(max_attempts=max_attempts)
+
+ @decorator
+ async def _call():
+ return await coro_func(*args, **kwargs)
+
+ return await _call()
+
+
+def is_retryable_error(error: Exception) -> bool:
+ """判断异常是否可重试"""
+ return isinstance(error, RETRYABLE_EXCEPTIONS)
+
+
+def log_retry_info(attempt: int, max_attempts: int, error: Exception, wait_time: float):
+ """记录重试信息的辅助函数"""
+ logger.warning(
+ f"🔄 LLM 调用失败 (尝试 {attempt}/{max_attempts}): {type(error).__name__}: {error}. "
+ f"等待 {wait_time:.1f}s 后重试..."
+ )
diff --git a/app/utils/session.py b/app/utils/session.py
new file mode 100644
index 0000000000000000000000000000000000000000..562aca67efda40ebcb20867bb083f6e5f45dba00
--- /dev/null
+++ b/app/utils/session.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+"""
+Session 工具模块
+
+提供基于仓库 URL 的 Session ID 生成和管理
+"""
+
+import hashlib
+import re
+from typing import Optional, Tuple, Dict
+from urllib.parse import urlparse
+
+from app.core.config import conversation_config
+
+
+def normalize_repo_url(url: str) -> str:
+ """
+ 标准化 GitHub 仓库 URL
+
+ 支持格式:
+ - https://github.com/owner/repo
+ - https://github.com/owner/repo.git
+ - https://github.com/owner/repo/tree/main
+ - git@github.com:owner/repo.git
+
+ Returns:
+ 标准化的 URL: https://github.com/owner/repo (全小写)
+ """
+ url = url.strip().lower() # 统一转为小写
+
+ # 处理 SSH 格式
+ if url.startswith('git@'):
+ # git@github.com:owner/repo.git -> https://github.com/owner/repo
+ match = re.match(r'git@github\.com:(.+?)(?:\.git)?$', url)
+ if match:
+ return f"https://github.com/{match.group(1)}"
+
+ # 处理 HTTPS 格式
+ parsed = urlparse(url)
+ path = parsed.path.strip('/')
+
+ # 移除 .git 后缀
+ if path.endswith('.git'):
+ path = path[:-4]
+
+ # 只保留 owner/repo 部分
+ parts = path.split('/')
+ if len(parts) >= 2:
+ path = f"{parts[0]}/{parts[1]}"
+
+ return f"https://github.com/{path}"
+
+
+def extract_repo_info(url: str) -> Tuple[str, str]:
+ """
+ 从 URL 提取仓库信息
+
+ Returns:
+ (owner, repo) 元组
+ """
+ normalized = normalize_repo_url(url)
+ path = urlparse(normalized).path.strip('/')
+ parts = path.split('/')
+
+ if len(parts) >= 2:
+ return parts[0], parts[1]
+ return "", ""
+
+
+def generate_repo_session_id(repo_url: str) -> str:
+ """
+ 基于仓库 URL 生成稳定的 Session ID
+
+ 同一仓库 URL -> 同一 Session ID
+
+ 格式: repo_{short_hash}_{owner}_{repo}
+ """
+ normalized = normalize_repo_url(repo_url)
+ owner, repo = extract_repo_info(repo_url)
+
+ # 生成短 hash (8 字符)
+ url_hash = hashlib.sha256(normalized.encode()).hexdigest()[:8]
+
+ # 清理 owner 和 repo 名称
+ clean_owner = re.sub(r'[^a-zA-Z0-9]', '', owner)[:10]
+ clean_repo = re.sub(r'[^a-zA-Z0-9]', '', repo)[:15]
+
+ return f"repo_{url_hash}_{clean_owner}_{clean_repo}"
+
+
+def is_repo_session_id(session_id: str) -> bool:
+ """判断是否为仓库级 Session ID"""
+ return session_id.startswith("repo_")
+
+
+# === 对话历史管理 ===
+
+class ConversationMemory:
+ """
+ 对话记忆管理 - 滑动窗口 + 摘要压缩
+
+ 特性:
+ 1. 保留最近 N 轮完整对话
+ 2. 早期对话自动压缩为摘要
+ 3. 支持 token 估算
+ """
+
+ def __init__(
+ self,
+ max_recent_turns: int = None,
+ max_context_tokens: int = None,
+ summary_threshold: int = None,
+ ):
+ # 使用统一配置
+ self.max_recent_turns = max_recent_turns or conversation_config.max_recent_turns
+ self.max_context_tokens = max_context_tokens or conversation_config.max_context_tokens
+ self.summary_threshold = summary_threshold or conversation_config.summary_threshold
+
+ self._messages: list = [] # 完整消息历史
+ self._summary: Optional[str] = None # 早期对话摘要
+ self._summary_up_to: int = 0 # 摘要覆盖到第 N 条消息
+
+ def add_message(self, role: str, content: str) -> None:
+ """添加消息"""
+ self._messages.append({
+ "role": role,
+ "content": content
+ })
+
+ def add_user_message(self, content: str) -> None:
+ """添加用户消息"""
+ self.add_message("user", content)
+
+ def add_assistant_message(self, content: str) -> None:
+ """添加助手消息"""
+ self.add_message("assistant", content)
+
+ def get_context_messages(self) -> list:
+ """
+ 获取用于 LLM 的上下文消息
+
+ 策略:
+ 1. 如果消息数 <= max_recent_turns * 2,返回全部
+ 2. 否则返回: [摘要] + 最近 N 轮
+ """
+ total_messages = len(self._messages)
+ max_messages = self.max_recent_turns * 2 # user + assistant = 1 轮
+
+ if total_messages <= max_messages:
+ return list(self._messages)
+
+ # 需要截断
+ recent_messages = self._messages[-max_messages:]
+
+ # 如果有摘要,加在前面
+ if self._summary:
+ return [
+ {"role": "system", "content": f"[Earlier conversation summary]\n{self._summary}"}
+ ] + recent_messages
+
+ return recent_messages
+
+ def needs_summarization(self) -> bool:
+ """检查是否需要生成摘要"""
+ unsummarized = len(self._messages) - self._summary_up_to
+ return unsummarized > self.summary_threshold * 2
+
+ def get_messages_to_summarize(self) -> list:
+ """获取需要摘要的消息"""
+ if not self.needs_summarization():
+ return []
+
+ # 保留最近的,摘要早期的
+ end_idx = len(self._messages) - self.max_recent_turns * 2
+ return self._messages[self._summary_up_to:end_idx]
+
+ def set_summary(self, summary: str, up_to_index: int) -> None:
+ """设置摘要"""
+ if self._summary:
+ # 合并旧摘要
+ self._summary = f"{self._summary}\n\n{summary}"
+ else:
+ self._summary = summary
+ self._summary_up_to = up_to_index
+
+ def clear(self) -> None:
+ """清空对话历史"""
+ self._messages = []
+ self._summary = None
+ self._summary_up_to = 0
+
+ def get_turn_count(self) -> int:
+ """获取对话轮数"""
+ return len(self._messages) // 2
+
+ def get_stats(self) -> dict:
+ """获取统计信息"""
+ return {
+ "total_messages": len(self._messages),
+ "turn_count": self.get_turn_count(),
+ "has_summary": self._summary is not None,
+ "summary_covers": self._summary_up_to,
+ }
+
+
+# === 全局对话记忆存储 ===
+# key: session_id, value: ConversationMemory
+# 纯内存存储,服务重启自动清空
+_conversation_memories: Dict[str, ConversationMemory] = {}
+
+
+def get_conversation_memory(session_id: str) -> ConversationMemory:
+ """获取或创建对话记忆"""
+ if session_id not in _conversation_memories:
+ _conversation_memories[session_id] = ConversationMemory()
+ return _conversation_memories[session_id]
+
+
+def clear_conversation_memory(session_id: str) -> None:
+ """清除对话记忆"""
+ if session_id in _conversation_memories:
+ del _conversation_memories[session_id]
+
+
+def get_memory_stats() -> dict:
+ """获取对话记忆统计"""
+ return {
+ "total_memories": len(_conversation_memories),
+ "sessions": list(_conversation_memories.keys()),
+ }
diff --git a/deploy.sh b/deploy.sh
new file mode 100644
index 0000000000000000000000000000000000000000..253eb5a08ebfd7cdee5e7b552419671cf7fae5ea
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,143 @@
+#!/bin/bash
+# ============================================================
+# GitHub RAG Agent - 生产环境部署脚本 (2核2G服务器优化版)
+# ============================================================
+#
+# 使用方法:
+# chmod +x deploy.sh
+# ./deploy.sh
+#
+# 前置要求:
+# - Python 3.10+
+# - Docker (用于运行 Qdrant)
+#
+# ============================================================
+
+set -e
+
+echo "🚀 GitHub RAG Agent 部署脚本"
+echo "=========================================="
+
+# 检查是否在项目目录
+if [ ! -f "requirements.txt" ]; then
+ echo "❌ 请在项目根目录运行此脚本"
+ exit 1
+fi
+
+# 检查 .env 文件
+if [ ! -f ".env" ]; then
+ echo "❌ 未找到 .env 文件,请先复制 .env.example 并配置"
+ echo " cp .env.example .env"
+ echo " vim .env"
+ exit 1
+fi
+
+# ============================================================
+# 1. 启动 Qdrant Server (Docker)
+# ============================================================
+echo ""
+echo "📦 步骤 1: 启动 Qdrant Server..."
+
+# 检查 Docker 是否运行
+if ! docker info > /dev/null 2>&1; then
+ echo "❌ Docker 未运行,请先启动 Docker"
+ exit 1
+fi
+
+# 检查 Qdrant 容器是否已存在
+if docker ps -a --format '{{.Names}}' | grep -q "^qdrant-server$"; then
+ echo " Qdrant 容器已存在,检查状态..."
+ if docker ps --format '{{.Names}}' | grep -q "^qdrant-server$"; then
+ echo " ✅ Qdrant 已在运行"
+ else
+ echo " 🔄 启动已有的 Qdrant 容器..."
+ docker start qdrant-server
+ fi
+else
+ echo " 🆕 创建并启动 Qdrant 容器 (内存限制 512MB)..."
+ docker run -d \
+ --name qdrant-server \
+ --restart unless-stopped \
+ -p 6333:6333 \
+ -p 6334:6334 \
+ -v qdrant_data:/qdrant/storage \
+ -m 512m \
+ -e QDRANT__STORAGE__ON_DISK_PAYLOAD=true \
+ qdrant/qdrant:latest
+fi
+
+# 等待 Qdrant 就绪
+echo " ⏳ 等待 Qdrant 就绪..."
+for i in {1..30}; do
+ if curl -s http://localhost:6333/health > /dev/null 2>&1; then
+ echo " ✅ Qdrant 已就绪"
+ break
+ fi
+ sleep 1
+done
+
+# ============================================================
+# 2. 创建 Python 虚拟环境
+# ============================================================
+echo ""
+echo "🐍 步骤 2: 配置 Python 环境..."
+
+if [ ! -d "venv" ]; then
+ echo " 创建虚拟环境..."
+ python3 -m venv venv
+fi
+
+echo " 激活虚拟环境..."
+source venv/bin/activate
+
+echo " 安装依赖..."
+pip install -q --upgrade pip
+pip install -q -r requirements.txt
+
+# ============================================================
+# 3. 创建必要目录
+# ============================================================
+echo ""
+echo "📁 步骤 3: 创建数据目录..."
+mkdir -p data/locks
+mkdir -p data/contexts
+mkdir -p logs
+
+# ============================================================
+# 4. 设置环境变量
+# ============================================================
+echo ""
+echo "⚙️ 步骤 4: 配置环境变量..."
+
+# 从 .env 加载
+set -a
+source .env
+set +a
+
+# 设置 Server 模式
+export QDRANT_MODE=server
+export QDRANT_URL=http://localhost:6333
+export LOCK_BACKEND=file
+export LOCK_DIR=data/locks
+export GUNICORN_WORKERS=2
+
+echo " QDRANT_MODE=$QDRANT_MODE"
+echo " QDRANT_URL=$QDRANT_URL"
+echo " GUNICORN_WORKERS=$GUNICORN_WORKERS"
+
+# ============================================================
+# 5. 启动应用
+# ============================================================
+echo ""
+echo "🌐 步骤 5: 启动 FastAPI 应用..."
+echo "=========================================="
+echo " Workers: 2 (优化2核CPU)"
+echo " 监听地址: 0.0.0.0:8000"
+echo " Qdrant: http://localhost:6333"
+echo "=========================================="
+echo ""
+echo " 按 Ctrl+C 停止服务"
+echo ""
+
+# 使用 Gunicorn 启动 (2 Workers)
+gunicorn app.main:app -c gunicorn_conf.py
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d90b4fca0db8f9fdea19babc6d1b184b776d8c8
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,102 @@
+# Docker Compose 配置 - 生产环境部署 (优化版: 2核2G服务器)
+# 包含: FastAPI 应用 + Qdrant Server
+
+version: '3.8'
+
+services:
+ # ============================================================
+ # Qdrant 向量数据库 (限制内存 512MB)
+ # ============================================================
+ qdrant:
+ image: qdrant/qdrant:latest
+ container_name: github-rag-qdrant
+ restart: unless-stopped
+ ports:
+ - "6333:6333" # REST API
+ - "6334:6334" # gRPC
+ volumes:
+ - qdrant_data:/qdrant/storage
+ environment:
+ - QDRANT__SERVICE__GRPC_PORT=6334
+ - QDRANT__STORAGE__ON_DISK_PAYLOAD=true # Payload 存磁盘,省内存
+ deploy:
+ resources:
+ limits:
+ memory: 512M
+ reservations:
+ memory: 256M
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:6333/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+ # ============================================================
+ # FastAPI 应用 (2 Workers, 限制内存 1GB)
+ # ============================================================
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: github-rag-app
+ restart: unless-stopped
+ ports:
+ - "8000:8000"
+ environment:
+ # Qdrant Server 模式
+ - QDRANT_MODE=server
+ - QDRANT_URL=http://qdrant:6333
+
+ # Worker 数量 (2核服务器建议2个)
+ - GUNICORN_WORKERS=2
+
+ # 文件锁 (多 Worker)
+ - LOCK_BACKEND=file
+ - LOCK_DIR=/app/data/locks
+
+ # LLM 配置 (从 .env 读取)
+ - LLM_PROVIDER=${LLM_PROVIDER:-deepseek}
+ - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
+ - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
+ - GEMINI_API_KEY=${GEMINI_API_KEY}
+ - SILICON_API_KEY=${SILICON_API_KEY}
+ - GITHUB_TOKEN=${GITHUB_TOKEN}
+ volumes:
+ - app_data:/app/data
+ - app_logs:/app/logs
+ deploy:
+ resources:
+ limits:
+ memory: 1G
+ reservations:
+ memory: 512M
+ depends_on:
+ qdrant:
+ condition: service_healthy
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+volumes:
+ qdrant_data:
+ driver: local
+ app_data:
+ driver: local
+ app_logs:
+ driver: local
+
+# ============================================================
+# 使用说明
+# ============================================================
+# 1. 复制 .env.example 为 .env 并配置 API Keys
+# 2. 启动服务: docker-compose up -d
+# 3. 查看日志: docker-compose logs -f app
+# 4. 停止服务: docker-compose down
+#
+# 扩展到多 Worker:
+# 修改 Dockerfile 中的 gunicorn workers 数量,或使用:
+# docker-compose up -d --scale app=3
+# 配合 Nginx/Traefik 做负载均衡
diff --git a/evaluation/__init__.py b/evaluation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9e1b472ee75a19bd18836308c53646468559327
--- /dev/null
+++ b/evaluation/__init__.py
@@ -0,0 +1,64 @@
+# evaluation/__init__.py
+"""
+Evaluation 模块
+
+提供完整的评估框架,包括:
+- 数据模型 (models.py)
+- 评估引擎 (evaluation_framework.py)
+- 数据路由 (data_router.py)
+- 工具函数 (utils.py)
+- 数据分析 (analyze_eval_results.py)
+- 数据清洗 (clean_and_export_sft_data.py)
+
+使用示例:
+ from evaluation import EvaluationEngine, DataRoutingEngine, EvaluationResult
+ from evaluation.models import GenerationMetrics
+"""
+
+# 核心导出
+from evaluation.models import (
+ EvaluationLayer,
+ DataQualityTier,
+ QueryRewriteMetrics,
+ RetrievalMetrics,
+ GenerationMetrics,
+ AgenticMetrics,
+ EvaluationResult,
+)
+
+from evaluation.data_router import DataRoutingEngine
+from evaluation.evaluation_framework import EvaluationEngine
+
+# 工具函数
+from evaluation.utils import (
+ is_chatty_query,
+ has_code_indicators,
+ read_jsonl,
+ append_jsonl,
+ safe_truncate,
+ smart_truncate,
+ SFTLengthConfig,
+)
+
+__all__ = [
+ # 枚举
+ "EvaluationLayer",
+ "DataQualityTier",
+ # 数据模型
+ "QueryRewriteMetrics",
+ "RetrievalMetrics",
+ "GenerationMetrics",
+ "AgenticMetrics",
+ "EvaluationResult",
+ # 引擎
+ "EvaluationEngine",
+ "DataRoutingEngine",
+ # 工具函数
+ "is_chatty_query",
+ "has_code_indicators",
+ "read_jsonl",
+ "append_jsonl",
+ "safe_truncate",
+ "smart_truncate",
+ "SFTLengthConfig",
+]
diff --git a/evaluation/analyze_eval_results.py b/evaluation/analyze_eval_results.py
new file mode 100644
index 0000000000000000000000000000000000000000..b43ed42ec45bbe9fa686fbfe801ac5009c9a364c
--- /dev/null
+++ b/evaluation/analyze_eval_results.py
@@ -0,0 +1,379 @@
+# 文件路径: evaluation/analyze_eval_results.py
+"""
+自动化数据分析脚本
+用于分析评估结果,识别问题并生成诊断报告
+
+核心功能:
+1. 自动读取所有评估结果
+2. 按问题类型分类 Bad Case
+3. 生成可视化报告
+4. 推荐优化方向
+
+Author: Dexter
+Date: 2025-01-27
+"""
+
+import os
+from typing import Dict, List
+from collections import Counter, defaultdict
+from datetime import datetime
+
+from evaluation.utils import read_jsonl
+
+
+class EvaluationAnalyzer:
+ """评估结果分析器"""
+
+ def __init__(self, eval_results_file: str = "evaluation/sft_data/eval_results.jsonl"):
+ self.eval_results_file = eval_results_file
+ self.results: List[Dict] = read_jsonl(eval_results_file)
+ if not self.results:
+ print(f"⚠️ No results loaded from: {eval_results_file}")
+
+ def get_basic_stats(self) -> Dict:
+ """获取基本统计"""
+ if not self.results:
+ return {}
+
+ scores = [r.get("overall_score", 0) for r in self.results]
+ tiers = [r.get("data_quality_tier", "unknown") for r in self.results]
+
+ return {
+ "total_evaluations": len(self.results),
+ "avg_score": sum(scores) / len(scores) if scores else 0,
+ "max_score": max(scores) if scores else 0,
+ "min_score": min(scores) if scores else 0,
+ "median_score": sorted(scores)[len(scores)//2] if scores else 0,
+ "quality_distribution": dict(Counter(tiers)),
+ "sft_ready_count": sum(1 for r in self.results if r.get("sft_ready", False))
+ }
+
+ def identify_bad_cases(self, threshold: float = 0.6) -> List[Dict]:
+ """
+ 识别 Bad Case (得分低于阈值的结果)
+ 返回按得分排序的结果
+ """
+ bad_cases = [r for r in self.results if r.get("overall_score", 1) < threshold]
+ return sorted(bad_cases, key=lambda x: x.get("overall_score", 1))
+
+ def categorize_failures(self) -> Dict[str, List[Dict]]:
+ """
+ 按失败原因分类 Bad Case
+
+ 失败类型:
+ - retrieval_failure: 检索未命中
+ - generation_hallucination: 生成幻觉
+ - generation_incomplete: 回答不完整
+ - tool_call_error: 工具调用失败
+ """
+ categorized = defaultdict(list)
+
+ for result in self.identify_bad_cases():
+ reasons = []
+
+ # 检查检索失败
+ if result.get("retrieval"):
+ retrieval = result["retrieval"]
+ if retrieval.get("hit_rate", 1) == 0:
+ reasons.append("retrieval_failure")
+ elif retrieval.get("recall_at_k", 1) < 0.5:
+ reasons.append("retrieval_low_recall")
+
+ # 检查生成问题
+ if result.get("generation"):
+ generation = result["generation"]
+ if generation.get("faithfulness", 1) < 0.5:
+ reasons.append("generation_hallucination")
+ if generation.get("answer_completeness", 1) < 0.4:
+ reasons.append("generation_incomplete")
+ if generation.get("hallucination_count", 0) > 0:
+ reasons.append("hallucination_detected")
+
+ # 检查Agent行为
+ if result.get("agentic"):
+ agentic = result["agentic"]
+ if not agentic.get("success", True):
+ reasons.append("agentic_failure")
+
+ # 如果没有具体原因,标记为unknown
+ if not reasons:
+ reasons.append("unknown")
+
+ for reason in reasons:
+ categorized[reason].append(result)
+
+ return dict(categorized)
+
+ def layer_performance(self) -> Dict[str, Dict]:
+ """分析各层性能"""
+ layer_scores = defaultdict(list)
+
+ for result in self.results:
+ if result.get("query_rewrite"):
+ score = result["query_rewrite"].get("overall_score", 0)
+ if score:
+ layer_scores["query_rewrite"].append(score)
+
+ if result.get("retrieval"):
+ score = result["retrieval"].get("overall_score", 0)
+ if score:
+ layer_scores["retrieval"].append(score)
+
+ if result.get("generation"):
+ score = result["generation"].get("overall_score", 0)
+ if score:
+ layer_scores["generation"].append(score)
+
+ if result.get("agentic"):
+ score = result["agentic"].get("overall_score", 0)
+ if score:
+ layer_scores["agentic"].append(score)
+
+ # 计算每层的统计
+ layer_stats = {}
+ for layer, scores in layer_scores.items():
+ if scores:
+ layer_stats[layer] = {
+ "avg": sum(scores) / len(scores),
+ "min": min(scores),
+ "max": max(scores),
+ "count": len(scores)
+ }
+
+ return layer_stats
+
+ def get_recommendations(self) -> List[str]:
+ """基于分析结果生成优化建议"""
+ recommendations = []
+
+ # 分析各层性能
+ layer_perf = self.layer_performance()
+
+ # 检索层分析
+ if "retrieval" in layer_perf:
+ retrieval_score = layer_perf["retrieval"]["avg"]
+ if retrieval_score < 0.7:
+ recommendations.append(
+ "🔴 RETRIEVAL 层性能差 (avg: {:.2f})\n"
+ " 建议:\n"
+ " 1. 检查 chunking 策略是否过度分割\n"
+ " 2. 优化 embedding 模型 (考虑更强的模型)\n"
+ " 3. 调整混合检索的权重 (BM25 vs Vector)\n"
+ " 4. 分析实际召回的文件,看是否与预期偏离".format(retrieval_score)
+ )
+
+ # 生成层分析
+ if "generation" in layer_perf:
+ gen_score = layer_perf["generation"]["avg"]
+ if gen_score < 0.7:
+ recommendations.append(
+ "🟡 GENERATION 层存在问题 (avg: {:.2f})\n"
+ " 建议:\n"
+ " 1. 检查 Prompt 是否清晰 (可能LLM理解偏差)\n"
+ " 2. 检查是否存在幻觉 (生成不存在的函数名等)\n"
+ " 3. 优化 Context 的组织方式\n"
+ " 4. 考虑使用更强的LLM模型".format(gen_score)
+ )
+
+ # Query Rewrite 分析
+ if "query_rewrite" in layer_perf:
+ rewrite_score = layer_perf["query_rewrite"]["avg"]
+ if rewrite_score < 0.6:
+ recommendations.append(
+ "🟠 QUERY_REWRITE 层准确度低 (avg: {:.2f})\n"
+ " 建议:\n"
+ " 1. 优化关键词提取 Prompt\n"
+ " 2. 增加多语言处理支持\n"
+ " 3. 添加领域词汇表 (Domain Vocabulary)".format(rewrite_score)
+ )
+
+ # 通用建议
+ stats = self.get_basic_stats()
+ if stats.get("sft_ready_count", 0) / max(stats.get("total_evaluations", 1), 1) < 0.5:
+ recommendations.append(
+ "⚠️ SFT 可用数据不足 (< 50%)\n"
+ " 立即行动:\n"
+ " 1. 运行 continuous_eval 脚本收集更多数据\n"
+ " 2. 对现有数据进行自纠正 (Self-Correction)\n"
+ " 3. 扩展黄金数据集来改进模型"
+ )
+
+ return recommendations
+
+ def generate_report(self, output_file: str = "evaluation/analysis_report.md") -> str:
+ """生成完整的分析报告"""
+
+ report = []
+ report.append("# 📊 GitHub Agent 评估分析报告\n")
+ report.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ report.append("---\n")
+
+ # 1. 基本统计
+ stats = self.get_basic_stats()
+ report.append("## 📈 基本统计\n")
+ report.append(f"- 总评估次数: {stats.get('total_evaluations', 0)}\n")
+ report.append(f"- 平均得分: {stats.get('avg_score', 0):.3f}\n")
+ report.append(f"- 最高得分: {stats.get('max_score', 0):.3f}\n")
+ report.append(f"- 最低得分: {stats.get('min_score', 0):.3f}\n")
+ report.append(f"- 中位数得分: {stats.get('median_score', 0):.3f}\n")
+ report.append(f"- SFT 可用样本: {stats.get('sft_ready_count', 0)}\n\n")
+
+ # 2. 质量分级分布
+ report.append("## 🏆 质量分级分布\n")
+ distribution = stats.get("quality_distribution", {})
+ for tier, count in sorted(distribution.items()):
+ percentage = (count / stats.get('total_evaluations', 1)) * 100
+ report.append(f"- {tier.upper()}: {count} ({percentage:.1f}%)\n")
+ report.append("\n")
+
+ # 3. 各层性能
+ report.append("## 🎯 各层性能分析\n\n")
+ layer_perf = self.layer_performance()
+ for layer in ["query_rewrite", "retrieval", "generation", "agentic"]:
+ if layer in layer_perf:
+ perf = layer_perf[layer]
+ report.append(f"### {layer.upper()}\n")
+ report.append(f"- 平均得分: {perf['avg']:.3f}\n")
+ report.append(f"- 范围: [{perf['min']:.3f}, {perf['max']:.3f}]\n")
+ report.append(f"- 样本数: {perf['count']}\n\n")
+
+ # 4. Bad Case 分类
+ report.append("## 🔴 Bad Case 分析\n\n")
+ failures = self.categorize_failures()
+ for reason, cases in sorted(failures.items(), key=lambda x: -len(x[1])):
+ report.append(f"### {reason} ({len(cases)} cases)\n")
+ for case in cases[:3]: # 显示top 3
+ report.append(f"- 查询: {case.get('query', 'N/A')[:60]}...\n")
+ report.append(f" 得分: {case.get('overall_score', 0):.3f}\n")
+ report.append("\n")
+
+ # 5. 推荐行动
+ report.append("## 💡 优化建议\n\n")
+ recommendations = self.get_recommendations()
+ for i, rec in enumerate(recommendations, 1):
+ report.append(f"{i}. {rec}\n\n")
+
+ # 写入文件
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
+ with open(output_file, 'w', encoding='utf-8') as f:
+ f.writelines(report)
+
+ return "".join(report)
+
+ def export_bad_cases_csv(self, output_file: str = "evaluation/bad_cases.csv") -> None:
+ """导出 Bad Case 为 CSV (用于人工审查)"""
+ import csv
+
+ bad_cases = self.identify_bad_cases()
+
+ with open(output_file, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=[
+ "query", "overall_score", "tier",
+ "retrieval_score", "generation_score", "agentic_score",
+ "error_message", "timestamp"
+ ])
+
+ writer.writeheader()
+ for case in bad_cases:
+ writer.writerow({
+ "query": case.get("query", ""),
+ "overall_score": case.get("overall_score", 0),
+ "tier": case.get("data_quality_tier", "unknown"),
+ "retrieval_score": case.get("retrieval", {}).get("overall_score", 0),
+ "generation_score": case.get("generation", {}).get("overall_score", 0),
+ "agentic_score": case.get("agentic", {}).get("overall_score", 0),
+ "error_message": case.get("error_message", ""),
+ "timestamp": case.get("timestamp", "")
+ })
+
+ print(f"✅ Exported {len(bad_cases)} bad cases to {output_file}")
+
+
+# ============================================================================
+# 命令行工具
+# ============================================================================
+
+def print_summary(analyzer: EvaluationAnalyzer):
+ """打印摘要"""
+ print("\n" + "=" * 70)
+ print("📊 评估结果摘要")
+ print("=" * 70)
+
+ stats = analyzer.get_basic_stats()
+
+ print(f"\n📈 基本统计:")
+ print(f" 总评估: {stats.get('total_evaluations', 0)}")
+ print(f" 平均分: {stats.get('avg_score', 0):.3f}")
+ print(f" 分布: {stats.get('quality_distribution', {})}")
+ print(f" SFT可用: {stats.get('sft_ready_count', 0)}")
+
+ print(f"\n🎯 各层性能:")
+ layer_perf = analyzer.layer_performance()
+ for layer, perf in layer_perf.items():
+ print(f" {layer:.<30} {perf['avg']:.3f} (avg)")
+
+ print(f"\n🔴 Bad Case Top 5:")
+ bad_cases = analyzer.identify_bad_cases()[:5]
+ for i, case in enumerate(bad_cases, 1):
+ print(f" {i}. {case.get('query', 'N/A')[:40]:<40} Score: {case.get('overall_score', 0):.3f}")
+
+ print(f"\n💡 优化建议:")
+ recommendations = analyzer.get_recommendations()
+ for rec in recommendations[:3]:
+ print(f" - {rec.split(chr(10))[0]}")
+
+ print("\n" + "=" * 70)
+
+
+def main():
+ import sys
+
+ analyzer = EvaluationAnalyzer()
+
+ if len(sys.argv) > 1:
+ command = sys.argv[1]
+
+ if command == "summary":
+ print_summary(analyzer)
+
+ elif command == "report":
+ report = analyzer.generate_report()
+ print(report)
+
+ elif command == "bad-cases":
+ analyzer.export_bad_cases_csv()
+ bad_cases = analyzer.identify_bad_cases()
+ print(f"\n✅ Found {len(bad_cases)} bad cases")
+ print("详见 evaluation/bad_cases.csv")
+
+ elif command == "layer-perf":
+ layer_perf = analyzer.layer_performance()
+ print("\n🎯 各层性能:")
+ for layer, perf in layer_perf.items():
+ print(f"\n{layer.upper()}:")
+ print(f" Average: {perf['avg']:.3f}")
+ print(f" Range: [{perf['min']:.3f}, {perf['max']:.3f}]")
+ print(f" Samples: {perf['count']}")
+
+ elif command == "recommendations":
+ recs = analyzer.get_recommendations()
+ print("\n💡 优化建议:\n")
+ for i, rec in enumerate(recs, 1):
+ print(f"{i}.\n{rec}\n")
+
+ else:
+ print(f"Unknown command: {command}")
+
+ else:
+ print("自动化评估数据分析工具")
+ print()
+ print("用法:")
+ print(" python analyze_eval_results.py summary # 快速摘要")
+ print(" python analyze_eval_results.py report # 生成完整报告")
+ print(" python analyze_eval_results.py bad-cases # 导出Bad Case")
+ print(" python analyze_eval_results.py layer-perf # 各层性能分析")
+ print(" python analyze_eval_results.py recommendations # 优化建议")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/evaluation/clean_and_export_sft_data.py b/evaluation/clean_and_export_sft_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac194aa642270e93c1d4bcb16dcfa5b8df05b015
--- /dev/null
+++ b/evaluation/clean_and_export_sft_data.py
@@ -0,0 +1,369 @@
+#!/usr/bin/env python3
+"""
+SFT 数据清洗与导出脚本
+
+功能:
+1. 从 eval_results.jsonl 读取原始评估数据
+2. 应用严格的质量过滤规则
+3. 转换为标准 SFT 训练格式
+4. 导出为可直接用于训练的数据集
+
+Author: Dexter
+Date: 2026-01-28
+"""
+
+import json
+import os
+from datetime import datetime
+from typing import Dict, List, Tuple
+from pathlib import Path
+
+from evaluation.utils import is_chatty_query, has_code_indicators
+
+
+# ============================================================================
+# 配置
+# ============================================================================
+
+class CleaningConfig:
+ """数据清洗配置"""
+ # 质量阈值
+ MIN_OVERALL_SCORE = 0.7 # 最低综合分
+ MIN_FAITHFULNESS = 0.6 # 最低 faithfulness
+ MIN_ANSWER_RELEVANCE = 0.6 # 最低 answer_relevance
+
+ # 长度阈值
+ MIN_QUERY_LENGTH = 10 # 最短 query
+ MIN_ANSWER_LENGTH = 100 # 最短 answer
+ MIN_CONTEXT_LENGTH = 50 # 最短 context
+ MAX_CONTEXT_LENGTH = 4000 # 最长 context(截断)
+
+ # 必须条件
+ REQUIRE_REPO_URL = True # 必须有仓库 URL
+ REQUIRE_CODE_IN_CONTEXT = True # 上下文必须包含代码
+
+ # 输出配置
+ OUTPUT_DIR = "evaluation/sft_data/cleaned"
+
+
+# ============================================================================
+# 数据清洗逻辑
+# ============================================================================
+
+def validate_sample(sample: Dict, config: CleaningConfig) -> Tuple[bool, str]:
+ """
+ 验证单个样本是否符合质量标准
+
+ Returns:
+ (is_valid, rejection_reason)
+ """
+ # 1. 检查基本字段存在
+ if not sample.get("query"):
+ return False, "missing_query"
+
+ if not sample.get("generation"):
+ return False, "missing_generation"
+
+ gen = sample["generation"]
+
+ # 2. 检查 repo_url
+ if config.REQUIRE_REPO_URL and not sample.get("repo_url"):
+ return False, "missing_repo_url"
+
+ # 3. 检查质量分数
+ overall_score = sample.get("overall_score", 0)
+ if overall_score < config.MIN_OVERALL_SCORE:
+ return False, f"low_score:{overall_score:.2f}"
+
+ faithfulness = gen.get("faithfulness", 0)
+ if faithfulness < config.MIN_FAITHFULNESS:
+ return False, f"low_faithfulness:{faithfulness:.2f}"
+
+ answer_relevance = gen.get("answer_relevance", 0)
+ if answer_relevance < config.MIN_ANSWER_RELEVANCE:
+ return False, f"low_relevance:{answer_relevance:.2f}"
+
+ # 4. 检查长度
+ query = sample.get("query", "")
+ if len(query) < config.MIN_QUERY_LENGTH:
+ return False, f"short_query:{len(query)}"
+
+ answer = gen.get("generated_answer", "")
+ if len(answer) < config.MIN_ANSWER_LENGTH:
+ return False, f"short_answer:{len(answer)}"
+
+ context = gen.get("retrieved_context", "")
+ if len(context) < config.MIN_CONTEXT_LENGTH:
+ return False, f"short_context:{len(context)}"
+
+ # 5. 检查闲聊
+ if is_chatty_query(query):
+ return False, "chatty_query"
+
+ # 6. 检查代码存在
+ if config.REQUIRE_CODE_IN_CONTEXT and not has_code_indicators(context):
+ return False, "no_code_in_context"
+
+ return True, "passed"
+
+
+def transform_to_sft_format(sample: Dict, config: CleaningConfig) -> Dict:
+ """
+ 将原始评估数据转换为标准 SFT 格式
+ """
+ gen = sample["generation"]
+
+ # 清理和截断 context
+ context = gen.get("retrieved_context", "")
+ if len(context) > config.MAX_CONTEXT_LENGTH:
+ context = context[:config.MAX_CONTEXT_LENGTH] + "\n... [truncated]"
+
+ # 构建标准 SFT 格式
+ sft_sample = {
+ # === 核心训练字段 ===
+ "instruction": "你是一个专业的GitHub代码仓库分析助手。根据提供的代码上下文,准确回答用户关于代码实现、架构设计、功能逻辑等问题。回答时应该:1) 直接引用相关代码 2) 解释代码的工作原理 3) 如有必要,提供代码示例。",
+ "input": f"[用户问题]\n{sample['query']}\n\n[代码上下文]\n{context}",
+ "output": gen.get("generated_answer", ""),
+
+ # === 元数据 ===
+ "metadata": {
+ "query": sample["query"],
+ "repo_url": sample.get("repo_url", ""),
+ "language": sample.get("language", "en"),
+ "session_id": sample.get("session_id", ""),
+ "timestamp": sample.get("timestamp", ""),
+ "quality_tier": sample.get("data_quality_tier", ""),
+ "overall_score": sample.get("overall_score", 0),
+ "faithfulness": gen.get("faithfulness", 0),
+ "answer_relevance": gen.get("answer_relevance", 0),
+ "answer_completeness": gen.get("answer_completeness", 0),
+ "code_correctness": gen.get("code_correctness", 0),
+ }
+ }
+
+ return sft_sample
+
+
+def clean_and_export(
+ input_file: str = "evaluation/sft_data/eval_results.jsonl",
+ config: CleaningConfig = None
+) -> Dict:
+ """
+ 清洗数据并导出
+
+ Returns:
+ 统计信息
+ """
+ config = config or CleaningConfig()
+
+ # 创建输出目录
+ output_dir = Path(config.OUTPUT_DIR)
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ # 统计
+ stats = {
+ "total_read": 0,
+ "passed": 0,
+ "rejected": 0,
+ "rejection_reasons": {},
+ "quality_distribution": {"gold": 0, "silver": 0, "bronze": 0}
+ }
+
+ # 输出文件
+ output_file = output_dir / f"sft_train_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
+ rejected_file = output_dir / f"rejected_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
+
+ print("=" * 60)
+ print("🧹 SFT 数据清洗与导出")
+ print("=" * 60)
+ print(f"输入文件: {input_file}")
+ print(f"输出目录: {output_dir}")
+ print(f"质量阈值: score >= {config.MIN_OVERALL_SCORE}")
+ print()
+
+ if not os.path.exists(input_file):
+ print(f"❌ 输入文件不存在: {input_file}")
+ return stats
+
+ passed_samples = []
+ rejected_samples = []
+
+ # 读取并处理
+ with open(input_file, 'r', encoding='utf-8') as f:
+ for line_num, line in enumerate(f, 1):
+ try:
+ sample = json.loads(line)
+ stats["total_read"] += 1
+
+ # 验证
+ is_valid, reason = validate_sample(sample, config)
+
+ if is_valid:
+ # 转换格式
+ sft_sample = transform_to_sft_format(sample, config)
+ passed_samples.append(sft_sample)
+ stats["passed"] += 1
+
+ # 统计质量分布
+ score = sample.get("overall_score", 0)
+ if score > 0.9:
+ stats["quality_distribution"]["gold"] += 1
+ elif score > 0.7:
+ stats["quality_distribution"]["silver"] += 1
+ else:
+ stats["quality_distribution"]["bronze"] += 1
+ else:
+ rejected_samples.append({
+ "reason": reason,
+ "query": sample.get("query", "")[:50],
+ "score": sample.get("overall_score", 0)
+ })
+ stats["rejected"] += 1
+ stats["rejection_reasons"][reason] = stats["rejection_reasons"].get(reason, 0) + 1
+
+ except json.JSONDecodeError as e:
+ print(f" ⚠️ 第 {line_num} 行 JSON 解析错误: {e}")
+ continue
+
+ # 写入通过的样本
+ if passed_samples:
+ with open(output_file, 'w', encoding='utf-8') as f:
+ for sample in passed_samples:
+ f.write(json.dumps(sample, ensure_ascii=False) + '\n')
+ print(f"✅ 已导出 {len(passed_samples)} 条高质量样本到: {output_file}")
+
+ # 写入拒绝的样本(用于分析)
+ if rejected_samples:
+ with open(rejected_file, 'w', encoding='utf-8') as f:
+ for sample in rejected_samples:
+ f.write(json.dumps(sample, ensure_ascii=False) + '\n')
+ print(f"📝 已记录 {len(rejected_samples)} 条被拒绝样本到: {rejected_file}")
+
+ # 打印统计
+ print()
+ print("=" * 60)
+ print("📊 统计报告")
+ print("=" * 60)
+ print(f"总读取: {stats['total_read']}")
+ print(f"通过: {stats['passed']} ({stats['passed']/max(stats['total_read'],1)*100:.1f}%)")
+ print(f"拒绝: {stats['rejected']} ({stats['rejected']/max(stats['total_read'],1)*100:.1f}%)")
+ print()
+ print("质量分布:")
+ print(f" 🥇 Gold (>0.9): {stats['quality_distribution']['gold']}")
+ print(f" 🥈 Silver (>0.7): {stats['quality_distribution']['silver']}")
+ print(f" 🥉 Bronze (>0.5): {stats['quality_distribution']['bronze']}")
+ print()
+
+ if stats["rejection_reasons"]:
+ print("拒绝原因分布:")
+ for reason, count in sorted(stats["rejection_reasons"].items(), key=lambda x: -x[1]):
+ print(f" - {reason}: {count}")
+
+ print()
+ print("=" * 60)
+
+ return stats
+
+
+def export_for_training(
+ input_file: str,
+ output_file: str,
+ format_type: str = "alpaca"
+) -> int:
+ """
+ 将清洗后的数据导出为特定训练格式
+
+ Args:
+ input_file: 清洗后的 JSONL 文件
+ output_file: 输出文件
+ format_type: 格式类型 (alpaca, sharegpt, messages)
+
+ Returns:
+ 导出的样本数量
+ """
+ samples = []
+
+ with open(input_file, 'r', encoding='utf-8') as f:
+ for line in f:
+ sample = json.loads(line)
+
+ if format_type == "alpaca":
+ # Alpaca 格式(适用于 LLaMA-Factory 等)
+ formatted = {
+ "instruction": sample["instruction"],
+ "input": sample["input"],
+ "output": sample["output"]
+ }
+
+ elif format_type == "sharegpt":
+ # ShareGPT 格式
+ formatted = {
+ "conversations": [
+ {"from": "system", "value": sample["instruction"]},
+ {"from": "human", "value": sample["input"]},
+ {"from": "gpt", "value": sample["output"]}
+ ]
+ }
+
+ elif format_type == "messages":
+ # OpenAI messages 格式
+ formatted = {
+ "messages": [
+ {"role": "system", "content": sample["instruction"]},
+ {"role": "user", "content": sample["input"]},
+ {"role": "assistant", "content": sample["output"]}
+ ]
+ }
+
+ else:
+ formatted = sample
+
+ samples.append(formatted)
+
+ # 写入
+ with open(output_file, 'w', encoding='utf-8') as f:
+ if output_file.endswith('.json'):
+ json.dump(samples, f, ensure_ascii=False, indent=2)
+ else:
+ for sample in samples:
+ f.write(json.dumps(sample, ensure_ascii=False) + '\n')
+
+ print(f"✅ 已导出 {len(samples)} 条样本为 {format_type} 格式: {output_file}")
+ return len(samples)
+
+
+# ============================================================================
+# 主函数
+# ============================================================================
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="SFT 数据清洗与导出工具")
+ parser.add_argument("--input", "-i", default="evaluation/sft_data/eval_results.jsonl",
+ help="输入文件路径")
+ parser.add_argument("--min-score", "-s", type=float, default=0.7,
+ help="最低质量分数 (默认: 0.7)")
+ parser.add_argument("--format", "-f", choices=["alpaca", "sharegpt", "messages"],
+ default="alpaca", help="导出格式 (默认: alpaca)")
+ parser.add_argument("--export", "-e", action="store_true",
+ help="同时导出为训练格式")
+
+ args = parser.parse_args()
+
+ # 配置
+ config = CleaningConfig()
+ config.MIN_OVERALL_SCORE = args.min_score
+
+ # 清洗
+ stats = clean_and_export(args.input, config)
+
+ # 导出为训练格式
+ if args.export and stats["passed"] > 0:
+ # 找到最新的清洗文件
+ output_dir = Path(config.OUTPUT_DIR)
+ cleaned_files = sorted(output_dir.glob("sft_train_*.jsonl"), reverse=True)
+ if cleaned_files:
+ latest_file = cleaned_files[0]
+ export_file = output_dir / f"train_{args.format}.jsonl"
+ export_for_training(str(latest_file), str(export_file), args.format)
diff --git a/evaluation/data_router.py b/evaluation/data_router.py
new file mode 100644
index 0000000000000000000000000000000000000000..c82e91f0f4923780da3cfb43ff53ecedbfb2b47c
--- /dev/null
+++ b/evaluation/data_router.py
@@ -0,0 +1,222 @@
+# 文件路径: evaluation/data_router.py
+"""
+数据路由引擎 - 负责 SFT 数据管理和路由
+
+根据评估结果将样本路由到不同的数据集
+"""
+
+import json
+import os
+from typing import Dict, List, Any
+
+from evaluation.models import EvaluationResult, DataQualityTier
+from evaluation.utils import smart_truncate, SFTLengthConfig
+
+
+class DataRoutingEngine:
+ """评估驱动的数据路由引擎"""
+
+ # SFT 训练提示词
+ SFT_INSTRUCTION = (
+ "你是一个专业的GitHub代码仓库分析助手。根据提供的代码上下文,"
+ "准确回答用户关于代码实现、架构设计、功能逻辑等问题。"
+ "回答时应该:1) 直接引用相关代码 2) 解释代码的工作原理 3) 如有必要,提供代码示例。"
+ )
+
+ def __init__(self, output_dir: str = "evaluation/sft_data"):
+ self.output_dir = output_dir
+ os.makedirs(output_dir, exist_ok=True)
+
+ self.positive_samples_file = os.path.join(output_dir, "positive_samples.jsonl")
+ self.negative_samples_file = os.path.join(output_dir, "negative_samples.jsonl")
+ self.dpo_pairs_file = os.path.join(output_dir, "dpo_pairs.jsonl")
+ self.eval_results_file = os.path.join(output_dir, "eval_results.jsonl")
+
+ def route_sample(self, eval_result: EvaluationResult) -> str:
+ """路由单个样本,返回数据质量等级"""
+ if eval_result.overall_score == 0.0:
+ eval_result.compute_overall_score()
+
+ self.route_data(eval_result)
+ return eval_result.data_quality_tier.value
+
+ def route_data(self, eval_result: EvaluationResult) -> None:
+ """
+ 根据评估结果路由数据
+
+ 路由规则:
+ - score > 0.9 → Gold → positive_samples.jsonl
+ - score > 0.6 → Silver → positive_samples.jsonl
+ - score > 0.4 → Bronze → negative_samples.jsonl
+ - score <= 0.4 → Rejected (不应到达此处,在 auto_eval 中已过滤)
+
+ 注意: eval_results.jsonl 记录所有通过验证的样本,用于分析和审计
+ """
+ # 记录所有评估结果(完整审计日志)
+ self._append_jsonl(self.eval_results_file, eval_result.to_dict())
+
+ # 根据质量分级路由到不同的 SFT 数据文件
+ if eval_result.overall_score > 0.9:
+ # Gold: 高质量正样本
+ sft_sample = self._build_sft_sample(eval_result)
+ self._append_jsonl(self.positive_samples_file, sft_sample)
+
+ elif eval_result.overall_score > 0.6:
+ # Silver: 可用正样本
+ sft_sample = self._build_sft_sample(eval_result)
+ self._append_jsonl(self.positive_samples_file, sft_sample)
+
+ elif eval_result.overall_score > 0.4:
+ # Bronze: 负样本,可用于 DPO 或人工修正
+ sft_sample = self._build_sft_sample(eval_result, negative=True)
+ self._append_jsonl(self.negative_samples_file, sft_sample)
+
+ # <= 0.4: 不写入任何 SFT 文件(已在 auto_eval 中被拒绝)
+
+ def _build_sft_sample(self, eval_result: EvaluationResult, negative: bool = False) -> Dict:
+ """
+ 构建 SFT 训练样本
+
+ 长度限制(基于 SFTLengthConfig):
+ - Context: 最大 2500 字符 (~800 tokens)
+ - Answer: 最大 3000 字符 (~1000 tokens)
+ - 总计: ~2000 tokens,适合 4096 max_length 训练
+ """
+ if eval_result.generation_metrics is None:
+ return {}
+
+ cfg = SFTLengthConfig
+
+ # 1. 截断 Query
+ query = eval_result.query
+ if len(query) > cfg.MAX_QUERY_CHARS:
+ query = query[:cfg.MAX_QUERY_CHARS] + "..."
+
+ # 2. 智能截断 Context(保留开头 70% + 结尾 30%)
+ context = eval_result.generation_metrics.retrieved_context
+ context = smart_truncate(context, cfg.MAX_CONTEXT_CHARS, keep_ratio=0.7)
+
+ # 3. 截断 Answer(保留开头,通常结论在开头)
+ answer = eval_result.generation_metrics.generated_answer
+ if len(answer) > cfg.MAX_ANSWER_CHARS:
+ answer = answer[:cfg.MAX_ANSWER_CHARS] + "\n\n... [回答过长,已截断]"
+
+ # 4. 构建 input 并检查总长度
+ input_text = f"[用户问题]\n{query}\n\n[代码上下文]\n{context}"
+
+ # 如果总长度仍超限,进一步压缩 context
+ total_len = len(self.SFT_INSTRUCTION) + len(input_text) + len(answer)
+ if total_len > cfg.MAX_TOTAL_CHARS:
+ excess = total_len - cfg.MAX_TOTAL_CHARS
+ new_context_len = max(500, len(context) - excess) # 至少保留 500 字符
+ context = smart_truncate(
+ eval_result.generation_metrics.retrieved_context,
+ new_context_len,
+ keep_ratio=0.7
+ )
+ input_text = f"[用户问题]\n{query}\n\n[代码上下文]\n{context}"
+
+ return {
+ "instruction": self.SFT_INSTRUCTION,
+ "input": input_text,
+ "output": answer,
+ "metadata": {
+ "query": eval_result.query[:200], # metadata 中也截断,节省空间
+ "repo_url": eval_result.repo_url,
+ "language": eval_result.language,
+ "session_id": eval_result.session_id,
+ "timestamp": eval_result.timestamp.isoformat(),
+ "quality_tier": eval_result.data_quality_tier.value,
+ "overall_score": eval_result.overall_score,
+ "faithfulness": eval_result.generation_metrics.faithfulness,
+ "answer_relevance": eval_result.generation_metrics.answer_relevance,
+ "answer_completeness": eval_result.generation_metrics.answer_completeness,
+ "code_correctness": eval_result.generation_metrics.code_correctness,
+ "is_negative": negative,
+ "sft_ready": eval_result.sft_ready,
+ # 记录原始长度,便于分析
+ "original_context_len": len(eval_result.generation_metrics.retrieved_context),
+ "original_answer_len": len(eval_result.generation_metrics.generated_answer),
+ "truncated": len(eval_result.generation_metrics.retrieved_context) > cfg.MAX_CONTEXT_CHARS
+ or len(eval_result.generation_metrics.generated_answer) > cfg.MAX_ANSWER_CHARS,
+ }
+ }
+
+ def _append_jsonl(self, filepath: str, data: Dict) -> None:
+ """追加数据到 JSONL 文件"""
+ with open(filepath, 'a', encoding='utf-8') as f:
+ f.write(json.dumps(data, ensure_ascii=False) + '\n')
+
+ def get_statistics(self) -> Dict[str, int]:
+ """获取当前数据统计"""
+ stats = {}
+ for name, filepath in [
+ ("positive", self.positive_samples_file),
+ ("negative", self.negative_samples_file),
+ ("dpo_pairs", self.dpo_pairs_file),
+ ]:
+ if os.path.exists(filepath):
+ with open(filepath, 'r', encoding='utf-8') as f:
+ stats[name] = sum(1 for _ in f)
+ else:
+ stats[name] = 0
+ return stats
+
+ def get_distribution(self) -> Dict[str, int]:
+ """获取评估结果的质量分布"""
+ distribution = {"gold": 0, "silver": 0, "bronze": 0, "rejected": 0, "corrected": 0}
+
+ if not os.path.exists(self.eval_results_file):
+ return distribution
+
+ try:
+ with open(self.eval_results_file, 'r', encoding='utf-8') as f:
+ for line in f:
+ try:
+ result = json.loads(line)
+ tier = result.get("data_quality_tier", "bronze")
+ if tier in distribution:
+ distribution[tier] += 1
+ except json.JSONDecodeError:
+ continue
+ except Exception as e:
+ print(f"⚠️ Error reading eval results: {e}")
+
+ return distribution
+
+ def get_bad_samples(self, limit: int = 10) -> List[Dict[str, Any]]:
+ """获取低质量样本用于人工审核"""
+ bad_samples = []
+
+ if not os.path.exists(self.eval_results_file):
+ return bad_samples
+
+ try:
+ with open(self.eval_results_file, 'r', encoding='utf-8') as f:
+ for line in f:
+ try:
+ result = json.loads(line)
+ if result.get("overall_score", 0) < 0.5:
+ sample = {
+ "query": result.get("query", ""),
+ "score": result.get("overall_score", 0),
+ "issue": result.get("error_message", "Low quality"),
+ "quality_tier": result.get("data_quality_tier", "rejected"),
+ "timestamp": result.get("timestamp", "")
+ }
+ if result.get("generation"):
+ gen = result["generation"]
+ sample.update({
+ "faithfulness": gen.get("faithfulness", 0),
+ "answer_relevance": gen.get("answer_relevance", 0),
+ "answer_completeness": gen.get("answer_completeness", 0),
+ })
+ bad_samples.append(sample)
+ if len(bad_samples) >= limit:
+ break
+ except json.JSONDecodeError:
+ continue
+ except Exception as e:
+ print(f"⚠️ Error reading bad samples: {e}")
+
+ return sorted(bad_samples, key=lambda x: x["score"])[:limit]
diff --git a/evaluation/evaluation_framework.py b/evaluation/evaluation_framework.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4794842987cedf6971fd8a14e251f58428aef4e
--- /dev/null
+++ b/evaluation/evaluation_framework.py
@@ -0,0 +1,512 @@
+# 文件路径: evaluation/evaluation_framework.py
+"""
+GitHub Agent 完整评估框架
+四层评估架构 + 数据路由引擎
+
+Author: Dexter
+Date: 2025-01-27
+
+注意: 数据模型已拆分到 models.py,数据路由已拆分到 data_router.py
+ 此文件保留核心评估引擎逻辑,并重新导出所有符号保持向后兼容
+"""
+
+import json
+import os
+import re
+from typing import List, Dict, Any
+from datetime import datetime
+
+# 重新导出所有模型(保持向后兼容)
+from evaluation.models import (
+ EvaluationLayer,
+ DataQualityTier,
+ QueryRewriteMetrics,
+ RetrievalMetrics,
+ GenerationMetrics,
+ AgenticMetrics,
+ EvaluationResult,
+)
+from evaluation.data_router import DataRoutingEngine
+
+
+# ============================================================================
+# 评估引擎核心逻辑
+# ============================================================================
+
+class EvaluationEngine:
+ """评估引擎 - 负责多层面打分"""
+
+ def __init__(
+ self,
+ llm_client=None,
+ golden_dataset_path: str = "evaluation/golden_dataset.json",
+ model_name: str = None
+ ):
+ self.llm_client = llm_client
+ self.model_name = model_name or "gpt-4o-mini" # 默认使用轻量模型
+ self.golden_dataset = self._load_golden_dataset(golden_dataset_path)
+
+ def _load_golden_dataset(self, path: str) -> List[Dict]:
+ """加载黄金数据集"""
+ if not os.path.exists(path):
+ print(f"⚠️ Golden dataset not found at {path}")
+ return []
+
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+
+ async def evaluate_query_rewrite(
+ self,
+ original_query: str,
+ rewritten_query: str,
+ language_detected: str
+ ) -> QueryRewriteMetrics:
+ """
+ 评估查询重写质量
+
+ 指标:
+ - keyword_coverage: 重写后的关键词是否覆盖了原Query的核心概念?
+ - semantic_preservation: 语义是否保留?
+ - diversity_score: 关键词多样性
+ """
+
+ # 简化版: 使用关键词匹配
+ original_tokens = set(original_query.lower().split())
+ rewritten_tokens = set(rewritten_query.lower().split())
+
+ # 关键词覆盖度: 原Query的关键词有多少在重写中保留
+ if original_tokens:
+ coverage = len(original_tokens & rewritten_tokens) / len(original_tokens)
+ else:
+ coverage = 0.0
+
+ # 多样性: 重写后的关键词数量越多、越不重复,分数越高
+ unique_ratio = len(rewritten_tokens) / max(len(original_tokens), 1)
+ diversity = min(1.0, unique_ratio)
+
+ # 语义保留度 (简化版本: 假设如果覆盖度高就认为语义保留良好)
+ semantic_preservation = min(1.0, coverage + 0.2) # 基础分+覆盖度加分
+
+ return QueryRewriteMetrics(
+ original_query=original_query,
+ rewritten_query=rewritten_query,
+ language_detected=language_detected,
+ keyword_coverage=coverage,
+ semantic_preservation=semantic_preservation,
+ diversity_score=diversity
+ )
+
+ async def evaluate_retrieval(
+ self,
+ query: str,
+ retrieved_files: List[str],
+ ground_truth_files: List[str],
+ top_k: int = 5,
+ retrieval_latency_ms: float = 0,
+ vector_scores: List[float] = None,
+ bm25_scores: List[float] = None
+ ) -> RetrievalMetrics:
+ """
+ 评估检索层质量
+
+ 指标:
+ - hit_rate: 是否找到了任何正确的文件?
+ - recall_at_k: 前K个中有多少是正确的?
+ - precision_at_k: 返回的文件中有多少是正确的?
+ - mrr: 第一个正确结果的排名倒数
+ """
+
+ retrieved_set = set(retrieved_files[:top_k])
+ ground_truth_set = set(ground_truth_files)
+
+ # Hit rate: 是否有交集
+ hit_rate = 1.0 if retrieved_set & ground_truth_set else 0.0
+
+ # Recall@K: 找到的正确结果数 / 正确结果总数
+ correct_count = len(retrieved_set & ground_truth_set)
+ recall = correct_count / len(ground_truth_set) if ground_truth_set else 0.0
+
+ # Precision@K: 找到的正确结果数 / 返回的结果总数
+ precision = correct_count / len(retrieved_set) if retrieved_set else 0.0
+
+ # MRR: 第一个正确结果的倒数排名
+ mrr = 0.0
+ for i, file in enumerate(retrieved_files[:top_k], 1):
+ if file in ground_truth_set:
+ mrr = 1.0 / i
+ break
+
+ # Context Relevance: 简化版 - 假设Precision反映了相关性
+ context_relevance = precision
+
+ # Chunk Integrity: 简化版 - 假设没有太多文件就认为完整度高
+ chunk_integrity = min(1.0, 1.0 / len(retrieved_set)) if retrieved_set else 0.0
+
+ vector_avg = sum(vector_scores) / len(vector_scores) if vector_scores else 0.0
+ bm25_avg = sum(bm25_scores) / len(bm25_scores) if bm25_scores else 0.0
+
+ return RetrievalMetrics(
+ query=query,
+ top_k=top_k,
+ hit_rate=hit_rate,
+ recall_at_k=recall,
+ precision_at_k=precision,
+ mrr=mrr,
+ context_relevance=context_relevance,
+ chunk_integrity=chunk_integrity,
+ retrieval_latency_ms=retrieval_latency_ms,
+ vector_score_avg=vector_avg,
+ bm25_score_avg=bm25_avg,
+ retrieved_files=retrieved_files,
+ ground_truth_files=ground_truth_files
+ )
+
+ async def evaluate_generation(
+ self,
+ query: str,
+ retrieved_context: str,
+ generated_answer: str,
+ ground_truth_answer: str = "",
+ generation_latency_ms: float = 0,
+ token_usage: Dict[str, int] = None
+ ) -> GenerationMetrics:
+ """
+ 评估生成层质量
+
+ 指标:
+ - faithfulness: 回答是否严格基于Context?
+ - answer_relevance: 回答是否回答了问题?
+ - answer_completeness: 回答是否足够完整?
+ - code_correctness: 生成的代码是否正确?
+ """
+
+ # 1. Faithfulness: 使用LLM-as-Judge进行幻觉检测
+ faithfulness = await self._judge_faithfulness(
+ retrieved_context,
+ generated_answer
+ )
+
+ # 2. Answer Relevance: 回答和问题的相似度
+ answer_relevance = await self._judge_answer_relevance(
+ query,
+ generated_answer
+ )
+
+ # 3. Answer Completeness: 简化版 - 通过长度和结构判断
+ completeness = self._judge_completeness(
+ generated_answer,
+ ground_truth_answer
+ )
+
+ # 4. Code Correctness: 使用AST检查代码块
+ code_samples = self._extract_code_blocks(generated_answer)
+ code_correctness = self._check_code_correctness(code_samples)
+
+ metrics = GenerationMetrics(
+ query=query,
+ retrieved_context=retrieved_context,
+ generated_answer=generated_answer,
+ ground_truth_answer=ground_truth_answer,
+ faithfulness=faithfulness,
+ answer_relevance=answer_relevance,
+ answer_completeness=completeness,
+ code_correctness=code_correctness,
+ generated_code_samples=code_samples,
+ generation_latency_ms=generation_latency_ms,
+ token_usage=token_usage or {"input": 0, "output": 0}
+ )
+
+ return metrics
+
+ async def _judge_faithfulness(self, context: str, answer: str) -> float:
+ """
+ LLM-as-Judge: 判断回答是否由Context支撑
+ 返回 0-1 的分数
+
+ 注意:Faithfulness 判断的是"回答中的信息是否能从 Context 中找到依据"
+ 而不是"回答是否完全复制 Context 内容"
+ """
+ if not self.llm_client:
+ # 简化版: 如果没有LLM客户端,使用启发式方法
+ # 统计Answer中的关键词有多少出现在Context中
+ context_lower = context.lower()
+ answer_words = set(answer.lower().split())
+ # 过滤掉常见停用词
+ stop_words = {'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
+ 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
+ 'would', 'could', 'should', 'may', 'might', 'must', 'shall',
+ 'can', 'need', 'dare', 'ought', 'used', 'to', 'of', 'in',
+ 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'that',
+ 'which', 'who', 'whom', 'this', 'these', 'those', 'it', 'its'}
+ meaningful_words = answer_words - stop_words
+ if not meaningful_words:
+ return 0.7 # 没有有意义的词,给默认分
+ # 计算答案中有多少有意义的词出现在Context中
+ found_count = sum(1 for word in meaningful_words if word in context_lower)
+ overlap = found_count / len(meaningful_words)
+ return min(1.0, overlap + 0.2) # 给一定的基础分
+
+ # 智能截取 Context:提取与 Answer 相关的部分
+ # 如果 Context 太长,优先包含 Answer 中提到的关键词附近的内容
+ max_context_len = 6000 # 增加到 6000 字符
+ if len(context) > max_context_len:
+ # 尝试找到 Answer 中提到的关键文件/函数名
+ import re
+ # 提取 Answer 中可能的文件路径或函数名
+ patterns = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*', answer[:500])
+ important_terms = [p for p in patterns if len(p) > 3][:5] # 取前5个重要词
+
+ # 优先截取包含这些词的部分
+ context_parts = []
+ remaining = max_context_len
+ for term in important_terms:
+ idx = context.find(term)
+ if idx != -1 and remaining > 0:
+ start = max(0, idx - 300)
+ end = min(len(context), idx + 700)
+ snippet = context[start:end]
+ if snippet not in ''.join(context_parts):
+ context_parts.append(snippet)
+ remaining -= len(snippet)
+
+ # 如果没找到相关部分,还是用前 6000 字符
+ if context_parts:
+ truncated_context = "\n...\n".join(context_parts)
+ else:
+ truncated_context = context[:max_context_len]
+ else:
+ truncated_context = context
+
+ # 改进的 Prompt:更明确定义 Faithfulness
+ prompt = f"""Evaluate the FAITHFULNESS of the answer to the given context.
+
+FAITHFULNESS means: The claims and information in the answer can be verified from or are consistent with the context.
+- Score HIGH (0.7-1.0) if the answer correctly identifies or explains concepts that ARE in the context
+- Score MEDIUM (0.4-0.7) if the answer is partially supported but makes some unsupported claims
+- Score LOW (0.0-0.4) if the answer contradicts the context or makes completely unsupported claims
+
+NOTE: If the answer says "X is not in the context" and X is indeed not shown, that's a FAITHFUL statement (score 0.7+)
+NOTE: If the answer correctly identifies WHERE something is defined based on imports/references in context, that's FAITHFUL
+
+[Context]
+{truncated_context}
+
+[Answer]
+{answer[:1500]}
+
+SCORE (0.0-1.0):"""
+
+ try:
+ response = await self.llm_client.chat.completions.create(
+ model=self.model_name,
+ messages=[{"role": "user", "content": prompt}],
+ temperature=0.1,
+ max_tokens=10
+ )
+ score_str = response.choices[0].message.content.strip()
+ # 提取数字(处理可能的额外文本)
+ import re
+ match = re.search(r'(\d+\.?\d*)', score_str)
+ if match:
+ score = float(match.group(1))
+ else:
+ score = float(score_str)
+ return min(1.0, max(0.0, score))
+ except Exception as e:
+ print(f"⚠️ Faithfulness judgment failed: {e}")
+ return 0.5
+
+ async def _judge_answer_relevance(self, query: str, answer: str) -> float:
+ """判断回答与问题的相关性"""
+ if not self.llm_client:
+ # 简化版: 使用关键词重叠度
+ query_words = set(query.lower().split())
+ answer_words = set(answer.lower().split())
+ overlap = len(query_words & answer_words) / max(len(query_words), 1)
+ return min(1.0, overlap + 0.3) # 基础分0.3+重叠度
+
+ prompt = f"""
+ Does the answer address the query?
+
+ [Query]
+ {query}
+
+ [Answer]
+ {answer[:1000]}
+
+ Score (0.0-1.0):
+ """
+
+ try:
+ response = await self.llm_client.chat.completions.create(
+ model=self.model_name,
+ messages=[{"role": "user", "content": prompt}],
+ temperature=0.1,
+ max_tokens=10
+ )
+ score = float(response.choices[0].message.content.strip())
+ return min(1.0, max(0.0, score))
+ except:
+ return 0.5
+
+ def _judge_completeness(self, generated_answer: str, ground_truth: str = "") -> float:
+ """判断回答的完整性"""
+ # 简化版: 根据长度和结构
+ if len(generated_answer) < 50:
+ return 0.3
+ elif len(generated_answer) < 200:
+ return 0.6
+ else:
+ return 0.9
+
+ def _extract_code_blocks(self, text: str) -> List[str]:
+ """从文本中提取代码块"""
+ import re
+ code_pattern = r'```[\w]*\n(.*?)\n```'
+ matches = re.findall(code_pattern, text, re.DOTALL)
+ return matches
+
+ def _check_code_correctness(self, code_samples: List[str]) -> float:
+ """检查代码是否有语法错误"""
+ if not code_samples:
+ return 1.0 # 没有代码就认为正确
+
+ import ast
+ correct_count = 0
+ for code in code_samples:
+ try:
+ ast.parse(code)
+ correct_count += 1
+ except SyntaxError:
+ pass
+
+ return correct_count / len(code_samples)
+
+ async def evaluate_agentic(
+ self,
+ query: str,
+ tool_calls: List[Dict[str, Any]],
+ success: bool,
+ steps_taken: int = 0,
+ end_to_end_latency_ms: float = 0
+ ) -> AgenticMetrics:
+ """
+ 评估Agent的决策和行为
+ """
+
+ # Tool Selection Accuracy: 工具选择是否正确?
+ tool_selection_accuracy = 1.0 if success else 0.5
+
+ # Tool Parameter Correctness: 参数是否正确传递?
+ tool_param_correctness = 1.0 if all(
+ tc.get("success", False) for tc in tool_calls
+ ) else 0.5
+
+ # 计算冗余步骤
+ unnecessary_steps = 0
+ backtrack_count = 0
+
+ # 简化版: 如果有重复的工具调用则视为冗余
+ tool_call_signatures = [tc.get("name", "") for tc in tool_calls]
+ for i, sig in enumerate(tool_call_signatures):
+ if i > 0 and sig == tool_call_signatures[i-1]:
+ unnecessary_steps += 1
+
+ return AgenticMetrics(
+ query=query,
+ tool_calls=tool_calls,
+ tool_selection_accuracy=tool_selection_accuracy,
+ tool_parameter_correctness=tool_param_correctness,
+ steps_taken=steps_taken,
+ unnecessary_steps=unnecessary_steps,
+ backtrack_count=backtrack_count,
+ success=success,
+ end_to_end_latency_ms=end_to_end_latency_ms
+ )
+
+ def get_statistics(self) -> Dict[str, Any]:
+ """
+ 获取评估统计信息
+
+ Returns:
+ 包含 total_evaluations, average_score, quality_distribution, top_issues 的字典
+ """
+ # 从 eval_results.jsonl 读取评估结果
+ eval_results_path = "evaluation/sft_data/eval_results.jsonl"
+
+ stats = {
+ "total_evaluations": 0,
+ "average_score": 0.0,
+ "quality_distribution": {
+ "gold": 0,
+ "silver": 0,
+ "bronze": 0,
+ "rejected": 0
+ },
+ "top_issues": []
+ }
+
+ if not os.path.exists(eval_results_path):
+ return stats
+
+ # 读取和分析评估结果
+ scores = []
+ issues = {}
+
+ try:
+ with open(eval_results_path, 'r', encoding='utf-8') as f:
+ for line in f:
+ try:
+ result = json.loads(line)
+ stats["total_evaluations"] += 1
+
+ # 收集得分
+ score = result.get("overall_score", 0)
+ scores.append(score)
+
+ # 统计质量分布
+ tier = result.get("data_quality_tier", "bronze")
+ if tier in stats["quality_distribution"]:
+ stats["quality_distribution"][tier] += 1
+
+ # 收集常见问题 (假设记录在 notes 或 error_message 中)
+ note = result.get("notes", "") or result.get("error_message", "")
+ if note:
+ issues[note] = issues.get(note, 0) + 1
+ except json.JSONDecodeError:
+ continue
+ except Exception as e:
+ print(f"⚠️ Error reading eval results: {e}")
+
+ # 计算平均分
+ if scores:
+ stats["average_score"] = sum(scores) / len(scores)
+
+ # 获取前5个常见问题
+ if issues:
+ stats["top_issues"] = [
+ {"issue": issue, "count": count}
+ for issue, count in sorted(issues.items(), key=lambda x: x[1], reverse=True)[:5]
+ ]
+
+ return stats
+
+
+# ============================================================================
+# __all__ 导出列表(保持向后兼容)
+# ============================================================================
+
+__all__ = [
+ # 枚举
+ "EvaluationLayer",
+ "DataQualityTier",
+ # 数据模型
+ "QueryRewriteMetrics",
+ "RetrievalMetrics",
+ "GenerationMetrics",
+ "AgenticMetrics",
+ "EvaluationResult",
+ # 引擎
+ "EvaluationEngine",
+ "DataRoutingEngine",
+]
diff --git a/evaluation/golden_dataset_builder.py b/evaluation/golden_dataset_builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8251b0a4cdd5c4f6a67c90a6211d297c6660f21
--- /dev/null
+++ b/evaluation/golden_dataset_builder.py
@@ -0,0 +1,414 @@
+# 文件路径: evaluation/golden_dataset_builder.py
+"""
+黄金数据集构建工具
+用于快速构建评估所需的标注数据集
+
+使用场景:
+1. 初始化: 为新项目快速创建 50 条测试用例
+2. 扩展: 定期添加新的问题和标注
+3. 验证: 自动验证数据集的完整性
+
+Author: Dexter
+Date: 2025-01-27
+"""
+
+import json
+import os
+from typing import List, Dict, Optional
+from dataclasses import dataclass, asdict
+from datetime import datetime
+
+
+@dataclass
+class GoldenSample:
+ """黄金数据集样本"""
+ id: str # 唯一ID
+ description: str # 问题描述 (用于标注人员理解问题类型)
+ query: str # 用户查询
+ expected_files: List[str] # 标准答案: 应该返回的文件列表
+ expected_answer: str = "" # 标准答案: 预期回答 (可选)
+ difficulty: str = "medium" # 难度: easy/medium/hard
+ category: str = "general" # 类别: general/code_finding/architecture/workflow
+ language: str = "en" # 语言: en/zh
+ created_at: str = ""
+
+ def __post_init__(self):
+ if not self.created_at:
+ self.created_at = datetime.now().isoformat()
+
+
+class GoldenDatasetBuilder:
+ """黄金数据集构建器"""
+
+ def __init__(self, filepath: str = "evaluation/golden_dataset.json"):
+ self.filepath = filepath
+ self.samples: List[GoldenSample] = []
+ self.load()
+
+ def load(self):
+ """加载现有数据集"""
+ if os.path.exists(self.filepath):
+ with open(self.filepath, 'r', encoding='utf-8') as f:
+ try:
+ raw_data = json.load(f)
+ # 兼容旧格式 (直接是字典列表)
+ if isinstance(raw_data, list):
+ self.samples = [
+ GoldenSample(**item) if isinstance(item, dict) and "id" in item
+ else GoldenSample(
+ id=str(len(self.samples)),
+ description=item.get("description", ""),
+ query=item.get("query", ""),
+ expected_files=[item.get("answer_file", "")] if item.get("answer_file") else []
+ )
+ for item in raw_data
+ ]
+ except:
+ self.samples = []
+
+ def save(self):
+ """保存数据集"""
+ os.makedirs(os.path.dirname(self.filepath), exist_ok=True)
+ data = [asdict(s) for s in self.samples]
+ with open(self.filepath, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+
+ def add_sample(self, sample: GoldenSample):
+ """添加样本"""
+ sample.id = f"sample_{len(self.samples):04d}"
+ self.samples.append(sample)
+
+ def add_samples_batch(self, samples: List[GoldenSample]):
+ """批量添加样本"""
+ for sample in samples:
+ self.add_sample(sample)
+
+ def get_samples_by_category(self, category: str) -> List[GoldenSample]:
+ """按类别筛选"""
+ return [s for s in self.samples if s.category == category]
+
+ def get_samples_by_difficulty(self, difficulty: str) -> List[GoldenSample]:
+ """按难度筛选"""
+ return [s for s in self.samples if s.difficulty == difficulty]
+
+ def get_statistics(self) -> Dict:
+ """获取统计信息"""
+ stats = {
+ "total": len(self.samples),
+ "by_category": {},
+ "by_difficulty": {},
+ "by_language": {}
+ }
+
+ for s in self.samples:
+ stats["by_category"][s.category] = stats["by_category"].get(s.category, 0) + 1
+ stats["by_difficulty"][s.difficulty] = stats["by_difficulty"].get(s.difficulty, 0) + 1
+ stats["by_language"][s.language] = stats["by_language"].get(s.language, 0) + 1
+
+ return stats
+
+
+# ============================================================================
+# 预定义的通用问题模板
+# ============================================================================
+
+# 针对 FastAPI 项目的初始数据集 (参考你现有的 golden_dataset.json)
+FASTAPI_GOLDEN_SAMPLES = [
+ # Easy: 代码位置查找
+ GoldenSample(
+ id="",
+ description="简单函数查找",
+ query="Where is the 'serialize_response' function?",
+ expected_files=["fastapi/routing.py"],
+ difficulty="easy",
+ category="code_finding"
+ ),
+
+ # Medium: 理解数据流
+ GoldenSample(
+ id="",
+ description="理解核心模块职责",
+ query="How does dependency injection work in FastAPI?",
+ expected_files=["fastapi/dependencies/utils.py", "fastapi/depends.py"],
+ difficulty="medium",
+ category="architecture"
+ ),
+
+ # Hard: 跨文件理解工作流
+ GoldenSample(
+ id="",
+ description="完整工作流理解",
+ query="Show me the complete flow from request to response in FastAPI",
+ expected_files=["fastapi/routing.py", "fastapi/applications.py", "fastapi/dependencies/utils.py"],
+ difficulty="hard",
+ category="workflow"
+ ),
+]
+
+# GitHub Agent 项目的初始数据集
+GITHUB_AGENT_GOLDEN_SAMPLES = [
+ GoldenSample(
+ id="",
+ description="检索核心逻辑",
+ query="How is chunk_file method implemented?",
+ expected_files=["app/services/chunking_service.py"],
+ expected_answer="The chunk_file method is implemented in chunking_service.py. It takes content and file_path as parameters and uses AST parsing for Python files to intelligently chunk the code.",
+ difficulty="easy",
+ category="code_finding",
+ language="en"
+ ),
+
+ GoldenSample(
+ id="",
+ description="向量搜索机制",
+ query="What vector database is used for retrieval?",
+ expected_files=["app/services/vector_service.py"],
+ difficulty="medium",
+ category="architecture",
+ language="en"
+ ),
+
+ GoldenSample(
+ id="",
+ description="完整分析流程",
+ query="How does the agent analyze a GitHub repository?",
+ expected_files=["app/services/agent_service.py", "app/services/chunking_service.py", "app/services/vector_service.py"],
+ difficulty="hard",
+ category="workflow",
+ language="en"
+ ),
+]
+
+
+# ============================================================================
+# 交互式数据集构建工具
+# ============================================================================
+
+def interactive_builder():
+ """交互式构建黄金数据集"""
+ builder = GoldenDatasetBuilder()
+
+ print("=" * 60)
+ print("🛠️ 黄金数据集构建工具")
+ print("=" * 60)
+
+ while True:
+ print("\n请选择操作:")
+ print("1. 添加新样本")
+ print("2. 查看现有样本")
+ print("3. 按类别筛选")
+ print("4. 统计信息")
+ print("5. 保存并退出")
+ print("0. 退出(不保存)")
+
+ choice = input("请输入选项 (0-5): ").strip()
+
+ if choice == "1":
+ sample = GoldenSample(
+ id="",
+ description=input("📝 描述 (问题类型): "),
+ query=input("❓ 查询/问题: "),
+ expected_files=input("📁 预期文件 (逗号分隔): ").split(","),
+ expected_answer=input("📄 标准答案 (可选): "),
+ difficulty=input("⭐ 难度 (easy/medium/hard) [medium]: ") or "medium",
+ category=input("🏷️ 类别 (code_finding/architecture/workflow/general) [general]: ") or "general",
+ language=input("🌍 语言 (en/zh) [en]: ") or "en"
+ )
+ builder.add_sample(sample)
+ print("✅ 样本已添加")
+
+ elif choice == "2":
+ print(f"\n总共 {len(builder.samples)} 个样本:")
+ for s in builder.samples[-10:]: # 显示最后10个
+ print(f" - [{s.difficulty}] {s.query[:50]}")
+
+ elif choice == "3":
+ category = input("输入类别: ")
+ samples = builder.get_samples_by_category(category)
+ print(f"\n找到 {len(samples)} 个 '{category}' 类别的样本:")
+ for s in samples:
+ print(f" - {s.query}")
+
+ elif choice == "4":
+ stats = builder.get_statistics()
+ print(f"\n📊 数据集统计:")
+ print(f" 总样本数: {stats['total']}")
+ print(f" 按类别: {stats['by_category']}")
+ print(f" 按难度: {stats['by_difficulty']}")
+ print(f" 按语言: {stats['by_language']}")
+
+ elif choice == "5":
+ builder.save()
+ print("✅ 数据集已保存")
+ break
+
+ elif choice == "0":
+ print("⚠️ 未保存,退出")
+ break
+
+
+# ============================================================================
+# 自动评估数据集的完整性
+# ============================================================================
+
+def validate_golden_dataset(filepath: str = "evaluation/golden_dataset.json") -> Dict:
+ """验证黄金数据集的完整性"""
+
+ builder = GoldenDatasetBuilder(filepath)
+ issues = {
+ "missing_fields": [],
+ "empty_queries": [],
+ "empty_files": [],
+ "duplicates": []
+ }
+
+ seen_queries = set()
+
+ for i, sample in enumerate(builder.samples):
+ # 检查必填字段
+ if not sample.query:
+ issues["empty_queries"].append(f"Sample {i}: query is empty")
+
+ if not sample.expected_files or all(not f for f in sample.expected_files):
+ issues["empty_files"].append(f"Sample {i}: expected_files is empty")
+
+ # 检查重复
+ if sample.query in seen_queries:
+ issues["duplicates"].append(f"Sample {i}: duplicate query")
+ seen_queries.add(sample.query)
+
+ return {
+ "valid": len(issues) == 0 or not any(issues.values()),
+ "total_samples": len(builder.samples),
+ "issues": issues,
+ "stats": builder.get_statistics()
+ }
+
+
+# ============================================================================
+# 快速初始化脚本
+# ============================================================================
+
+def init_github_agent_dataset():
+ """快速初始化 GitHub Agent 项目的数据集"""
+ builder = GoldenDatasetBuilder("evaluation/golden_dataset.json")
+
+ # 清空现有 (可选)
+ # builder.samples = []
+
+ # 添加初始样本
+ builder.add_samples_batch(GITHUB_AGENT_GOLDEN_SAMPLES)
+
+ # 额外添加更多样本 (扩展到30+)
+ extra_samples = [
+ GoldenSample(
+ id="",
+ description="向量检索质量",
+ query="What retrieval metrics are tracked?",
+ expected_files=["evaluation/evaluation_framework.py"],
+ difficulty="medium",
+ category="architecture"
+ ),
+ GoldenSample(
+ id="",
+ description="Agent决策过程",
+ query="How does the agent decide which files to read?",
+ expected_files=["app/services/agent_service.py"],
+ difficulty="hard",
+ category="workflow"
+ ),
+ GoldenSample(
+ id="",
+ description="错误处理",
+ query="Where are network timeout errors handled?",
+ expected_files=["app/services/agent_service.py", "app/services/chat_service.py"],
+ difficulty="medium",
+ category="code_finding"
+ ),
+ ]
+ builder.add_samples_batch(extra_samples)
+ builder.save()
+
+ print(f"✅ 初始化完成: {len(builder.samples)} 个样本")
+ print(f"📊 {builder.get_statistics()}")
+
+
+# ============================================================================
+# 导出为 Ragas 格式
+# ============================================================================
+
+def export_to_ragas_format(golden_filepath: str, output_filepath: str = "evaluation/ragas_eval_dataset.json"):
+ """
+ 将黄金数据集导出为 Ragas 评估框架所需的格式
+
+ Ragas 格式:
+ {
+ "questions": [...],
+ "contexts": [...],
+ "ground_truths": [...]
+ }
+ """
+ builder = GoldenDatasetBuilder(golden_filepath)
+
+ ragas_data = {
+ "questions": [],
+ "contexts": [],
+ "ground_truths": [],
+ "metadata": []
+ }
+
+ for sample in builder.samples:
+ ragas_data["questions"].append(sample.query)
+ ragas_data["ground_truths"].append({
+ "answer": sample.expected_answer,
+ "files": sample.expected_files
+ })
+ ragas_data["contexts"].append("\n".join(sample.expected_files))
+ ragas_data["metadata"].append({
+ "difficulty": sample.difficulty,
+ "category": sample.category,
+ "description": sample.description
+ })
+
+ os.makedirs(os.path.dirname(output_filepath), exist_ok=True)
+ with open(output_filepath, 'w', encoding='utf-8') as f:
+ json.dump(ragas_data, f, ensure_ascii=False, indent=2)
+
+ print(f"✅ Exported to {output_filepath}")
+ print(f" Questions: {len(ragas_data['questions'])}")
+
+
+# ============================================================================
+# 命令行接口
+# ============================================================================
+
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) > 1:
+ command = sys.argv[1]
+
+ if command == "init":
+ init_github_agent_dataset()
+
+ elif command == "validate":
+ result = validate_golden_dataset()
+ print(json.dumps(result, indent=2, ensure_ascii=False))
+
+ elif command == "export-ragas":
+ export_to_ragas_format("evaluation/golden_dataset.json")
+
+ elif command == "interactive":
+ interactive_builder()
+
+ else:
+ print(f"Unknown command: {command}")
+
+ else:
+ print("黄金数据集构建工具")
+ print()
+ print("用法:")
+ print(" python golden_dataset_builder.py init # 快速初始化")
+ print(" python golden_dataset_builder.py validate # 验证数据集")
+ print(" python golden_dataset_builder.py export-ragas # 导出为Ragas格式")
+ print(" python golden_dataset_builder.py interactive # 交互式构建")
diff --git a/evaluation/models.py b/evaluation/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d3242b3635ea6632f2102ea13a9f61ea87b71c2
--- /dev/null
+++ b/evaluation/models.py
@@ -0,0 +1,244 @@
+# 文件路径: evaluation/models.py
+"""
+评估数据模型定义
+
+将所有数据类和枚举集中管理,保持代码职责清晰
+"""
+
+from dataclasses import dataclass, field, asdict
+from typing import List, Dict, Optional, Any
+from enum import Enum
+from datetime import datetime
+
+
+class EvaluationLayer(Enum):
+ """评估层次分类"""
+ QUERY_REWRITE = "query_rewrite"
+ RETRIEVAL = "retrieval"
+ GENERATION = "generation"
+ AGENTIC = "agentic"
+
+
+class DataQualityTier(Enum):
+ """数据质量分级 (用于SFT数据路由)"""
+ GOLD = "gold" # 完美样本 (score > 0.9)
+ SILVER = "silver" # 优质样本 (score 0.7-0.9)
+ BRONZE = "bronze" # 可用样本 (score 0.5-0.7)
+ REJECTED = "rejected" # 拒绝 (score < 0.5)
+ CORRECTED = "corrected" # 自纠正后的样本 (用于DPO)
+
+
+# ============================================================================
+# 各层评估指标
+# ============================================================================
+
+@dataclass
+class QueryRewriteMetrics:
+ """查询重写评估指标"""
+ original_query: str
+ rewritten_query: str
+ language_detected: str
+ keyword_coverage: float # 0-1
+ semantic_preservation: float # 0-1
+ diversity_score: float # 0-1
+
+ def overall_score(self) -> float:
+ return (
+ self.keyword_coverage * 0.4 +
+ self.semantic_preservation * 0.4 +
+ self.diversity_score * 0.2
+ )
+
+
+@dataclass
+class RetrievalMetrics:
+ """检索层评估指标"""
+ query: str
+ top_k: int
+
+ # 核心指标
+ hit_rate: float
+ recall_at_k: float
+ precision_at_k: float
+ mrr: float # Mean Reciprocal Rank
+
+ # 高级指标
+ context_relevance: float
+ chunk_integrity: float
+ retrieval_latency_ms: float
+
+ # 混合检索
+ vector_score_avg: float
+ bm25_score_avg: float
+
+ retrieved_files: List[str] = field(default_factory=list)
+ ground_truth_files: List[str] = field(default_factory=list)
+
+ def overall_score(self) -> float:
+ return (
+ self.recall_at_k * 0.3 +
+ self.precision_at_k * 0.3 +
+ self.context_relevance * 0.25 +
+ self.chunk_integrity * 0.15
+ )
+
+
+@dataclass
+class GenerationMetrics:
+ """生成层评估指标"""
+ query: str
+ retrieved_context: str
+ generated_answer: str
+
+ # 核心指标
+ faithfulness: float
+ answer_relevance: float
+ answer_completeness: float
+ code_correctness: float
+
+ # 可选
+ ground_truth_answer: str = ""
+ hallucination_count: int = 0
+ unsupported_claims: List[str] = field(default_factory=list)
+ generated_code_samples: List[str] = field(default_factory=list)
+ generation_latency_ms: float = 0
+ token_usage: Dict[str, int] = field(default_factory=lambda: {"input": 0, "output": 0})
+
+ def overall_score(self) -> float:
+ base_score = (
+ self.faithfulness * 0.35 +
+ self.answer_relevance * 0.35 +
+ self.answer_completeness * 0.2 +
+ self.code_correctness * 0.1
+ )
+ penalty = self.hallucination_count * 0.1
+ return max(0, base_score - penalty)
+
+
+@dataclass
+class AgenticMetrics:
+ """Agent行为评估指标"""
+ query: str
+ tool_selection_accuracy: float
+ tool_parameter_correctness: float
+
+ tool_calls: List[Dict[str, Any]] = field(default_factory=list)
+ steps_taken: int = 0
+ unnecessary_steps: int = 0
+ backtrack_count: int = 0
+ success: bool = True
+ early_termination: bool = False
+ end_to_end_latency_ms: float = 0
+
+ def efficiency_score(self) -> float:
+ if self.steps_taken == 0:
+ return 0
+ redundancy_ratio = self.unnecessary_steps / self.steps_taken
+ return max(0, min(1, 1 - redundancy_ratio - self.backtrack_count * 0.1))
+
+ def overall_score(self) -> float:
+ return (
+ self.tool_selection_accuracy * 0.4 +
+ self.tool_parameter_correctness * 0.3 +
+ self.efficiency_score() * 0.2 +
+ (1.0 if self.success else 0.0) * 0.1
+ )
+
+
+# ============================================================================
+# 综合评估结果
+# ============================================================================
+
+@dataclass
+class EvaluationResult:
+ """单次评估完整结果"""
+ session_id: str
+ query: str
+ repo_url: str
+ timestamp: datetime
+ language: str = "en"
+
+ # 各层评估结果
+ query_rewrite_metrics: Optional[QueryRewriteMetrics] = None
+ retrieval_metrics: Optional[RetrievalMetrics] = None
+ generation_metrics: Optional[GenerationMetrics] = None
+ agentic_metrics: Optional[AgenticMetrics] = None
+
+ # 综合评分
+ overall_score: float = 0.0
+ data_quality_tier: DataQualityTier = DataQualityTier.BRONZE
+
+ # SFT标注
+ sft_ready: bool = False
+ dpo_candidate: bool = False
+
+ # 元数据
+ error_message: Optional[str] = None
+ notes: str = ""
+
+ def compute_overall_score(self) -> float:
+ """计算加权综合得分"""
+ scores, weights = [], []
+
+ if self.query_rewrite_metrics:
+ scores.append(self.query_rewrite_metrics.overall_score())
+ weights.append(0.15)
+
+ if self.retrieval_metrics:
+ scores.append(self.retrieval_metrics.overall_score())
+ weights.append(0.35)
+
+ if self.generation_metrics:
+ scores.append(self.generation_metrics.overall_score())
+ weights.append(0.4)
+
+ if self.agentic_metrics:
+ scores.append(self.agentic_metrics.overall_score())
+ weights.append(0.1)
+
+ if not scores:
+ return 0.0
+
+ total_weight = sum(weights)
+ self.overall_score = sum(s * w for s, w in zip(scores, weights)) / total_weight
+
+ # 分级
+ if self.overall_score > 0.9:
+ self.data_quality_tier = DataQualityTier.GOLD
+ self.sft_ready = True
+ elif self.overall_score > 0.7:
+ self.data_quality_tier = DataQualityTier.SILVER
+ self.sft_ready = True
+ elif self.overall_score > 0.5:
+ self.data_quality_tier = DataQualityTier.BRONZE
+ else:
+ self.data_quality_tier = DataQualityTier.REJECTED
+
+ return self.overall_score
+
+ def to_dict(self) -> Dict:
+ """转换为字典供存储"""
+ result = {
+ "session_id": self.session_id,
+ "query": self.query,
+ "repo_url": self.repo_url,
+ "timestamp": self.timestamp.isoformat(),
+ "language": self.language,
+ "overall_score": self.overall_score,
+ "data_quality_tier": self.data_quality_tier.value,
+ "sft_ready": self.sft_ready,
+ "dpo_candidate": self.dpo_candidate,
+ "error_message": self.error_message,
+ "notes": self.notes,
+ }
+
+ if self.query_rewrite_metrics:
+ result["query_rewrite"] = asdict(self.query_rewrite_metrics)
+ if self.retrieval_metrics:
+ result["retrieval"] = asdict(self.retrieval_metrics)
+ if self.generation_metrics:
+ result["generation"] = asdict(self.generation_metrics)
+ if self.agentic_metrics:
+ result["agentic"] = asdict(self.agentic_metrics)
+
+ return result
diff --git a/evaluation/test_retrieval.py b/evaluation/test_retrieval.py
new file mode 100644
index 0000000000000000000000000000000000000000..24629ecfa0edf8461c8a23bac0f816c5a7e6144c
--- /dev/null
+++ b/evaluation/test_retrieval.py
@@ -0,0 +1,330 @@
+#!/usr/bin/env python3
+"""
+检索系统离线评估脚本
+
+用于测试 chunking 和检索策略的准确率。
+使用 golden_dataset.json 中的标注数据作为 ground truth。
+
+使用方法:
+ python evaluation/test_retrieval.py --repo https://github.com/tiangolo/fastapi
+ python evaluation/test_retrieval.py --repo https://github.com/tiangolo/fastapi --top-k 5
+ python evaluation/test_retrieval.py --repo https://github.com/tiangolo/fastapi --verbose
+
+Author: Dexter
+Date: 2026-01-28
+"""
+
+import json
+import os
+import sys
+import asyncio
+import argparse
+from typing import List, Dict, Tuple
+from dataclasses import dataclass, field
+from datetime import datetime
+
+# 添加项目根目录到 path
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from app.services.vector_service import store_manager
+from app.services.github_service import get_repo_structure
+
+
+@dataclass
+class RetrievalTestResult:
+ """单个测试用例的结果"""
+ query: str
+ expected_files: List[str]
+ retrieved_files: List[str]
+ hit: bool # 是否命中任意一个预期文件
+ recall: float # 召回率: 命中的预期文件 / 总预期文件
+ precision: float # 精确率: 命中的预期文件 / 检索结果数
+ reciprocal_rank: float # 倒数排名: 1 / 第一个命中的位置
+ difficulty: str = ""
+ category: str = ""
+
+
+@dataclass
+class EvaluationReport:
+ """完整评估报告"""
+ repo_url: str
+ top_k: int
+ total_queries: int
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
+
+ # 聚合指标
+ hit_rate: float = 0.0 # 命中率: 至少命中一个的查询比例
+ mean_recall: float = 0.0 # 平均召回率
+ mean_precision: float = 0.0 # 平均精确率
+ mrr: float = 0.0 # Mean Reciprocal Rank
+
+ # 按难度分组
+ by_difficulty: Dict[str, Dict] = field(default_factory=dict)
+
+ # 详细结果
+ results: List[RetrievalTestResult] = field(default_factory=list)
+ failed_cases: List[Dict] = field(default_factory=list)
+
+
+class RetrievalEvaluator:
+ """检索系统评估器"""
+
+ def __init__(self, golden_dataset_path: str = "evaluation/golden_dataset.json"):
+ self.golden_dataset = self._load_golden_dataset(golden_dataset_path)
+ print(f"📊 Loaded {len(self.golden_dataset)} test cases from golden dataset")
+
+ def _load_golden_dataset(self, path: str) -> List[Dict]:
+ """加载黄金数据集"""
+ if not os.path.exists(path):
+ raise FileNotFoundError(f"Golden dataset not found: {path}")
+
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+
+ async def evaluate(
+ self,
+ repo_url: str,
+ session_id: str = "eval_test",
+ top_k: int = 5,
+ verbose: bool = False
+ ) -> EvaluationReport:
+ """
+ 运行完整的检索评估
+
+ Args:
+ repo_url: 要评估的仓库 URL
+ session_id: 会话 ID
+ top_k: 每次检索返回的文件数
+ verbose: 是否打印详细信息
+ """
+ print(f"\n{'='*60}")
+ print(f"🔍 Retrieval Evaluation")
+ print(f"{'='*60}")
+ print(f"Repository: {repo_url}")
+ print(f"Top-K: {top_k}")
+ print(f"Test Cases: {len(self.golden_dataset)}")
+ print(f"{'='*60}\n")
+
+ # 获取仓库文件列表
+ print("📂 Fetching repository structure...")
+ file_list = get_repo_structure(repo_url) # 同步函数,不需要 await
+ print(f" Found {len(file_list)} files")
+
+ # 获取向量存储
+ store = store_manager.get_store(session_id)
+ chunk_count = store.collection.count() # 使用 collection.count()
+ if chunk_count == 0:
+ print("\n⚠️ Vector store is empty!")
+ print(" Please run the agent first to index the repository.")
+ print(" Example: Access http://localhost:8000 and analyze the repo first.")
+ return None
+ print(f" Vector store has {chunk_count} chunks")
+
+ # 运行评估
+ report = EvaluationReport(
+ repo_url=repo_url,
+ top_k=top_k,
+ total_queries=len(self.golden_dataset)
+ )
+
+ hits = 0
+ recalls = []
+ precisions = []
+ reciprocal_ranks = []
+
+ difficulty_stats = {}
+
+ for i, sample in enumerate(self.golden_dataset):
+ query = sample.get("query", "")
+ expected_files = sample.get("expected_files", [])
+ difficulty = sample.get("difficulty", "medium")
+ category = sample.get("category", "general")
+
+ if not query or not expected_files:
+ continue
+
+ # 执行检索 (使用 hybrid search)
+ try:
+ results = await store.search_hybrid(query, top_k=top_k)
+ except Exception as e:
+ if verbose:
+ print(f" [ERR] Search failed: {e}")
+ continue
+
+ # 提取检索到的文件路径
+ retrieved_files = []
+ for doc in results:
+ if isinstance(doc, dict):
+ file_path = doc.get("file", "")
+ if file_path and file_path not in retrieved_files:
+ retrieved_files.append(file_path)
+
+ # 计算指标
+ expected_set = set(expected_files)
+ retrieved_set = set(retrieved_files[:top_k])
+
+ # 命中的文件
+ hits_set = expected_set & retrieved_set
+
+ # Hit: 是否命中任意一个
+ hit = len(hits_set) > 0
+ if hit:
+ hits += 1
+
+ # Recall: 命中的 / 期望的
+ recall = len(hits_set) / len(expected_set) if expected_set else 0
+ recalls.append(recall)
+
+ # Precision: 命中的 / 检索的
+ precision = len(hits_set) / min(len(retrieved_files), top_k) if retrieved_files else 0
+ precisions.append(precision)
+
+ # Reciprocal Rank: 1 / 第一个命中的位置
+ rr = 0.0
+ for rank, file in enumerate(retrieved_files[:top_k], 1):
+ if file in expected_set:
+ rr = 1.0 / rank
+ break
+ reciprocal_ranks.append(rr)
+
+ # 记录结果
+ result = RetrievalTestResult(
+ query=query,
+ expected_files=expected_files,
+ retrieved_files=retrieved_files[:top_k],
+ hit=hit,
+ recall=recall,
+ precision=precision,
+ reciprocal_rank=rr,
+ difficulty=difficulty,
+ category=category
+ )
+ report.results.append(result)
+
+ # 按难度统计
+ if difficulty not in difficulty_stats:
+ difficulty_stats[difficulty] = {"hits": 0, "total": 0, "recalls": [], "precisions": []}
+ difficulty_stats[difficulty]["total"] += 1
+ if hit:
+ difficulty_stats[difficulty]["hits"] += 1
+ difficulty_stats[difficulty]["recalls"].append(recall)
+ difficulty_stats[difficulty]["precisions"].append(precision)
+
+ # 记录失败案例
+ if not hit:
+ report.failed_cases.append({
+ "query": query,
+ "expected": expected_files,
+ "retrieved": retrieved_files[:top_k],
+ "difficulty": difficulty
+ })
+
+ # 打印进度
+ if verbose:
+ status = "✅" if hit else "❌"
+ print(f" [{i+1:3d}] {status} Recall={recall:.2f} | {query[:50]}...")
+ else:
+ print(f"\r Progress: {i+1}/{len(self.golden_dataset)}", end="")
+
+ print("\n")
+
+ # 计算聚合指标
+ report.hit_rate = hits / len(self.golden_dataset) if self.golden_dataset else 0
+ report.mean_recall = sum(recalls) / len(recalls) if recalls else 0
+ report.mean_precision = sum(precisions) / len(precisions) if precisions else 0
+ report.mrr = sum(reciprocal_ranks) / len(reciprocal_ranks) if reciprocal_ranks else 0
+
+ # 按难度汇总
+ for diff, stats in difficulty_stats.items():
+ report.by_difficulty[diff] = {
+ "hit_rate": stats["hits"] / stats["total"] if stats["total"] else 0,
+ "mean_recall": sum(stats["recalls"]) / len(stats["recalls"]) if stats["recalls"] else 0,
+ "mean_precision": sum(stats["precisions"]) / len(stats["precisions"]) if stats["precisions"] else 0,
+ "total": stats["total"]
+ }
+
+ return report
+
+ def print_report(self, report: EvaluationReport):
+ """打印评估报告"""
+ print(f"\n{'='*60}")
+ print(f"📊 RETRIEVAL EVALUATION REPORT")
+ print(f"{'='*60}")
+ print(f"Repository: {report.repo_url}")
+ print(f"Top-K: {report.top_k}")
+ print(f"Total Queries: {report.total_queries}")
+ print(f"Timestamp: {report.timestamp}")
+ print(f"{'='*60}\n")
+
+ print("📈 OVERALL METRICS")
+ print(f" Hit Rate: {report.hit_rate:.1%}")
+ print(f" Mean Recall: {report.mean_recall:.1%}")
+ print(f" Mean Precision: {report.mean_precision:.1%}")
+ print(f" MRR: {report.mrr:.3f}")
+
+ print(f"\n📊 BY DIFFICULTY")
+ for diff, stats in sorted(report.by_difficulty.items()):
+ print(f" {diff.upper():8s} | Hit: {stats['hit_rate']:.1%} | Recall: {stats['mean_recall']:.1%} | n={stats['total']}")
+
+ if report.failed_cases:
+ print(f"\n❌ FAILED CASES ({len(report.failed_cases)} total)")
+ for case in report.failed_cases[:5]: # 只显示前5个
+ print(f" Query: {case['query'][:60]}...")
+ print(f" Expected: {case['expected']}")
+ print(f" Got: {case['retrieved'][:3]}...")
+ print()
+
+ print(f"{'='*60}")
+
+ def save_report(self, report: EvaluationReport, output_path: str = "evaluation/retrieval_report.json"):
+ """保存报告到文件"""
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
+
+ # 转换为可序列化格式
+ data = {
+ "repo_url": report.repo_url,
+ "top_k": report.top_k,
+ "total_queries": report.total_queries,
+ "timestamp": report.timestamp,
+ "metrics": {
+ "hit_rate": report.hit_rate,
+ "mean_recall": report.mean_recall,
+ "mean_precision": report.mean_precision,
+ "mrr": report.mrr
+ },
+ "by_difficulty": report.by_difficulty,
+ "failed_cases": report.failed_cases
+ }
+
+ with open(output_path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+
+ print(f"\n💾 Report saved to: {output_path}")
+
+
+async def main():
+ parser = argparse.ArgumentParser(description="Evaluate retrieval system using golden dataset")
+ parser.add_argument("--repo", required=True, help="GitHub repository URL to evaluate")
+ parser.add_argument("--top-k", type=int, default=5, help="Number of results to retrieve (default: 5)")
+ parser.add_argument("--session", default="eval_test", help="Session ID for vector store")
+ parser.add_argument("--verbose", "-v", action="store_true", help="Print detailed results")
+ parser.add_argument("--save", action="store_true", help="Save report to file")
+
+ args = parser.parse_args()
+
+ evaluator = RetrievalEvaluator()
+ report = await evaluator.evaluate(
+ repo_url=args.repo,
+ session_id=args.session,
+ top_k=args.top_k,
+ verbose=args.verbose
+ )
+
+ if report:
+ evaluator.print_report(report)
+ if args.save:
+ evaluator.save_report(report)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/evaluation/utils.py b/evaluation/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9914280a4cf976d3792357408062de4a6d8da505
--- /dev/null
+++ b/evaluation/utils.py
@@ -0,0 +1,196 @@
+# 文件路径: evaluation/utils.py
+"""
+评估模块公共工具函数和常量
+
+将重复的逻辑抽取到这里,保持代码 DRY (Don't Repeat Yourself)
+"""
+
+from typing import List
+
+
+# ============================================================================
+# 闲聊/无效 Query 检测
+# ============================================================================
+
+CHATTY_PATTERNS: List[str] = [
+ # 中文闲聊
+ "你好", "您好", "嗨", "在吗", "在不在", "谢谢", "多谢", "再见", "拜拜",
+ "什么是", "你是谁", "你叫什么", "帮帮我", "教教我",
+ # 英文闲聊
+ "hello", "hi", "hey", "thanks", "thank you", "bye", "goodbye",
+ "what is", "who are you", "help me", "can you",
+ # 单词/简短
+ "test", "测试", "ok", "yes", "no",
+]
+
+# 代码语言指示符
+CODE_INDICATORS: List[str] = [
+ # Python
+ "def ", "class ", "import ", "from ",
+ # JavaScript/TypeScript
+ "function ", "const ", "let ", "var ",
+ # Java/C#
+ "public ", "private ", "void ",
+ # Go
+ "func ", "package ",
+ # 通用
+ "```", # Markdown 代码块
+]
+
+
+def is_chatty_query(query: str, min_length: int = 5) -> bool:
+ """
+ 检测是否为闲聊/无效 query
+
+ Args:
+ query: 用户查询
+ min_length: 最小有效长度,低于此值视为无效
+
+ Returns:
+ True 如果是闲聊/无效查询
+ """
+ if not query:
+ return True
+
+ query_lower = query.lower().strip()
+
+ # 长度检查
+ if len(query_lower) < min_length:
+ return True
+
+ # 模式匹配
+ for pattern in CHATTY_PATTERNS:
+ if query_lower == pattern or query_lower.startswith(pattern + " "):
+ return True
+
+ return False
+
+
+def has_code_indicators(text: str) -> bool:
+ """
+ 检查文本是否包含代码指示符
+
+ Args:
+ text: 要检查的文本
+
+ Returns:
+ True 如果包含代码特征
+ """
+ if not text:
+ return False
+
+ for indicator in CODE_INDICATORS:
+ if indicator in text:
+ return True
+
+ return False
+
+
+# ============================================================================
+# 文件操作工具
+# ============================================================================
+
+def append_jsonl(filepath: str, data: dict) -> None:
+ """
+ 追加一行 JSON 到 JSONL 文件
+
+ Args:
+ filepath: 文件路径
+ data: 要追加的数据字典
+ """
+ import json
+ with open(filepath, 'a', encoding='utf-8') as f:
+ f.write(json.dumps(data, ensure_ascii=False) + '\n')
+
+
+def read_jsonl(filepath: str) -> list:
+ """
+ 读取 JSONL 文件
+
+ Args:
+ filepath: 文件路径
+
+ Returns:
+ 数据列表
+ """
+ import json
+ import os
+
+ if not os.path.exists(filepath):
+ return []
+
+ results = []
+ with open(filepath, 'r', encoding='utf-8') as f:
+ for line in f:
+ try:
+ results.append(json.loads(line))
+ except json.JSONDecodeError:
+ continue
+ return results
+
+
+def safe_truncate(text: str, max_length: int, suffix: str = "\n... [truncated]") -> str:
+ """
+ 安全截断文本
+
+ Args:
+ text: 原始文本
+ max_length: 最大长度
+ suffix: 截断后缀
+
+ Returns:
+ 截断后的文本
+ """
+ if not text or len(text) <= max_length:
+ return text
+ return text[:max_length] + suffix
+
+
+def smart_truncate(text: str, max_length: int, keep_ratio: float = 0.7) -> str:
+ """
+ 智能截断:保留开头大部分 + 结尾小部分,适合代码上下文
+
+ Args:
+ text: 原始文本
+ max_length: 最大长度
+ keep_ratio: 开头保留比例(默认 70% 开头,30% 结尾)
+
+ Returns:
+ 截断后的文本,保留首尾关键内容
+ """
+ if not text or len(text) <= max_length:
+ return text
+
+ separator = "\n\n... [中间内容已省略] ...\n\n"
+ available = max_length - len(separator)
+
+ if available <= 0:
+ return text[:max_length]
+
+ head_len = int(available * keep_ratio)
+ tail_len = available - head_len
+
+ return text[:head_len] + separator + text[-tail_len:]
+
+
+# ============================================================================
+# SFT 数据长度配置
+# ============================================================================
+
+class SFTLengthConfig:
+ """SFT 训练数据长度配置"""
+
+ # Context 限制(检索到的代码上下文)
+ MAX_CONTEXT_CHARS = 2500 # 最大字符数 (~800 tokens)
+
+ # Answer 限制(模型生成的回答)
+ MAX_ANSWER_CHARS = 3000 # 最大字符数 (~1000 tokens)
+
+ # Query 限制
+ MAX_QUERY_CHARS = 500 # 最大字符数
+
+ # 总体限制
+ MAX_TOTAL_CHARS = 6000 # 总字符数上限 (~2000 tokens)
+
+ # Token 估算(中英文混合,保守估计)
+ CHARS_PER_TOKEN = 3 # 平均每 token 的字符数
diff --git a/frontend-dist/assets/Tableau10-B-NsZVaP.js b/frontend-dist/assets/Tableau10-B-NsZVaP.js
new file mode 100644
index 0000000000000000000000000000000000000000..4223ec34a3bdc31019c4e2b5589b485c14449370
--- /dev/null
+++ b/frontend-dist/assets/Tableau10-B-NsZVaP.js
@@ -0,0 +1 @@
+function o(e){for(var c=e.length/6|0,n=new Array(c),a=0;au*u+V*V&&(F=P,G=x),{cx:F,cy:G,x01:-n,y01:-m,x11:F*(v/T-1),y11:G*(v/T-1)}}function vn(){var l=cn,h=yn,z=I(0),E=null,v=gn,A=mn,O=pn,a=null,B=ln(i);function i(){var n,m,r=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-rn,c=A.apply(this,arguments)-rn,S=un(c-f),o=c>f;if(a||(a=n=B()),sy))a.moveTo(0,0);else if(S>on-y)a.moveTo(s*j(f),s*D(f)),a.arc(0,0,s,f,c,!o),r>y&&(a.moveTo(r*j(c),r*D(c)),a.arc(0,0,r,c,f,o));else{var p=f,g=c,R=f,T=c,w=S,C=S,F=O.apply(this,arguments)/2,G=F>y&&(E?+E.apply(this,arguments):K(r*r+s*s)),P=_(un(s-r)/2,+z.apply(this,arguments)),x=P,d=P,e,u;if(G>y){var V=sn(G/r*D(F)),L=sn(G/s*D(F));(w-=V*2)>y?(V*=o?1:-1,R+=V,T-=V):(w=0,R=T=(f+c)/2),(C-=L*2)>y?(L*=o?1:-1,p+=L,g-=L):(C=0,p=g=(f+c)/2)}var H=s*j(p),J=s*D(p),M=r*j(T),N=r*D(T);if(P>y){var Q=s*j(g),U=s*D(g),X=r*j(R),Y=r*D(R),q;if(Sy?d>y?(e=W(X,Y,H,J,s,d,o),u=W(Q,U,M,N,s,d,o),a.moveTo(e.cx+e.x01,e.cy+e.y01),dy)||!(w>y)?a.lineTo(M,N):x>y?(e=W(M,N,Q,U,r,-x,o),u=W(H,J,X,Y,r,-x,o),a.lineTo(e.cx+e.x01,e.cy+e.y01),x
"u"&&(w.yylloc={});var J=w.yylloc;t.push(J);var me=w.options&&w.options.ranges;typeof K.yy.parseError=="function"?this.parseError=K.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function _e(){var P;return P=u.pop()||w.lex()||C,typeof P!="number"&&(P instanceof Array&&(u=P,P=u.pop()),P=s.symbols_[P]||P),P}for(var I,M,z,Q,W={},X,B,ae,G;;){if(M=i[i.length-1],this.defaultActions[M]?z=this.defaultActions[M]:((I===null||typeof I>"u")&&(I=_e()),z=m[M]&&m[M][I]),typeof z>"u"||!z.length||!z[0]){var $="";G=[];for(X in m[M])this.terminals_[X]&&X>F&&G.push("'"+this.terminals_[X]+"'");w.showPosition?$="Parse error on line "+(R+1)+`:
+`+w.showPosition()+`
+Expecting `+G.join(", ")+", got '"+(this.terminals_[I]||I)+"'":$="Parse error on line "+(R+1)+": Unexpected "+(I==C?"end of input":"'"+(this.terminals_[I]||I)+"'"),this.parseError($,{text:w.match,token:this.terminals_[I]||I,line:w.yylineno,loc:J,expected:G})}if(z[0]instanceof Array&&z.length>1)throw new Error("Parse Error: multiple actions possible at state: "+M+", token: "+I);switch(z[0]){case 1:i.push(I),h.push(w.yytext),t.push(w.yylloc),i.push(z[1]),I=null,Y=w.yyleng,r=w.yytext,R=w.yylineno,J=w.yylloc;break;case 2:if(B=this.productions_[z[1]][1],W.$=h[h.length-B],W._$={first_line:t[t.length-(B||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(B||1)].first_column,last_column:t[t.length-1].last_column},me&&(W._$.range=[t[t.length-(B||1)].range[0],t[t.length-1].range[1]]),Q=this.performAction.apply(W,[r,Y,R,K.yy,z[1],h,t].concat(Le)),typeof Q<"u")return Q;B&&(i=i.slice(0,-1*B*2),h=h.slice(0,-1*B),t=t.slice(0,-1*B)),i.push(this.productions_[z[1]][0]),h.push(W.$),t.push(W._$),ae=m[i[i.length-2]][i[i.length-1]],i.push(ae);break;case 3:return!0}}return!0}},A=function(){var D={EOF:1,parseError:function(s,i){if(this.yy.parser)this.yy.parser.parseError(s,i);else throw new Error(s)},setInput:function(o,s){return this.yy=s||this.yy||{},this._input=o,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var o=this._input[0];this.yytext+=o,this.yyleng++,this.offset++,this.match+=o,this.matched+=o;var s=o.match(/(?:\r\n?|\n).*/g);return s?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),o},unput:function(o){var s=o.length,i=o.split(/(?:\r\n?|\n)/g);this._input=o+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-s),this.offset-=s;var u=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var h=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===u.length?this.yylloc.first_column:0)+u[u.length-i.length].length-i[0].length:this.yylloc.first_column-s},this.options.ranges&&(this.yylloc.range=[h[0],h[0]+this.yyleng-s]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
+`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},less:function(o){this.unput(this.match.slice(o))},pastInput:function(){var o=this.matched.substr(0,this.matched.length-this.match.length);return(o.length>20?"...":"")+o.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var o=this.match;return o.length<20&&(o+=this._input.substr(0,20-o.length)),(o.substr(0,20)+(o.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var o=this.pastInput(),s=new Array(o.length+1).join("-");return o+this.upcomingInput()+`
+`+s+"^"},test_match:function(o,s){var i,u,h;if(this.options.backtrack_lexer&&(h={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(h.yylloc.range=this.yylloc.range.slice(0))),u=o[0].match(/(?:\r\n?|\n).*/g),u&&(this.yylineno+=u.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:u?u[u.length-1].length-u[u.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+o[0].length},this.yytext+=o[0],this.match+=o[0],this.matches=o,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(o[0].length),this.matched+=o[0],i=this.performAction.call(this,this.yy,this,s,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var t in h)this[t]=h[t];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var o,s,i,u;this._more||(this.yytext="",this.match="");for(var h=this._currentRules(),t=0;ts[0].length)){if(s=i,u=t,this.options.backtrack_lexer){if(o=this.test_match(i,h[t]),o!==!1)return o;if(this._backtrack){s=!1;continue}else return!1}else if(!this.options.flex)break}return s?(o=this.test_match(s,h[u]),o!==!1?o:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text.
+`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var s=this.next();return s||this.lex()},begin:function(s){this.conditionStack.push(s)},popState:function(){var s=this.conditionStack.length-1;return s>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(s){return s=this.conditionStack.length-1-Math.abs(s||0),s>=0?this.conditionStack[s]:"INITIAL"},pushState:function(s){this.begin(s)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(s,i,u,h){switch(u){case 0:return 10;case 1:return s.getLogger().debug("Found space-block"),31;case 2:return s.getLogger().debug("Found nl-block"),31;case 3:return s.getLogger().debug("Found space-block"),29;case 4:s.getLogger().debug(".",i.yytext);break;case 5:s.getLogger().debug("_",i.yytext);break;case 6:return 5;case 7:return i.yytext=-1,28;case 8:return i.yytext=i.yytext.replace(/columns\s+/,""),s.getLogger().debug("COLUMNS (LEX)",i.yytext),28;case 9:this.pushState("md_string");break;case 10:return"MD_STR";case 11:this.popState();break;case 12:this.pushState("string");break;case 13:s.getLogger().debug("LEX: POPPING STR:",i.yytext),this.popState();break;case 14:return s.getLogger().debug("LEX: STR end:",i.yytext),"STR";case 15:return i.yytext=i.yytext.replace(/space\:/,""),s.getLogger().debug("SPACE NUM (LEX)",i.yytext),21;case 16:return i.yytext="1",s.getLogger().debug("COLUMNS (LEX)",i.yytext),21;case 17:return 43;case 18:return"LINKSTYLE";case 19:return"INTERPOLATE";case 20:return this.pushState("CLASSDEF"),40;case 21:return this.popState(),this.pushState("CLASSDEFID"),"DEFAULT_CLASSDEF_ID";case 22:return this.popState(),this.pushState("CLASSDEFID"),41;case 23:return this.popState(),42;case 24:return this.pushState("CLASS"),44;case 25:return this.popState(),this.pushState("CLASS_STYLE"),45;case 26:return this.popState(),46;case 27:return this.pushState("STYLE_STMNT"),47;case 28:return this.popState(),this.pushState("STYLE_DEFINITION"),48;case 29:return this.popState(),49;case 30:return this.pushState("acc_title"),"acc_title";case 31:return this.popState(),"acc_title_value";case 32:return this.pushState("acc_descr"),"acc_descr";case 33:return this.popState(),"acc_descr_value";case 34:this.pushState("acc_descr_multiline");break;case 35:this.popState();break;case 36:return"acc_descr_multiline_value";case 37:return 30;case 38:return this.popState(),s.getLogger().debug("Lex: (("),"NODE_DEND";case 39:return this.popState(),s.getLogger().debug("Lex: (("),"NODE_DEND";case 40:return this.popState(),s.getLogger().debug("Lex: ))"),"NODE_DEND";case 41:return this.popState(),s.getLogger().debug("Lex: (("),"NODE_DEND";case 42:return this.popState(),s.getLogger().debug("Lex: (("),"NODE_DEND";case 43:return this.popState(),s.getLogger().debug("Lex: (-"),"NODE_DEND";case 44:return this.popState(),s.getLogger().debug("Lex: -)"),"NODE_DEND";case 45:return this.popState(),s.getLogger().debug("Lex: (("),"NODE_DEND";case 46:return this.popState(),s.getLogger().debug("Lex: ]]"),"NODE_DEND";case 47:return this.popState(),s.getLogger().debug("Lex: ("),"NODE_DEND";case 48:return this.popState(),s.getLogger().debug("Lex: ])"),"NODE_DEND";case 49:return this.popState(),s.getLogger().debug("Lex: /]"),"NODE_DEND";case 50:return this.popState(),s.getLogger().debug("Lex: /]"),"NODE_DEND";case 51:return this.popState(),s.getLogger().debug("Lex: )]"),"NODE_DEND";case 52:return this.popState(),s.getLogger().debug("Lex: )"),"NODE_DEND";case 53:return this.popState(),s.getLogger().debug("Lex: ]>"),"NODE_DEND";case 54:return this.popState(),s.getLogger().debug("Lex: ]"),"NODE_DEND";case 55:return s.getLogger().debug("Lexa: -)"),this.pushState("NODE"),36;case 56:return s.getLogger().debug("Lexa: (-"),this.pushState("NODE"),36;case 57:return s.getLogger().debug("Lexa: ))"),this.pushState("NODE"),36;case 58:return s.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;case 59:return s.getLogger().debug("Lex: ((("),this.pushState("NODE"),36;case 60:return s.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;case 61:return s.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;case 62:return s.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;case 63:return s.getLogger().debug("Lexc: >"),this.pushState("NODE"),36;case 64:return s.getLogger().debug("Lexa: (["),this.pushState("NODE"),36;case 65:return s.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;case 66:return this.pushState("NODE"),36;case 67:return this.pushState("NODE"),36;case 68:return this.pushState("NODE"),36;case 69:return this.pushState("NODE"),36;case 70:return this.pushState("NODE"),36;case 71:return this.pushState("NODE"),36;case 72:return this.pushState("NODE"),36;case 73:return s.getLogger().debug("Lexa: ["),this.pushState("NODE"),36;case 74:return this.pushState("BLOCK_ARROW"),s.getLogger().debug("LEX ARR START"),38;case 75:return s.getLogger().debug("Lex: NODE_ID",i.yytext),32;case 76:return s.getLogger().debug("Lex: EOF",i.yytext),8;case 77:this.pushState("md_string");break;case 78:this.pushState("md_string");break;case 79:return"NODE_DESCR";case 80:this.popState();break;case 81:s.getLogger().debug("Lex: Starting string"),this.pushState("string");break;case 82:s.getLogger().debug("LEX ARR: Starting string"),this.pushState("string");break;case 83:return s.getLogger().debug("LEX: NODE_DESCR:",i.yytext),"NODE_DESCR";case 84:s.getLogger().debug("LEX POPPING"),this.popState();break;case 85:s.getLogger().debug("Lex: =>BAE"),this.pushState("ARROW_DIR");break;case 86:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (right): dir:",i.yytext),"DIR";case 87:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (left):",i.yytext),"DIR";case 88:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (x):",i.yytext),"DIR";case 89:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (y):",i.yytext),"DIR";case 90:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (up):",i.yytext),"DIR";case 91:return i.yytext=i.yytext.replace(/^,\s*/,""),s.getLogger().debug("Lex (down):",i.yytext),"DIR";case 92:return i.yytext="]>",s.getLogger().debug("Lex (ARROW_DIR end):",i.yytext),this.popState(),this.popState(),"BLOCK_ARROW_END";case 93:return s.getLogger().debug("Lex: LINK","#"+i.yytext+"#"),15;case 94:return s.getLogger().debug("Lex: LINK",i.yytext),15;case 95:return s.getLogger().debug("Lex: LINK",i.yytext),15;case 96:return s.getLogger().debug("Lex: LINK",i.yytext),15;case 97:return s.getLogger().debug("Lex: START_LINK",i.yytext),this.pushState("LLABEL"),16;case 98:return s.getLogger().debug("Lex: START_LINK",i.yytext),this.pushState("LLABEL"),16;case 99:return s.getLogger().debug("Lex: START_LINK",i.yytext),this.pushState("LLABEL"),16;case 100:this.pushState("md_string");break;case 101:return s.getLogger().debug("Lex: Starting string"),this.pushState("string"),"LINK_LABEL";case 102:return this.popState(),s.getLogger().debug("Lex: LINK","#"+i.yytext+"#"),15;case 103:return this.popState(),s.getLogger().debug("Lex: LINK",i.yytext),15;case 104:return this.popState(),s.getLogger().debug("Lex: LINK",i.yytext),15;case 105:return s.getLogger().debug("Lex: COLON",i.yytext),i.yytext=i.yytext.slice(1),27}},rules:[/^(?:block-beta\b)/,/^(?:block\s+)/,/^(?:block\n+)/,/^(?:block:)/,/^(?:[\s]+)/,/^(?:[\n]+)/,/^(?:((\u000D\u000A)|(\u000A)))/,/^(?:columns\s+auto\b)/,/^(?:columns\s+[\d]+)/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:space[:]\d+)/,/^(?:space\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\s+)/,/^(?:DEFAULT\s+)/,/^(?:\w+\s+)/,/^(?:[^\n]*)/,/^(?:class\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:style\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:end\b\s*)/,/^(?:\(\(\()/,/^(?:\)\)\))/,/^(?:[\)]\))/,/^(?:\}\})/,/^(?:\})/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\()/,/^(?:\]\])/,/^(?:\()/,/^(?:\]\))/,/^(?:\\\])/,/^(?:\/\])/,/^(?:\)\])/,/^(?:[\)])/,/^(?:\]>)/,/^(?:[\]])/,/^(?:-\))/,/^(?:\(-)/,/^(?:\)\))/,/^(?:\))/,/^(?:\(\(\()/,/^(?:\(\()/,/^(?:\{\{)/,/^(?:\{)/,/^(?:>)/,/^(?:\(\[)/,/^(?:\()/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\[\\)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:\[)/,/^(?:<\[)/,/^(?:[^\(\[\n\-\)\{\}\s\<\>:]+)/,/^(?:$)/,/^(?:["][`])/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:\]>\s*\()/,/^(?:,?\s*right\s*)/,/^(?:,?\s*left\s*)/,/^(?:,?\s*x\s*)/,/^(?:,?\s*y\s*)/,/^(?:,?\s*up\s*)/,/^(?:,?\s*down\s*)/,/^(?:\)\s*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*~~[\~]+\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:["][`])/,/^(?:["])/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?::\d+)/],conditions:{STYLE_DEFINITION:{rules:[29],inclusive:!1},STYLE_STMNT:{rules:[28],inclusive:!1},CLASSDEFID:{rules:[23],inclusive:!1},CLASSDEF:{rules:[21,22],inclusive:!1},CLASS_STYLE:{rules:[26],inclusive:!1},CLASS:{rules:[25],inclusive:!1},LLABEL:{rules:[100,101,102,103,104],inclusive:!1},ARROW_DIR:{rules:[86,87,88,89,90,91,92],inclusive:!1},BLOCK_ARROW:{rules:[77,82,85],inclusive:!1},NODE:{rules:[38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,78,81],inclusive:!1},md_string:{rules:[10,11,79,80],inclusive:!1},space:{rules:[],inclusive:!1},string:{rules:[13,14,83,84],inclusive:!1},acc_descr_multiline:{rules:[35,36],inclusive:!1},acc_descr:{rules:[33],inclusive:!1},acc_title:{rules:[31],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,12,15,16,17,18,19,20,24,27,30,32,34,37,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,93,94,95,96,97,98,99,105],inclusive:!0}}};return D}();L.lexer=A;function k(){this.yy={}}return k.prototype=L,L.Parser=k,new k}();ee.parser=ee;const Pe=ee;let O={},ie=[],V={};const ce="color",ue="fill",Fe="bgFill",pe=",",Ke=he();let j={};const Me=e=>De.sanitizeText(e,Ke),Ye=function(e,a=""){j[e]===void 0&&(j[e]={id:e,styles:[],textStyles:[]});const d=j[e];a!=null&&a.split(pe).forEach(c=>{const n=c.replace(/([^;]*);/,"$1").trim();if(c.match(ce)){const l=n.replace(ue,Fe).replace(ce,ue);d.textStyles.push(l)}d.styles.push(n)})},We=function(e,a=""){const d=O[e];a!=null&&(d.styles=a.split(pe))},Ve=function(e,a){e.split(",").forEach(function(d){let c=O[d];if(c===void 0){const n=d.trim();O[n]={id:n,type:"na",children:[]},c=O[n]}c.classes||(c.classes=[]),c.classes.push(a)})},fe=(e,a)=>{const d=e.flat(),c=[];for(const n of d){if(n.label&&(n.label=Me(n.label)),n.type==="classDef"){Ye(n.id,n.css);continue}if(n.type==="applyClass"){Ve(n.id,(n==null?void 0:n.styleClass)||"");continue}if(n.type==="applyStyles"){n!=null&&n.stylesStr&&We(n.id,n==null?void 0:n.stylesStr);continue}if(n.type==="column-setting")a.columns=n.columns||-1;else if(n.type==="edge")V[n.id]?V[n.id]++:V[n.id]=1,n.id=V[n.id]+"-"+n.id,ie.push(n);else{n.label||(n.type==="composite"?n.label="":n.label=n.id);const g=!O[n.id];if(g?O[n.id]=n:(n.type!=="na"&&(O[n.id].type=n.type),n.label!==n.id&&(O[n.id].label=n.label)),n.children&&fe(n.children,n),n.type==="space"){const l=n.width||1;for(let f=0;f{S.debug("Clear called"),Ee(),U={id:"root",type:"composite",children:[],columns:-1},O={root:U},re=[],j={},ie=[],V={}};function Ue(e){switch(S.debug("typeStr2Type",e),e){case"[]":return"square";case"()":return S.debug("we have a round"),"round";case"(())":return"circle";case">]":return"rect_left_inv_arrow";case"{}":return"diamond";case"{{}}":return"hexagon";case"([])":return"stadium";case"[[]]":return"subroutine";case"[()]":return"cylinder";case"((()))":return"doublecircle";case"[//]":return"lean_right";case"[\\\\]":return"lean_left";case"[/\\]":return"trapezoid";case"[\\/]":return"inv_trapezoid";case"<[]>":return"block_arrow";default:return"na"}}function Xe(e){switch(S.debug("typeStr2Type",e),e){case"==":return"thick";default:return"normal"}}function Ge(e){switch(e.trim()){case"--x":return"arrow_cross";case"--o":return"arrow_circle";default:return"arrow_point"}}let de=0;const He=()=>(de++,"id-"+Math.random().toString(36).substr(2,12)+"-"+de),qe=e=>{U.children=e,fe(e,U),re=U.children},Ze=e=>{const a=O[e];return a?a.columns?a.columns:a.children?a.children.length:-1:-1},Je=()=>[...Object.values(O)],Qe=()=>re||[],$e=()=>ie,et=e=>O[e],tt=e=>{O[e.id]=e},st=()=>console,it=function(){return j},rt={getConfig:()=>se().block,typeStr2Type:Ue,edgeTypeStr2Type:Xe,edgeStrToEdgeData:Ge,getLogger:st,getBlocksFlat:Je,getBlocks:Qe,getEdges:$e,setHierarchy:qe,getBlock:et,setBlock:tt,getColumns:Ze,getClasses:it,clear:je,generateId:He},nt=rt,q=(e,a)=>{const d=Re,c=d(e,"r"),n=d(e,"g"),g=d(e,"b");return we(c,n,g,a)},at=e=>`.label {
+ font-family: ${e.fontFamily};
+ color: ${e.nodeTextColor||e.textColor};
+ }
+ .cluster-label text {
+ fill: ${e.titleColor};
+ }
+ .cluster-label span,p {
+ color: ${e.titleColor};
+ }
+
+
+
+ .label text,span,p {
+ fill: ${e.nodeTextColor||e.textColor};
+ color: ${e.nodeTextColor||e.textColor};
+ }
+
+ .node rect,
+ .node circle,
+ .node ellipse,
+ .node polygon,
+ .node path {
+ fill: ${e.mainBkg};
+ stroke: ${e.nodeBorder};
+ stroke-width: 1px;
+ }
+ .flowchart-label text {
+ text-anchor: middle;
+ }
+ // .flowchart-label .text-outer-tspan {
+ // text-anchor: middle;
+ // }
+ // .flowchart-label .text-inner-tspan {
+ // text-anchor: start;
+ // }
+
+ .node .label {
+ text-align: center;
+ }
+ .node.clickable {
+ cursor: pointer;
+ }
+
+ .arrowheadPath {
+ fill: ${e.arrowheadColor};
+ }
+
+ .edgePath .path {
+ stroke: ${e.lineColor};
+ stroke-width: 2.0px;
+ }
+
+ .flowchart-link {
+ stroke: ${e.lineColor};
+ fill: none;
+ }
+
+ .edgeLabel {
+ background-color: ${e.edgeLabelBackground};
+ rect {
+ opacity: 0.5;
+ background-color: ${e.edgeLabelBackground};
+ fill: ${e.edgeLabelBackground};
+ }
+ text-align: center;
+ }
+
+ /* For html labels only */
+ .labelBkg {
+ background-color: ${q(e.edgeLabelBackground,.5)};
+ // background-color:
+ }
+
+ .node .cluster {
+ // fill: ${q(e.mainBkg,.5)};
+ fill: ${q(e.clusterBkg,.5)};
+ stroke: ${q(e.clusterBorder,.2)};
+ box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
+ stroke-width: 1px;
+ }
+
+ .cluster text {
+ fill: ${e.titleColor};
+ }
+
+ .cluster span,p {
+ color: ${e.titleColor};
+ }
+ /* .cluster div {
+ color: ${e.titleColor};
+ } */
+
+ div.mermaidTooltip {
+ position: absolute;
+ text-align: center;
+ max-width: 200px;
+ padding: 2px;
+ font-family: ${e.fontFamily};
+ font-size: 12px;
+ background: ${e.tertiaryColor};
+ border: 1px solid ${e.border2};
+ border-radius: 2px;
+ pointer-events: none;
+ z-index: 100;
+ }
+
+ .flowchartTitleText {
+ text-anchor: middle;
+ font-size: 18px;
+ fill: ${e.textColor};
+ }
+`,lt=at;function be(e,a,d=!1){var c,n,g;const l=e;let f="default";(((c=l==null?void 0:l.classes)==null?void 0:c.length)||0)>0&&(f=((l==null?void 0:l.classes)||[]).join(" ")),f=f+" flowchart-label";let b=0,p="",x;switch(l.type){case"round":b=5,p="rect";break;case"composite":b=0,p="composite",x=0;break;case"square":p="rect";break;case"diamond":p="question";break;case"hexagon":p="hexagon";break;case"block_arrow":p="block_arrow";break;case"odd":p="rect_left_inv_arrow";break;case"lean_right":p="lean_right";break;case"lean_left":p="lean_left";break;case"trapezoid":p="trapezoid";break;case"inv_trapezoid":p="inv_trapezoid";break;case"rect_left_inv_arrow":p="rect_left_inv_arrow";break;case"circle":p="circle";break;case"ellipse":p="ellipse";break;case"stadium":p="stadium";break;case"subroutine":p="subroutine";break;case"cylinder":p="cylinder";break;case"group":p="rect";break;case"doublecircle":p="doublecircle";break;default:p="rect"}const y=ve((l==null?void 0:l.styles)||[]),T=l.label,v=l.size||{width:0,height:0,x:0,y:0};return{labelStyle:y.labelStyle,shape:p,labelText:T,rx:b,ry:b,class:f,style:y.style,id:l.id,directions:l.directions,width:v.width,height:v.height,x:v.x,y:v.y,positioned:d,intersect:void 0,type:l.type,padding:x??(((g=(n=se())==null?void 0:n.block)==null?void 0:g.padding)||0)}}async function ot(e,a,d){const c=be(a,d,!1);if(c.type==="group")return;const n=await ge(e,c),g=n.node().getBBox(),l=d.getBlock(c.id);l.size={width:g.width,height:g.height,x:0,y:0,node:n},d.setBlock(l),n.remove()}async function ct(e,a,d){const c=be(a,d,!0);d.getBlock(c.id).type!=="space"&&(await ge(e,c),a.intersect=c==null?void 0:c.intersect,ze(c))}async function ne(e,a,d,c){for(const n of a)await c(e,n,d),n.children&&await ne(e,n.children,d,c)}async function ut(e,a,d){await ne(e,a,d,ot)}async function dt(e,a,d){await ne(e,a,d,ct)}async function ht(e,a,d,c,n){const g=new Ce({multigraph:!0,compound:!0});g.setGraph({rankdir:"TB",nodesep:10,ranksep:10,marginx:8,marginy:8});for(const l of d)l.size&&g.setNode(l.id,{width:l.size.width,height:l.size.height,intersect:l.intersect});for(const l of a)if(l.start&&l.end){const f=c.getBlock(l.start),b=c.getBlock(l.end);if(f!=null&&f.size&&(b!=null&&b.size)){const p=f.size,x=b.size,y=[{x:p.x,y:p.y},{x:p.x+(x.x-p.x)/2,y:p.y+(x.y-p.y)/2},{x:x.x,y:x.y}];await Ie(e,{v:l.start,w:l.end,name:l.id},{...l,arrowTypeEnd:l.arrowTypeEnd,arrowTypeStart:l.arrowTypeStart,points:y,classes:"edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1"},void 0,"block",g,n),l.label&&(await Oe(e,{...l,label:l.label,labelStyle:"stroke: #333; stroke-width: 1.5px;fill:none;",arrowTypeEnd:l.arrowTypeEnd,arrowTypeStart:l.arrowTypeStart}),await Te({...l,x:y[1].x,y:y[1].y},{originalPath:y}))}}}const _=((oe=(le=he())==null?void 0:le.block)==null?void 0:oe.padding)||8;function gt(e,a){if(e===0||!Number.isInteger(e))throw new Error("Columns must be an integer !== 0.");if(a<0||!Number.isInteger(a))throw new Error("Position must be a non-negative integer."+a);if(e<0)return{px:a,py:0};if(e===1)return{px:0,py:a};const d=a%e,c=Math.floor(a/e);return{px:d,py:c}}const pt=e=>{let a=0,d=0;for(const c of e.children){const{width:n,height:g,x:l,y:f}=c.size||{width:0,height:0,x:0,y:0};S.debug("getMaxChildSize abc95 child:",c.id,"width:",n,"height:",g,"x:",l,"y:",f,c.type),c.type!=="space"&&(n>a&&(a=n/(e.widthInColumns||1)),g>d&&(d=g))}return{width:a,height:d}};function te(e,a,d=0,c=0){var n,g,l,f,b,p,x,y,T,v,N;S.debug("setBlockSizes abc95 (start)",e.id,(n=e==null?void 0:e.size)==null?void 0:n.x,"block width =",e==null?void 0:e.size,"sieblingWidth",d),(g=e==null?void 0:e.size)!=null&&g.width||(e.size={width:d,height:c,x:0,y:0});let E=0,L=0;if(((l=e.children)==null?void 0:l.length)>0){for(const h of e.children)te(h,a);const A=pt(e);E=A.width,L=A.height,S.debug("setBlockSizes abc95 maxWidth of",e.id,":s children is ",E,L);for(const h of e.children)h.size&&(S.debug(`abc95 Setting size of children of ${e.id} id=${h.id} ${E} ${L} ${h.size}`),h.size.width=E*(h.widthInColumns||1)+_*((h.widthInColumns||1)-1),h.size.height=L,h.size.x=0,h.size.y=0,S.debug(`abc95 updating size of ${e.id} children child:${h.id} maxWidth:${E} maxHeight:${L}`));for(const h of e.children)te(h,a,E,L);const k=e.columns||-1;let D=0;for(const h of e.children)D+=h.widthInColumns||1;let o=e.children.length;k>0&&k0?Math.min(e.children.length,k):e.children.length;if(h>0){const t=(i-h*_-_)/h;S.debug("abc95 (growing to fit) width",e.id,i,(x=e.size)==null?void 0:x.width,t);for(const m of e.children)m.size&&(m.size.width=t)}}e.size={width:i,height:u,x:0,y:0}}S.debug("setBlockSizes abc94 (done)",e.id,(y=e==null?void 0:e.size)==null?void 0:y.x,(T=e==null?void 0:e.size)==null?void 0:T.width,(v=e==null?void 0:e.size)==null?void 0:v.y,(N=e==null?void 0:e.size)==null?void 0:N.height)}function xe(e,a){var d,c,n,g,l,f,b,p,x,y,T,v,N,E,L,A,k;S.debug(`abc85 layout blocks (=>layoutBlocks) ${e.id} x: ${(d=e==null?void 0:e.size)==null?void 0:d.x} y: ${(c=e==null?void 0:e.size)==null?void 0:c.y} width: ${(n=e==null?void 0:e.size)==null?void 0:n.width}`);const D=e.columns||-1;if(S.debug("layoutBlocks columns abc95",e.id,"=>",D,e),e.children&&e.children.length>0){const o=((l=(g=e==null?void 0:e.children[0])==null?void 0:g.size)==null?void 0:l.width)||0,s=e.children.length*o+(e.children.length-1)*_;S.debug("widthOfChildren 88",s,"posX");let i=0;S.debug("abc91 block?.size?.x",e.id,(f=e==null?void 0:e.size)==null?void 0:f.x);let u=(b=e==null?void 0:e.size)!=null&&b.x?((p=e==null?void 0:e.size)==null?void 0:p.x)+(-((x=e==null?void 0:e.size)==null?void 0:x.width)/2||0):-_,h=0;for(const t of e.children){const m=e;if(!t.size)continue;const{width:r,height:R}=t.size,{px:Y,py:F}=gt(D,i);if(F!=h&&(h=F,u=(y=e==null?void 0:e.size)!=null&&y.x?((T=e==null?void 0:e.size)==null?void 0:T.x)+(-((v=e==null?void 0:e.size)==null?void 0:v.width)/2||0):-_,S.debug("New row in layout for block",e.id," and child ",t.id,h)),S.debug(`abc89 layout blocks (child) id: ${t.id} Pos: ${i} (px, py) ${Y},${F} (${(N=m==null?void 0:m.size)==null?void 0:N.x},${(E=m==null?void 0:m.size)==null?void 0:E.y}) parent: ${m.id} width: ${r}${_}`),m.size){const C=r/2;t.size.x=u+_+C,S.debug(`abc91 layout blocks (calc) px, pyid:${t.id} startingPos=X${u} new startingPosX${t.size.x} ${C} padding=${_} width=${r} halfWidth=${C} => x:${t.size.x} y:${t.size.y} ${t.widthInColumns} (width * (child?.w || 1)) / 2 ${r*((t==null?void 0:t.widthInColumns)||1)/2}`),u=t.size.x+C,t.size.y=m.size.y-m.size.height/2+F*(R+_)+R/2+_,S.debug(`abc88 layout blocks (calc) px, pyid:${t.id}startingPosX${u}${_}${C}=>x:${t.size.x}y:${t.size.y}${t.widthInColumns}(width * (child?.w || 1)) / 2${r*((t==null?void 0:t.widthInColumns)||1)/2}`)}t.children&&xe(t),i+=(t==null?void 0:t.widthInColumns)||1,S.debug("abc88 columnsPos",t,i)}}S.debug(`layout blocks (<==layoutBlocks) ${e.id} x: ${(L=e==null?void 0:e.size)==null?void 0:L.x} y: ${(A=e==null?void 0:e.size)==null?void 0:A.y} width: ${(k=e==null?void 0:e.size)==null?void 0:k.width}`)}function Se(e,{minX:a,minY:d,maxX:c,maxY:n}={minX:0,minY:0,maxX:0,maxY:0}){if(e.size&&e.id!=="root"){const{x:g,y:l,width:f,height:b}=e.size;g-f/2c&&(c=g+f/2),l+b/2>n&&(n=l+b/2)}if(e.children)for(const g of e.children)({minX:a,minY:d,maxX:c,maxY:n}=Se(g,{minX:a,minY:d,maxX:c,maxY:n}));return{minX:a,minY:d,maxX:c,maxY:n}}function ft(e){const a=e.getBlock("root");if(!a)return;te(a,e,0,0),xe(a),S.debug("getBlocks",JSON.stringify(a,null,2));const{minX:d,minY:c,maxX:n,maxY:g}=Se(a),l=g-c,f=n-d;return{x:d,y:c,width:f,height:l}}const bt=function(e,a){return a.db.getClasses()},xt=async function(e,a,d,c){const{securityLevel:n,block:g}=se(),l=c.db;let f;n==="sandbox"&&(f=H("#i"+a));const b=n==="sandbox"?H(f.nodes()[0].contentDocument.body):H("body"),p=n==="sandbox"?b.select(`[id="${a}"]`):H(`[id="${a}"]`);ke(p,["point","circle","cross"],c.type,a);const y=l.getBlocks(),T=l.getBlocksFlat(),v=l.getEdges(),N=p.insert("g").attr("class","block");await ut(N,y,l);const E=ft(l);if(await dt(N,y,l),await ht(N,v,T,l,a),E){const L=E,A=Math.max(1,Math.round(.125*(L.width/L.height))),k=L.height+A+10,D=L.width+10,{useMaxWidth:o}=g;ye(p,k,D,!!o),S.debug("Here Bounds",E,L),p.attr("viewBox",`${L.x-5} ${L.y-5} ${L.width+10} ${L.height+10}`)}Ae(Be)},St={draw:xt,getClasses:bt},Tt={parser:Pe,db:nt,renderer:St,styles:lt};export{Tt as diagram};
diff --git a/frontend-dist/assets/c4Diagram-c83219d4-Dwk4T9_E.js b/frontend-dist/assets/c4Diagram-c83219d4-Dwk4T9_E.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c98deef9f05bda0f281c70af3d5ae7d0c88d022
--- /dev/null
+++ b/frontend-dist/assets/c4Diagram-c83219d4-Dwk4T9_E.js
@@ -0,0 +1,10 @@
+import{s as we,g as Oe,a as Te,b as Re,c as Dt,d as Nt,l as le,e as De,f as Se,h as wt,i as ue,j as Pe,w as Me,k as Kt,m as oe}from"./index-BCNM9-Ly.js";import{d as Le,g as Ne}from"./svgDrawCommon-b86b1483-KNrWL8cU.js";var Yt=function(){var e=function(bt,_,x,m){for(x=x||{},m=bt.length;m--;x[bt[m]]=_);return x},t=[1,24],a=[1,25],o=[1,26],l=[1,27],i=[1,28],s=[1,63],r=[1,64],n=[1,65],h=[1,66],f=[1,67],d=[1,68],p=[1,69],E=[1,29],O=[1,30],R=[1,31],S=[1,32],L=[1,33],Y=[1,34],Q=[1,35],H=[1,36],q=[1,37],G=[1,38],K=[1,39],J=[1,40],Z=[1,41],$=[1,42],tt=[1,43],et=[1,44],it=[1,45],nt=[1,46],st=[1,47],at=[1,48],rt=[1,50],lt=[1,51],ot=[1,52],ct=[1,53],ht=[1,54],ut=[1,55],dt=[1,56],ft=[1,57],pt=[1,58],yt=[1,59],gt=[1,60],At=[14,42],Vt=[14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],Ot=[12,14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],v=[1,82],k=[1,83],A=[1,84],C=[1,85],w=[12,14,42],ne=[12,14,33,42],Pt=[12,14,33,42,76,77,79,80],mt=[12,33],zt=[34,36,37,38,39,40,41,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],Xt={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,direction:5,direction_tb:6,direction_bt:7,direction_rl:8,direction_lr:9,graphConfig:10,C4_CONTEXT:11,NEWLINE:12,statements:13,EOF:14,C4_CONTAINER:15,C4_COMPONENT:16,C4_DYNAMIC:17,C4_DEPLOYMENT:18,otherStatements:19,diagramStatements:20,otherStatement:21,title:22,accDescription:23,acc_title:24,acc_title_value:25,acc_descr:26,acc_descr_value:27,acc_descr_multiline_value:28,boundaryStatement:29,boundaryStartStatement:30,boundaryStopStatement:31,boundaryStart:32,LBRACE:33,ENTERPRISE_BOUNDARY:34,attributes:35,SYSTEM_BOUNDARY:36,BOUNDARY:37,CONTAINER_BOUNDARY:38,NODE:39,NODE_L:40,NODE_R:41,RBRACE:42,diagramStatement:43,PERSON:44,PERSON_EXT:45,SYSTEM:46,SYSTEM_DB:47,SYSTEM_QUEUE:48,SYSTEM_EXT:49,SYSTEM_EXT_DB:50,SYSTEM_EXT_QUEUE:51,CONTAINER:52,CONTAINER_DB:53,CONTAINER_QUEUE:54,CONTAINER_EXT:55,CONTAINER_EXT_DB:56,CONTAINER_EXT_QUEUE:57,COMPONENT:58,COMPONENT_DB:59,COMPONENT_QUEUE:60,COMPONENT_EXT:61,COMPONENT_EXT_DB:62,COMPONENT_EXT_QUEUE:63,REL:64,BIREL:65,REL_U:66,REL_D:67,REL_L:68,REL_R:69,REL_B:70,REL_INDEX:71,UPDATE_EL_STYLE:72,UPDATE_REL_STYLE:73,UPDATE_LAYOUT_CONFIG:74,attribute:75,STR:76,STR_KEY:77,STR_VALUE:78,ATTRIBUTE:79,ATTRIBUTE_EMPTY:80,$accept:0,$end:1},terminals_:{2:"error",6:"direction_tb",7:"direction_bt",8:"direction_rl",9:"direction_lr",11:"C4_CONTEXT",12:"NEWLINE",14:"EOF",15:"C4_CONTAINER",16:"C4_COMPONENT",17:"C4_DYNAMIC",18:"C4_DEPLOYMENT",22:"title",23:"accDescription",24:"acc_title",25:"acc_title_value",26:"acc_descr",27:"acc_descr_value",28:"acc_descr_multiline_value",33:"LBRACE",34:"ENTERPRISE_BOUNDARY",36:"SYSTEM_BOUNDARY",37:"BOUNDARY",38:"CONTAINER_BOUNDARY",39:"NODE",40:"NODE_L",41:"NODE_R",42:"RBRACE",44:"PERSON",45:"PERSON_EXT",46:"SYSTEM",47:"SYSTEM_DB",48:"SYSTEM_QUEUE",49:"SYSTEM_EXT",50:"SYSTEM_EXT_DB",51:"SYSTEM_EXT_QUEUE",52:"CONTAINER",53:"CONTAINER_DB",54:"CONTAINER_QUEUE",55:"CONTAINER_EXT",56:"CONTAINER_EXT_DB",57:"CONTAINER_EXT_QUEUE",58:"COMPONENT",59:"COMPONENT_DB",60:"COMPONENT_QUEUE",61:"COMPONENT_EXT",62:"COMPONENT_EXT_DB",63:"COMPONENT_EXT_QUEUE",64:"REL",65:"BIREL",66:"REL_U",67:"REL_D",68:"REL_L",69:"REL_R",70:"REL_B",71:"REL_INDEX",72:"UPDATE_EL_STYLE",73:"UPDATE_REL_STYLE",74:"UPDATE_LAYOUT_CONFIG",76:"STR",77:"STR_KEY",78:"STR_VALUE",79:"ATTRIBUTE",80:"ATTRIBUTE_EMPTY"},productions_:[0,[3,1],[3,1],[5,1],[5,1],[5,1],[5,1],[4,1],[10,4],[10,4],[10,4],[10,4],[10,4],[13,1],[13,1],[13,2],[19,1],[19,2],[19,3],[21,1],[21,1],[21,2],[21,2],[21,1],[29,3],[30,3],[30,3],[30,4],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[31,1],[20,1],[20,2],[20,3],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,1],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[35,1],[35,2],[75,1],[75,2],[75,1],[75,1]],performAction:function(_,x,m,g,T,u,Tt){var y=u.length-1;switch(T){case 3:g.setDirection("TB");break;case 4:g.setDirection("BT");break;case 5:g.setDirection("RL");break;case 6:g.setDirection("LR");break;case 8:case 9:case 10:case 11:case 12:g.setC4Type(u[y-3]);break;case 19:g.setTitle(u[y].substring(6)),this.$=u[y].substring(6);break;case 20:g.setAccDescription(u[y].substring(15)),this.$=u[y].substring(15);break;case 21:this.$=u[y].trim(),g.setTitle(this.$);break;case 22:case 23:this.$=u[y].trim(),g.setAccDescription(this.$);break;case 28:case 29:u[y].splice(2,0,"ENTERPRISE"),g.addPersonOrSystemBoundary(...u[y]),this.$=u[y];break;case 30:g.addPersonOrSystemBoundary(...u[y]),this.$=u[y];break;case 31:u[y].splice(2,0,"CONTAINER"),g.addContainerBoundary(...u[y]),this.$=u[y];break;case 32:g.addDeploymentNode("node",...u[y]),this.$=u[y];break;case 33:g.addDeploymentNode("nodeL",...u[y]),this.$=u[y];break;case 34:g.addDeploymentNode("nodeR",...u[y]),this.$=u[y];break;case 35:g.popBoundaryParseStack();break;case 39:g.addPersonOrSystem("person",...u[y]),this.$=u[y];break;case 40:g.addPersonOrSystem("external_person",...u[y]),this.$=u[y];break;case 41:g.addPersonOrSystem("system",...u[y]),this.$=u[y];break;case 42:g.addPersonOrSystem("system_db",...u[y]),this.$=u[y];break;case 43:g.addPersonOrSystem("system_queue",...u[y]),this.$=u[y];break;case 44:g.addPersonOrSystem("external_system",...u[y]),this.$=u[y];break;case 45:g.addPersonOrSystem("external_system_db",...u[y]),this.$=u[y];break;case 46:g.addPersonOrSystem("external_system_queue",...u[y]),this.$=u[y];break;case 47:g.addContainer("container",...u[y]),this.$=u[y];break;case 48:g.addContainer("container_db",...u[y]),this.$=u[y];break;case 49:g.addContainer("container_queue",...u[y]),this.$=u[y];break;case 50:g.addContainer("external_container",...u[y]),this.$=u[y];break;case 51:g.addContainer("external_container_db",...u[y]),this.$=u[y];break;case 52:g.addContainer("external_container_queue",...u[y]),this.$=u[y];break;case 53:g.addComponent("component",...u[y]),this.$=u[y];break;case 54:g.addComponent("component_db",...u[y]),this.$=u[y];break;case 55:g.addComponent("component_queue",...u[y]),this.$=u[y];break;case 56:g.addComponent("external_component",...u[y]),this.$=u[y];break;case 57:g.addComponent("external_component_db",...u[y]),this.$=u[y];break;case 58:g.addComponent("external_component_queue",...u[y]),this.$=u[y];break;case 60:g.addRel("rel",...u[y]),this.$=u[y];break;case 61:g.addRel("birel",...u[y]),this.$=u[y];break;case 62:g.addRel("rel_u",...u[y]),this.$=u[y];break;case 63:g.addRel("rel_d",...u[y]),this.$=u[y];break;case 64:g.addRel("rel_l",...u[y]),this.$=u[y];break;case 65:g.addRel("rel_r",...u[y]),this.$=u[y];break;case 66:g.addRel("rel_b",...u[y]),this.$=u[y];break;case 67:u[y].splice(0,1),g.addRel("rel",...u[y]),this.$=u[y];break;case 68:g.updateElStyle("update_el_style",...u[y]),this.$=u[y];break;case 69:g.updateRelStyle("update_rel_style",...u[y]),this.$=u[y];break;case 70:g.updateLayoutConfig("update_layout_config",...u[y]),this.$=u[y];break;case 71:this.$=[u[y]];break;case 72:u[y].unshift(u[y-1]),this.$=u[y];break;case 73:case 75:this.$=u[y].trim();break;case 74:let Et={};Et[u[y-1].trim()]=u[y].trim(),this.$=Et;break;case 76:this.$="";break}},table:[{3:1,4:2,5:3,6:[1,5],7:[1,6],8:[1,7],9:[1,8],10:4,11:[1,9],15:[1,10],16:[1,11],17:[1,12],18:[1,13]},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,7]},{1:[2,3]},{1:[2,4]},{1:[2,5]},{1:[2,6]},{12:[1,14]},{12:[1,15]},{12:[1,16]},{12:[1,17]},{12:[1,18]},{13:19,19:20,20:21,21:22,22:t,23:a,24:o,26:l,28:i,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{13:70,19:20,20:21,21:22,22:t,23:a,24:o,26:l,28:i,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{13:71,19:20,20:21,21:22,22:t,23:a,24:o,26:l,28:i,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{13:72,19:20,20:21,21:22,22:t,23:a,24:o,26:l,28:i,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{13:73,19:20,20:21,21:22,22:t,23:a,24:o,26:l,28:i,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{14:[1,74]},e(At,[2,13],{43:23,29:49,30:61,32:62,20:75,34:s,36:r,37:n,38:h,39:f,40:d,41:p,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt}),e(At,[2,14]),e(Vt,[2,16],{12:[1,76]}),e(At,[2,36],{12:[1,77]}),e(Ot,[2,19]),e(Ot,[2,20]),{25:[1,78]},{27:[1,79]},e(Ot,[2,23]),{35:80,75:81,76:v,77:k,79:A,80:C},{35:86,75:81,76:v,77:k,79:A,80:C},{35:87,75:81,76:v,77:k,79:A,80:C},{35:88,75:81,76:v,77:k,79:A,80:C},{35:89,75:81,76:v,77:k,79:A,80:C},{35:90,75:81,76:v,77:k,79:A,80:C},{35:91,75:81,76:v,77:k,79:A,80:C},{35:92,75:81,76:v,77:k,79:A,80:C},{35:93,75:81,76:v,77:k,79:A,80:C},{35:94,75:81,76:v,77:k,79:A,80:C},{35:95,75:81,76:v,77:k,79:A,80:C},{35:96,75:81,76:v,77:k,79:A,80:C},{35:97,75:81,76:v,77:k,79:A,80:C},{35:98,75:81,76:v,77:k,79:A,80:C},{35:99,75:81,76:v,77:k,79:A,80:C},{35:100,75:81,76:v,77:k,79:A,80:C},{35:101,75:81,76:v,77:k,79:A,80:C},{35:102,75:81,76:v,77:k,79:A,80:C},{35:103,75:81,76:v,77:k,79:A,80:C},{35:104,75:81,76:v,77:k,79:A,80:C},e(w,[2,59]),{35:105,75:81,76:v,77:k,79:A,80:C},{35:106,75:81,76:v,77:k,79:A,80:C},{35:107,75:81,76:v,77:k,79:A,80:C},{35:108,75:81,76:v,77:k,79:A,80:C},{35:109,75:81,76:v,77:k,79:A,80:C},{35:110,75:81,76:v,77:k,79:A,80:C},{35:111,75:81,76:v,77:k,79:A,80:C},{35:112,75:81,76:v,77:k,79:A,80:C},{35:113,75:81,76:v,77:k,79:A,80:C},{35:114,75:81,76:v,77:k,79:A,80:C},{35:115,75:81,76:v,77:k,79:A,80:C},{20:116,29:49,30:61,32:62,34:s,36:r,37:n,38:h,39:f,40:d,41:p,43:23,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt},{12:[1,118],33:[1,117]},{35:119,75:81,76:v,77:k,79:A,80:C},{35:120,75:81,76:v,77:k,79:A,80:C},{35:121,75:81,76:v,77:k,79:A,80:C},{35:122,75:81,76:v,77:k,79:A,80:C},{35:123,75:81,76:v,77:k,79:A,80:C},{35:124,75:81,76:v,77:k,79:A,80:C},{35:125,75:81,76:v,77:k,79:A,80:C},{14:[1,126]},{14:[1,127]},{14:[1,128]},{14:[1,129]},{1:[2,8]},e(At,[2,15]),e(Vt,[2,17],{21:22,19:130,22:t,23:a,24:o,26:l,28:i}),e(At,[2,37],{19:20,20:21,21:22,43:23,29:49,30:61,32:62,13:131,22:t,23:a,24:o,26:l,28:i,34:s,36:r,37:n,38:h,39:f,40:d,41:p,44:E,45:O,46:R,47:S,48:L,49:Y,50:Q,51:H,52:q,53:G,54:K,55:J,56:Z,57:$,58:tt,59:et,60:it,61:nt,62:st,63:at,64:rt,65:lt,66:ot,67:ct,68:ht,69:ut,70:dt,71:ft,72:pt,73:yt,74:gt}),e(Ot,[2,21]),e(Ot,[2,22]),e(w,[2,39]),e(ne,[2,71],{75:81,35:132,76:v,77:k,79:A,80:C}),e(Pt,[2,73]),{78:[1,133]},e(Pt,[2,75]),e(Pt,[2,76]),e(w,[2,40]),e(w,[2,41]),e(w,[2,42]),e(w,[2,43]),e(w,[2,44]),e(w,[2,45]),e(w,[2,46]),e(w,[2,47]),e(w,[2,48]),e(w,[2,49]),e(w,[2,50]),e(w,[2,51]),e(w,[2,52]),e(w,[2,53]),e(w,[2,54]),e(w,[2,55]),e(w,[2,56]),e(w,[2,57]),e(w,[2,58]),e(w,[2,60]),e(w,[2,61]),e(w,[2,62]),e(w,[2,63]),e(w,[2,64]),e(w,[2,65]),e(w,[2,66]),e(w,[2,67]),e(w,[2,68]),e(w,[2,69]),e(w,[2,70]),{31:134,42:[1,135]},{12:[1,136]},{33:[1,137]},e(mt,[2,28]),e(mt,[2,29]),e(mt,[2,30]),e(mt,[2,31]),e(mt,[2,32]),e(mt,[2,33]),e(mt,[2,34]),{1:[2,9]},{1:[2,10]},{1:[2,11]},{1:[2,12]},e(Vt,[2,18]),e(At,[2,38]),e(ne,[2,72]),e(Pt,[2,74]),e(w,[2,24]),e(w,[2,35]),e(zt,[2,25]),e(zt,[2,26],{12:[1,138]}),e(zt,[2,27])],defaultActions:{2:[2,1],3:[2,2],4:[2,7],5:[2,3],6:[2,4],7:[2,5],8:[2,6],74:[2,8],126:[2,9],127:[2,10],128:[2,11],129:[2,12]},parseError:function(_,x){if(x.recoverable)this.trace(_);else{var m=new Error(_);throw m.hash=x,m}},parse:function(_){var x=this,m=[0],g=[],T=[null],u=[],Tt=this.table,y="",Et=0,se=0,ve=2,ae=1,ke=u.slice.call(arguments,1),D=Object.create(this.lexer),vt={yy:{}};for(var Qt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Qt)&&(vt.yy[Qt]=this.yy[Qt]);D.setInput(_,vt.yy),vt.yy.lexer=D,vt.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var Ht=D.yylloc;u.push(Ht);var Ae=D.options&&D.options.ranges;typeof vt.yy.parseError=="function"?this.parseError=vt.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Ce(){var X;return X=g.pop()||D.lex()||ae,typeof X!="number"&&(X instanceof Array&&(g=X,X=g.pop()),X=x.symbols_[X]||X),X}for(var M,kt,N,qt,Ct={},Mt,z,re,Lt;;){if(kt=m[m.length-1],this.defaultActions[kt]?N=this.defaultActions[kt]:((M===null||typeof M>"u")&&(M=Ce()),N=Tt[kt]&&Tt[kt][M]),typeof N>"u"||!N.length||!N[0]){var Gt="";Lt=[];for(Mt in Tt[kt])this.terminals_[Mt]&&Mt>ve&&Lt.push("'"+this.terminals_[Mt]+"'");D.showPosition?Gt="Parse error on line "+(Et+1)+`:
+`+D.showPosition()+`
+Expecting `+Lt.join(", ")+", got '"+(this.terminals_[M]||M)+"'":Gt="Parse error on line "+(Et+1)+": Unexpected "+(M==ae?"end of input":"'"+(this.terminals_[M]||M)+"'"),this.parseError(Gt,{text:D.match,token:this.terminals_[M]||M,line:D.yylineno,loc:Ht,expected:Lt})}if(N[0]instanceof Array&&N.length>1)throw new Error("Parse Error: multiple actions possible at state: "+kt+", token: "+M);switch(N[0]){case 1:m.push(M),T.push(D.yytext),u.push(D.yylloc),m.push(N[1]),M=null,se=D.yyleng,y=D.yytext,Et=D.yylineno,Ht=D.yylloc;break;case 2:if(z=this.productions_[N[1]][1],Ct.$=T[T.length-z],Ct._$={first_line:u[u.length-(z||1)].first_line,last_line:u[u.length-1].last_line,first_column:u[u.length-(z||1)].first_column,last_column:u[u.length-1].last_column},Ae&&(Ct._$.range=[u[u.length-(z||1)].range[0],u[u.length-1].range[1]]),qt=this.performAction.apply(Ct,[y,se,Et,vt.yy,N[1],T,u].concat(ke)),typeof qt<"u")return qt;z&&(m=m.slice(0,-1*z*2),T=T.slice(0,-1*z),u=u.slice(0,-1*z)),m.push(this.productions_[N[1]][0]),T.push(Ct.$),u.push(Ct._$),re=Tt[m[m.length-2]][m[m.length-1]],m.push(re);break;case 3:return!0}}return!0}},Ee=function(){var bt={EOF:1,parseError:function(x,m){if(this.yy.parser)this.yy.parser.parseError(x,m);else throw new Error(x)},setInput:function(_,x){return this.yy=x||this.yy||{},this._input=_,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var _=this._input[0];this.yytext+=_,this.yyleng++,this.offset++,this.match+=_,this.matched+=_;var x=_.match(/(?:\r\n?|\n).*/g);return x?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),_},unput:function(_){var x=_.length,m=_.split(/(?:\r\n?|\n)/g);this._input=_+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-x),this.offset-=x;var g=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),m.length-1&&(this.yylineno-=m.length-1);var T=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:m?(m.length===g.length?this.yylloc.first_column:0)+g[g.length-m.length].length-m[0].length:this.yylloc.first_column-x},this.options.ranges&&(this.yylloc.range=[T[0],T[0]+this.yyleng-x]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
+`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},less:function(_){this.unput(this.match.slice(_))},pastInput:function(){var _=this.matched.substr(0,this.matched.length-this.match.length);return(_.length>20?"...":"")+_.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var _=this.match;return _.length<20&&(_+=this._input.substr(0,20-_.length)),(_.substr(0,20)+(_.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var _=this.pastInput(),x=new Array(_.length+1).join("-");return _+this.upcomingInput()+`
+`+x+"^"},test_match:function(_,x){var m,g,T;if(this.options.backtrack_lexer&&(T={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(T.yylloc.range=this.yylloc.range.slice(0))),g=_[0].match(/(?:\r\n?|\n).*/g),g&&(this.yylineno+=g.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:g?g[g.length-1].length-g[g.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+_[0].length},this.yytext+=_[0],this.match+=_[0],this.matches=_,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(_[0].length),this.matched+=_[0],m=this.performAction.call(this,this.yy,this,x,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),m)return m;if(this._backtrack){for(var u in T)this[u]=T[u];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var _,x,m,g;this._more||(this.yytext="",this.match="");for(var T=this._currentRules(),u=0;ux[0].length)){if(x=m,g=u,this.options.backtrack_lexer){if(_=this.test_match(m,T[u]),_!==!1)return _;if(this._backtrack){x=!1;continue}else return!1}else if(!this.options.flex)break}return x?(_=this.test_match(x,T[g]),_!==!1?_:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text.
+`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var x=this.next();return x||this.lex()},begin:function(x){this.conditionStack.push(x)},popState:function(){var x=this.conditionStack.length-1;return x>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(x){return x=this.conditionStack.length-1-Math.abs(x||0),x>=0?this.conditionStack[x]:"INITIAL"},pushState:function(x){this.begin(x)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(x,m,g,T){switch(g){case 0:return 6;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 22;case 5:return 23;case 6:return this.begin("acc_title"),24;case 7:return this.popState(),"acc_title_value";case 8:return this.begin("acc_descr"),26;case 9:return this.popState(),"acc_descr_value";case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:break;case 14:c;break;case 15:return 12;case 16:break;case 17:return 11;case 18:return 15;case 19:return 16;case 20:return 17;case 21:return 18;case 22:return this.begin("person_ext"),45;case 23:return this.begin("person"),44;case 24:return this.begin("system_ext_queue"),51;case 25:return this.begin("system_ext_db"),50;case 26:return this.begin("system_ext"),49;case 27:return this.begin("system_queue"),48;case 28:return this.begin("system_db"),47;case 29:return this.begin("system"),46;case 30:return this.begin("boundary"),37;case 31:return this.begin("enterprise_boundary"),34;case 32:return this.begin("system_boundary"),36;case 33:return this.begin("container_ext_queue"),57;case 34:return this.begin("container_ext_db"),56;case 35:return this.begin("container_ext"),55;case 36:return this.begin("container_queue"),54;case 37:return this.begin("container_db"),53;case 38:return this.begin("container"),52;case 39:return this.begin("container_boundary"),38;case 40:return this.begin("component_ext_queue"),63;case 41:return this.begin("component_ext_db"),62;case 42:return this.begin("component_ext"),61;case 43:return this.begin("component_queue"),60;case 44:return this.begin("component_db"),59;case 45:return this.begin("component"),58;case 46:return this.begin("node"),39;case 47:return this.begin("node"),39;case 48:return this.begin("node_l"),40;case 49:return this.begin("node_r"),41;case 50:return this.begin("rel"),64;case 51:return this.begin("birel"),65;case 52:return this.begin("rel_u"),66;case 53:return this.begin("rel_u"),66;case 54:return this.begin("rel_d"),67;case 55:return this.begin("rel_d"),67;case 56:return this.begin("rel_l"),68;case 57:return this.begin("rel_l"),68;case 58:return this.begin("rel_r"),69;case 59:return this.begin("rel_r"),69;case 60:return this.begin("rel_b"),70;case 61:return this.begin("rel_index"),71;case 62:return this.begin("update_el_style"),72;case 63:return this.begin("update_rel_style"),73;case 64:return this.begin("update_layout_config"),74;case 65:return"EOF_IN_STRUCT";case 66:return this.begin("attribute"),"ATTRIBUTE_EMPTY";case 67:this.begin("attribute");break;case 68:this.popState(),this.popState();break;case 69:return 80;case 70:break;case 71:return 80;case 72:this.begin("string");break;case 73:this.popState();break;case 74:return"STR";case 75:this.begin("string_kv");break;case 76:return this.begin("string_kv_key"),"STR_KEY";case 77:this.popState(),this.begin("string_kv_value");break;case 78:return"STR_VALUE";case 79:this.popState(),this.popState();break;case 80:return"STR";case 81:return"LBRACE";case 82:return"RBRACE";case 83:return"SPACE";case 84:return"EOL";case 85:return 14}},rules:[/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:title\s[^#\n;]+)/,/^(?:accDescription\s[^#\n;]+)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:C4Context\b)/,/^(?:C4Container\b)/,/^(?:C4Component\b)/,/^(?:C4Dynamic\b)/,/^(?:C4Deployment\b)/,/^(?:Person_Ext\b)/,/^(?:Person\b)/,/^(?:SystemQueue_Ext\b)/,/^(?:SystemDb_Ext\b)/,/^(?:System_Ext\b)/,/^(?:SystemQueue\b)/,/^(?:SystemDb\b)/,/^(?:System\b)/,/^(?:Boundary\b)/,/^(?:Enterprise_Boundary\b)/,/^(?:System_Boundary\b)/,/^(?:ContainerQueue_Ext\b)/,/^(?:ContainerDb_Ext\b)/,/^(?:Container_Ext\b)/,/^(?:ContainerQueue\b)/,/^(?:ContainerDb\b)/,/^(?:Container\b)/,/^(?:Container_Boundary\b)/,/^(?:ComponentQueue_Ext\b)/,/^(?:ComponentDb_Ext\b)/,/^(?:Component_Ext\b)/,/^(?:ComponentQueue\b)/,/^(?:ComponentDb\b)/,/^(?:Component\b)/,/^(?:Deployment_Node\b)/,/^(?:Node\b)/,/^(?:Node_L\b)/,/^(?:Node_R\b)/,/^(?:Rel\b)/,/^(?:BiRel\b)/,/^(?:Rel_Up\b)/,/^(?:Rel_U\b)/,/^(?:Rel_Down\b)/,/^(?:Rel_D\b)/,/^(?:Rel_Left\b)/,/^(?:Rel_L\b)/,/^(?:Rel_Right\b)/,/^(?:Rel_R\b)/,/^(?:Rel_Back\b)/,/^(?:RelIndex\b)/,/^(?:UpdateElementStyle\b)/,/^(?:UpdateRelStyle\b)/,/^(?:UpdateLayoutConfig\b)/,/^(?:$)/,/^(?:[(][ ]*[,])/,/^(?:[(])/,/^(?:[)])/,/^(?:,,)/,/^(?:,)/,/^(?:[ ]*["]["])/,/^(?:[ ]*["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[ ]*[\$])/,/^(?:[^=]*)/,/^(?:[=][ ]*["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:[^,]+)/,/^(?:\{)/,/^(?:\})/,/^(?:[\s]+)/,/^(?:[\n\r]+)/,/^(?:$)/],conditions:{acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},string_kv_value:{rules:[78,79],inclusive:!1},string_kv_key:{rules:[77],inclusive:!1},string_kv:{rules:[76],inclusive:!1},string:{rules:[73,74],inclusive:!1},attribute:{rules:[68,69,70,71,72,75,80],inclusive:!1},update_layout_config:{rules:[65,66,67,68],inclusive:!1},update_rel_style:{rules:[65,66,67,68],inclusive:!1},update_el_style:{rules:[65,66,67,68],inclusive:!1},rel_b:{rules:[65,66,67,68],inclusive:!1},rel_r:{rules:[65,66,67,68],inclusive:!1},rel_l:{rules:[65,66,67,68],inclusive:!1},rel_d:{rules:[65,66,67,68],inclusive:!1},rel_u:{rules:[65,66,67,68],inclusive:!1},rel_bi:{rules:[],inclusive:!1},rel:{rules:[65,66,67,68],inclusive:!1},node_r:{rules:[65,66,67,68],inclusive:!1},node_l:{rules:[65,66,67,68],inclusive:!1},node:{rules:[65,66,67,68],inclusive:!1},index:{rules:[],inclusive:!1},rel_index:{rules:[65,66,67,68],inclusive:!1},component_ext_queue:{rules:[],inclusive:!1},component_ext_db:{rules:[65,66,67,68],inclusive:!1},component_ext:{rules:[65,66,67,68],inclusive:!1},component_queue:{rules:[65,66,67,68],inclusive:!1},component_db:{rules:[65,66,67,68],inclusive:!1},component:{rules:[65,66,67,68],inclusive:!1},container_boundary:{rules:[65,66,67,68],inclusive:!1},container_ext_queue:{rules:[65,66,67,68],inclusive:!1},container_ext_db:{rules:[65,66,67,68],inclusive:!1},container_ext:{rules:[65,66,67,68],inclusive:!1},container_queue:{rules:[65,66,67,68],inclusive:!1},container_db:{rules:[65,66,67,68],inclusive:!1},container:{rules:[65,66,67,68],inclusive:!1},birel:{rules:[65,66,67,68],inclusive:!1},system_boundary:{rules:[65,66,67,68],inclusive:!1},enterprise_boundary:{rules:[65,66,67,68],inclusive:!1},boundary:{rules:[65,66,67,68],inclusive:!1},system_ext_queue:{rules:[65,66,67,68],inclusive:!1},system_ext_db:{rules:[65,66,67,68],inclusive:!1},system_ext:{rules:[65,66,67,68],inclusive:!1},system_queue:{rules:[65,66,67,68],inclusive:!1},system_db:{rules:[65,66,67,68],inclusive:!1},system:{rules:[65,66,67,68],inclusive:!1},person_ext:{rules:[65,66,67,68],inclusive:!1},person:{rules:[65,66,67,68],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,8,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,81,82,83,84,85],inclusive:!0}}};return bt}();Xt.lexer=Ee;function Wt(){this.yy={}}return Wt.prototype=Xt,Xt.Parser=Wt,new Wt}();Yt.parser=Yt;const Be=Yt;let U=[],_t=[""],P="global",j="",V=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],St=[],te="",ee=!1,It=4,jt=2;var de;const Ye=function(){return de},Ie=function(e){de=ue(e,Dt())},je=function(e,t,a,o,l,i,s,r,n){if(e==null||t===void 0||t===null||a===void 0||a===null||o===void 0||o===null)return;let h={};const f=St.find(d=>d.from===t&&d.to===a);if(f?h=f:St.push(h),h.type=e,h.from=t,h.to=a,h.label={text:o},l==null)h.techn={text:""};else if(typeof l=="object"){let[d,p]=Object.entries(l)[0];h[d]={text:p}}else h.techn={text:l};if(i==null)h.descr={text:""};else if(typeof i=="object"){let[d,p]=Object.entries(i)[0];h[d]={text:p}}else h.descr={text:i};if(typeof s=="object"){let[d,p]=Object.entries(s)[0];h[d]=p}else h.sprite=s;if(typeof r=="object"){let[d,p]=Object.entries(r)[0];h[d]=p}else h.tags=r;if(typeof n=="object"){let[d,p]=Object.entries(n)[0];h[d]=p}else h.link=n;h.wrap=xt()},Ue=function(e,t,a,o,l,i,s){if(t===null||a===null)return;let r={};const n=U.find(h=>h.alias===t);if(n&&t===n.alias?r=n:(r.alias=t,U.push(r)),a==null?r.label={text:""}:r.label={text:a},o==null)r.descr={text:""};else if(typeof o=="object"){let[h,f]=Object.entries(o)[0];r[h]={text:f}}else r.descr={text:o};if(typeof l=="object"){let[h,f]=Object.entries(l)[0];r[h]=f}else r.sprite=l;if(typeof i=="object"){let[h,f]=Object.entries(i)[0];r[h]=f}else r.tags=i;if(typeof s=="object"){let[h,f]=Object.entries(s)[0];r[h]=f}else r.link=s;r.typeC4Shape={text:e},r.parentBoundary=P,r.wrap=xt()},Fe=function(e,t,a,o,l,i,s,r){if(t===null||a===null)return;let n={};const h=U.find(f=>f.alias===t);if(h&&t===h.alias?n=h:(n.alias=t,U.push(n)),a==null?n.label={text:""}:n.label={text:a},o==null)n.techn={text:""};else if(typeof o=="object"){let[f,d]=Object.entries(o)[0];n[f]={text:d}}else n.techn={text:o};if(l==null)n.descr={text:""};else if(typeof l=="object"){let[f,d]=Object.entries(l)[0];n[f]={text:d}}else n.descr={text:l};if(typeof i=="object"){let[f,d]=Object.entries(i)[0];n[f]=d}else n.sprite=i;if(typeof s=="object"){let[f,d]=Object.entries(s)[0];n[f]=d}else n.tags=s;if(typeof r=="object"){let[f,d]=Object.entries(r)[0];n[f]=d}else n.link=r;n.wrap=xt(),n.typeC4Shape={text:e},n.parentBoundary=P},Ve=function(e,t,a,o,l,i,s,r){if(t===null||a===null)return;let n={};const h=U.find(f=>f.alias===t);if(h&&t===h.alias?n=h:(n.alias=t,U.push(n)),a==null?n.label={text:""}:n.label={text:a},o==null)n.techn={text:""};else if(typeof o=="object"){let[f,d]=Object.entries(o)[0];n[f]={text:d}}else n.techn={text:o};if(l==null)n.descr={text:""};else if(typeof l=="object"){let[f,d]=Object.entries(l)[0];n[f]={text:d}}else n.descr={text:l};if(typeof i=="object"){let[f,d]=Object.entries(i)[0];n[f]=d}else n.sprite=i;if(typeof s=="object"){let[f,d]=Object.entries(s)[0];n[f]=d}else n.tags=s;if(typeof r=="object"){let[f,d]=Object.entries(r)[0];n[f]=d}else n.link=r;n.wrap=xt(),n.typeC4Shape={text:e},n.parentBoundary=P},ze=function(e,t,a,o,l){if(e===null||t===null)return;let i={};const s=V.find(r=>r.alias===e);if(s&&e===s.alias?i=s:(i.alias=e,V.push(i)),t==null?i.label={text:""}:i.label={text:t},a==null)i.type={text:"system"};else if(typeof a=="object"){let[r,n]=Object.entries(a)[0];i[r]={text:n}}else i.type={text:a};if(typeof o=="object"){let[r,n]=Object.entries(o)[0];i[r]=n}else i.tags=o;if(typeof l=="object"){let[r,n]=Object.entries(l)[0];i[r]=n}else i.link=l;i.parentBoundary=P,i.wrap=xt(),j=P,P=e,_t.push(j)},Xe=function(e,t,a,o,l){if(e===null||t===null)return;let i={};const s=V.find(r=>r.alias===e);if(s&&e===s.alias?i=s:(i.alias=e,V.push(i)),t==null?i.label={text:""}:i.label={text:t},a==null)i.type={text:"container"};else if(typeof a=="object"){let[r,n]=Object.entries(a)[0];i[r]={text:n}}else i.type={text:a};if(typeof o=="object"){let[r,n]=Object.entries(o)[0];i[r]=n}else i.tags=o;if(typeof l=="object"){let[r,n]=Object.entries(l)[0];i[r]=n}else i.link=l;i.parentBoundary=P,i.wrap=xt(),j=P,P=e,_t.push(j)},We=function(e,t,a,o,l,i,s,r){if(t===null||a===null)return;let n={};const h=V.find(f=>f.alias===t);if(h&&t===h.alias?n=h:(n.alias=t,V.push(n)),a==null?n.label={text:""}:n.label={text:a},o==null)n.type={text:"node"};else if(typeof o=="object"){let[f,d]=Object.entries(o)[0];n[f]={text:d}}else n.type={text:o};if(l==null)n.descr={text:""};else if(typeof l=="object"){let[f,d]=Object.entries(l)[0];n[f]={text:d}}else n.descr={text:l};if(typeof s=="object"){let[f,d]=Object.entries(s)[0];n[f]=d}else n.tags=s;if(typeof r=="object"){let[f,d]=Object.entries(r)[0];n[f]=d}else n.link=r;n.nodeType=e,n.parentBoundary=P,n.wrap=xt(),j=P,P=t,_t.push(j)},Qe=function(){P=j,_t.pop(),j=_t.pop(),_t.push(j)},He=function(e,t,a,o,l,i,s,r,n,h,f){let d=U.find(p=>p.alias===t);if(!(d===void 0&&(d=V.find(p=>p.alias===t),d===void 0))){if(a!=null)if(typeof a=="object"){let[p,E]=Object.entries(a)[0];d[p]=E}else d.bgColor=a;if(o!=null)if(typeof o=="object"){let[p,E]=Object.entries(o)[0];d[p]=E}else d.fontColor=o;if(l!=null)if(typeof l=="object"){let[p,E]=Object.entries(l)[0];d[p]=E}else d.borderColor=l;if(i!=null)if(typeof i=="object"){let[p,E]=Object.entries(i)[0];d[p]=E}else d.shadowing=i;if(s!=null)if(typeof s=="object"){let[p,E]=Object.entries(s)[0];d[p]=E}else d.shape=s;if(r!=null)if(typeof r=="object"){let[p,E]=Object.entries(r)[0];d[p]=E}else d.sprite=r;if(n!=null)if(typeof n=="object"){let[p,E]=Object.entries(n)[0];d[p]=E}else d.techn=n;if(h!=null)if(typeof h=="object"){let[p,E]=Object.entries(h)[0];d[p]=E}else d.legendText=h;if(f!=null)if(typeof f=="object"){let[p,E]=Object.entries(f)[0];d[p]=E}else d.legendSprite=f}},qe=function(e,t,a,o,l,i,s){const r=St.find(n=>n.from===t&&n.to===a);if(r!==void 0){if(o!=null)if(typeof o=="object"){let[n,h]=Object.entries(o)[0];r[n]=h}else r.textColor=o;if(l!=null)if(typeof l=="object"){let[n,h]=Object.entries(l)[0];r[n]=h}else r.lineColor=l;if(i!=null)if(typeof i=="object"){let[n,h]=Object.entries(i)[0];r[n]=parseInt(h)}else r.offsetX=parseInt(i);if(s!=null)if(typeof s=="object"){let[n,h]=Object.entries(s)[0];r[n]=parseInt(h)}else r.offsetY=parseInt(s)}},Ge=function(e,t,a){let o=It,l=jt;if(typeof t=="object"){const i=Object.values(t)[0];o=parseInt(i)}else o=parseInt(t);if(typeof a=="object"){const i=Object.values(a)[0];l=parseInt(i)}else l=parseInt(a);o>=1&&(It=o),l>=1&&(jt=l)},Ke=function(){return It},Je=function(){return jt},Ze=function(){return P},$e=function(){return j},fe=function(e){return e==null?U:U.filter(t=>t.parentBoundary===e)},t0=function(e){return U.find(t=>t.alias===e)},e0=function(e){return Object.keys(fe(e))},pe=function(e){return e==null?V:V.filter(t=>t.parentBoundary===e)},i0=pe,n0=function(){return St},s0=function(){return te},a0=function(e){ee=e},xt=function(){return ee},r0=function(){U=[],V=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],j="",P="global",_t=[""],St=[],_t=[""],te="",ee=!1,It=4,jt=2},l0={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25},o0={FILLED:0,OPEN:1},c0={LEFTOF:0,RIGHTOF:1,OVER:2},h0=function(e){te=ue(e,Dt())},Jt={addPersonOrSystem:Ue,addPersonOrSystemBoundary:ze,addContainer:Fe,addContainerBoundary:Xe,addComponent:Ve,addDeploymentNode:We,popBoundaryParseStack:Qe,addRel:je,updateElStyle:He,updateRelStyle:qe,updateLayoutConfig:Ge,autoWrap:xt,setWrap:a0,getC4ShapeArray:fe,getC4Shape:t0,getC4ShapeKeys:e0,getBoundaries:pe,getBoundarys:i0,getCurrentBoundaryParse:Ze,getParentBoundaryParse:$e,getRels:n0,getTitle:s0,getC4Type:Ye,getC4ShapeInRow:Ke,getC4BoundaryInRow:Je,setAccTitle:Re,getAccTitle:Te,getAccDescription:Oe,setAccDescription:we,getConfig:()=>Dt().c4,clear:r0,LINETYPE:l0,ARROWTYPE:o0,PLACEMENT:c0,setTitle:h0,setC4Type:Ie},ie=function(e,t){return Le(e,t)},ye=function(e,t,a,o,l,i){const s=e.append("image");s.attr("width",t),s.attr("height",a),s.attr("x",o),s.attr("y",l);let r=i.startsWith("data:image/png;base64")?i:Pe.sanitizeUrl(i);s.attr("xlink:href",r)},u0=(e,t,a)=>{const o=e.append("g");let l=0;for(let i of t){let s=i.textColor?i.textColor:"#444444",r=i.lineColor?i.lineColor:"#444444",n=i.offsetX?parseInt(i.offsetX):0,h=i.offsetY?parseInt(i.offsetY):0,f="";if(l===0){let p=o.append("line");p.attr("x1",i.startPoint.x),p.attr("y1",i.startPoint.y),p.attr("x2",i.endPoint.x),p.attr("y2",i.endPoint.y),p.attr("stroke-width","1"),p.attr("stroke",r),p.style("fill","none"),i.type!=="rel_b"&&p.attr("marker-end","url("+f+"#arrowhead)"),(i.type==="birel"||i.type==="rel_b")&&p.attr("marker-start","url("+f+"#arrowend)"),l=-1}else{let p=o.append("path");p.attr("fill","none").attr("stroke-width","1").attr("stroke",r).attr("d","Mstartx,starty Qcontrolx,controly stopx,stopy ".replaceAll("startx",i.startPoint.x).replaceAll("starty",i.startPoint.y).replaceAll("controlx",i.startPoint.x+(i.endPoint.x-i.startPoint.x)/2-(i.endPoint.x-i.startPoint.x)/4).replaceAll("controly",i.startPoint.y+(i.endPoint.y-i.startPoint.y)/2).replaceAll("stopx",i.endPoint.x).replaceAll("stopy",i.endPoint.y)),i.type!=="rel_b"&&p.attr("marker-end","url("+f+"#arrowhead)"),(i.type==="birel"||i.type==="rel_b")&&p.attr("marker-start","url("+f+"#arrowend)")}let d=a.messageFont();W(a)(i.label.text,o,Math.min(i.startPoint.x,i.endPoint.x)+Math.abs(i.endPoint.x-i.startPoint.x)/2+n,Math.min(i.startPoint.y,i.endPoint.y)+Math.abs(i.endPoint.y-i.startPoint.y)/2+h,i.label.width,i.label.height,{fill:s},d),i.techn&&i.techn.text!==""&&(d=a.messageFont(),W(a)("["+i.techn.text+"]",o,Math.min(i.startPoint.x,i.endPoint.x)+Math.abs(i.endPoint.x-i.startPoint.x)/2+n,Math.min(i.startPoint.y,i.endPoint.y)+Math.abs(i.endPoint.y-i.startPoint.y)/2+a.messageFontSize+5+h,Math.max(i.label.width,i.techn.width),i.techn.height,{fill:s,"font-style":"italic"},d))}},d0=function(e,t,a){const o=e.append("g");let l=t.bgColor?t.bgColor:"none",i=t.borderColor?t.borderColor:"#444444",s=t.fontColor?t.fontColor:"black",r={"stroke-width":1,"stroke-dasharray":"7.0,7.0"};t.nodeType&&(r={"stroke-width":1});let n={x:t.x,y:t.y,fill:l,stroke:i,width:t.width,height:t.height,rx:2.5,ry:2.5,attrs:r};ie(o,n);let h=a.boundaryFont();h.fontWeight="bold",h.fontSize=h.fontSize+2,h.fontColor=s,W(a)(t.label.text,o,t.x,t.y+t.label.Y,t.width,t.height,{fill:"#444444"},h),t.type&&t.type.text!==""&&(h=a.boundaryFont(),h.fontColor=s,W(a)(t.type.text,o,t.x,t.y+t.type.Y,t.width,t.height,{fill:"#444444"},h)),t.descr&&t.descr.text!==""&&(h=a.boundaryFont(),h.fontSize=h.fontSize-2,h.fontColor=s,W(a)(t.descr.text,o,t.x,t.y+t.descr.Y,t.width,t.height,{fill:"#444444"},h))},f0=function(e,t,a){var o;let l=t.bgColor?t.bgColor:a[t.typeC4Shape.text+"_bg_color"],i=t.borderColor?t.borderColor:a[t.typeC4Shape.text+"_border_color"],s=t.fontColor?t.fontColor:"#FFFFFF",r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";switch(t.typeC4Shape.text){case"person":r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";break;case"external_person":r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAB6ElEQVR4Xu2YLY+EMBCG9+dWr0aj0Wg0Go1Go0+j8Xdv2uTCvv1gpt0ebHKPuhDaeW4605Z9mJvx4AdXUyTUdd08z+u6flmWZRnHsWkafk9DptAwDPu+f0eAYtu2PEaGWuj5fCIZrBAC2eLBAnRCsEkkxmeaJp7iDJ2QMDdHsLg8SxKFEJaAo8lAXnmuOFIhTMpxxKATebo4UiFknuNo4OniSIXQyRxEA3YsnjGCVEjVXD7yLUAqxBGUyPv/Y4W2beMgGuS7kVQIBycH0fD+oi5pezQETxdHKmQKGk1eQEYldK+jw5GxPfZ9z7Mk0Qnhf1W1m3w//EUn5BDmSZsbR44QQLBEqrBHqOrmSKaQAxdnLArCrxZcM7A7ZKs4ioRq8LFC+NpC3WCBJsvpVw5edm9iEXFuyNfxXAgSwfrFQ1c0iNda8AdejvUgnktOtJQQxmcfFzGglc5WVCj7oDgFqU18boeFSs52CUh8LE8BIVQDT1ABrB0HtgSEYlX5doJnCwv9TXocKCaKbnwhdDKPq4lf3SwU3HLq4V/+WYhHVMa/3b4IlfyikAduCkcBc7mQ3/z/Qq/cTuikhkzB12Ae/mcJC9U+Vo8Ej1gWAtgbeGgFsAMHr50BIWOLCbezvhpBFUdY6EJuJ/QDW0XoMX60zZ0AAAAASUVORK5CYII=";break}const n=e.append("g");n.attr("class","person-man");const h=Ne();switch(t.typeC4Shape.text){case"person":case"external_person":case"system":case"external_system":case"container":case"external_container":case"component":case"external_component":h.x=t.x,h.y=t.y,h.fill=l,h.width=t.width,h.height=t.height,h.stroke=i,h.rx=2.5,h.ry=2.5,h.attrs={"stroke-width":.5},ie(n,h);break;case"system_db":case"external_system_db":case"container_db":case"external_container_db":case"component_db":case"external_component_db":n.append("path").attr("fill",l).attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc0,-10 half,-10 half,-10c0,0 half,0 half,10l0,heightc0,10 -half,10 -half,10c0,0 -half,0 -half,-10l0,-height".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("half",t.width/2).replaceAll("height",t.height)),n.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc0,10 half,10 half,10c0,0 half,0 half,-10".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("half",t.width/2));break;case"system_queue":case"external_system_queue":case"container_queue":case"external_container_queue":case"component_queue":case"external_component_queue":n.append("path").attr("fill",l).attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startylwidth,0c5,0 5,half 5,halfc0,0 0,half -5,halfl-width,0c-5,0 -5,-half -5,-halfc0,0 0,-half 5,-half".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("width",t.width).replaceAll("half",t.height/2)),n.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc-5,0 -5,half -5,halfc0,half 5,half 5,half".replaceAll("startx",t.x+t.width).replaceAll("starty",t.y).replaceAll("half",t.height/2));break}let f=v0(a,t.typeC4Shape.text);switch(n.append("text").attr("fill",s).attr("font-family",f.fontFamily).attr("font-size",f.fontSize-2).attr("font-style","italic").attr("lengthAdjust","spacing").attr("textLength",t.typeC4Shape.width).attr("x",t.x+t.width/2-t.typeC4Shape.width/2).attr("y",t.y+t.typeC4Shape.Y).text("<<"+t.typeC4Shape.text+">>"),t.typeC4Shape.text){case"person":case"external_person":ye(n,48,48,t.x+t.width/2-24,t.y+t.image.Y,r);break}let d=a[t.typeC4Shape.text+"Font"]();return d.fontWeight="bold",d.fontSize=d.fontSize+2,d.fontColor=s,W(a)(t.label.text,n,t.x,t.y+t.label.Y,t.width,t.height,{fill:s},d),d=a[t.typeC4Shape.text+"Font"](),d.fontColor=s,t.techn&&((o=t.techn)==null?void 0:o.text)!==""?W(a)(t.techn.text,n,t.x,t.y+t.techn.Y,t.width,t.height,{fill:s,"font-style":"italic"},d):t.type&&t.type.text!==""&&W(a)(t.type.text,n,t.x,t.y+t.type.Y,t.width,t.height,{fill:s,"font-style":"italic"},d),t.descr&&t.descr.text!==""&&(d=a.personFont(),d.fontColor=s,W(a)(t.descr.text,n,t.x,t.y+t.descr.Y,t.width,t.height,{fill:s},d)),t.height},p0=function(e){e.append("defs").append("symbol").attr("id","database").attr("fill-rule","evenodd").attr("clip-rule","evenodd").append("path").attr("transform","scale(.5)").attr("d","M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z")},y0=function(e){e.append("defs").append("symbol").attr("id","computer").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z")},g0=function(e){e.append("defs").append("symbol").attr("id","clock").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z")},b0=function(e){e.append("defs").append("marker").attr("id","arrowhead").attr("refX",9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z")},_0=function(e){e.append("defs").append("marker").attr("id","arrowend").attr("refX",1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 10 0 L 0 5 L 10 10 z")},x0=function(e){e.append("defs").append("marker").attr("id","filled-head").attr("refX",18).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},m0=function(e){e.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},E0=function(e){const a=e.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",16).attr("refY",4);a.append("path").attr("fill","black").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 9,2 V 6 L16,4 Z"),a.append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 0,1 L 6,7 M 6,1 L 0,7")},v0=(e,t)=>({fontFamily:e[t+"FontFamily"],fontSize:e[t+"FontSize"],fontWeight:e[t+"FontWeight"]}),W=function(){function e(l,i,s,r,n,h,f){const d=i.append("text").attr("x",s+n/2).attr("y",r+h/2+5).style("text-anchor","middle").text(l);o(d,f)}function t(l,i,s,r,n,h,f,d){const{fontSize:p,fontFamily:E,fontWeight:O}=d,R=l.split(Kt.lineBreakRegex);for(let S=0;S=this.data.widthLimit||o>=this.data.widthLimit||this.nextData.cnt>ge)&&(a=this.nextData.startx+t.margin+b.nextLinePaddingX,l=this.nextData.stopy+t.margin*2,this.nextData.stopx=o=a+t.width,this.nextData.starty=this.nextData.stopy,this.nextData.stopy=i=l+t.height,this.nextData.cnt=1),t.x=a,t.y=l,this.updateVal(this.data,"startx",a,Math.min),this.updateVal(this.data,"starty",l,Math.min),this.updateVal(this.data,"stopx",o,Math.max),this.updateVal(this.data,"stopy",i,Math.max),this.updateVal(this.nextData,"startx",a,Math.min),this.updateVal(this.nextData,"starty",l,Math.min),this.updateVal(this.nextData,"stopx",o,Math.max),this.updateVal(this.nextData,"stopy",i,Math.max)}init(t){this.name="",this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,widthLimit:void 0},this.nextData={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,cnt:0},$t(t.db.getConfig())}bumpLastMargin(t){this.data.stopx+=t,this.data.stopy+=t}}const $t=function(e){Se(b,e),e.fontFamily&&(b.personFontFamily=b.systemFontFamily=b.messageFontFamily=e.fontFamily),e.fontSize&&(b.personFontSize=b.systemFontSize=b.messageFontSize=e.fontSize),e.fontWeight&&(b.personFontWeight=b.systemFontWeight=b.messageFontWeight=e.fontWeight)},Rt=(e,t)=>({fontFamily:e[t+"FontFamily"],fontSize:e[t+"FontSize"],fontWeight:e[t+"FontWeight"]}),Bt=e=>({fontFamily:e.boundaryFontFamily,fontSize:e.boundaryFontSize,fontWeight:e.boundaryFontWeight}),k0=e=>({fontFamily:e.messageFontFamily,fontSize:e.messageFontSize,fontWeight:e.messageFontWeight});function I(e,t,a,o,l){if(!t[e].width)if(a)t[e].text=Me(t[e].text,l,o),t[e].textLines=t[e].text.split(Kt.lineBreakRegex).length,t[e].width=l,t[e].height=oe(t[e].text,o);else{let i=t[e].text.split(Kt.lineBreakRegex);t[e].textLines=i.length;let s=0;t[e].height=0,t[e].width=0;for(const r of i)t[e].width=Math.max(wt(r,o),t[e].width),s=oe(r,o),t[e].height=t[e].height+s}}const _e=function(e,t,a){t.x=a.data.startx,t.y=a.data.starty,t.width=a.data.stopx-a.data.startx,t.height=a.data.stopy-a.data.starty,t.label.y=b.c4ShapeMargin-35;let o=t.wrap&&b.wrap,l=Bt(b);l.fontSize=l.fontSize+2,l.fontWeight="bold";let i=wt(t.label.text,l);I("label",t,o,l,i),F.drawBoundary(e,t,b)},xe=function(e,t,a,o){let l=0;for(const i of o){l=0;const s=a[i];let r=Rt(b,s.typeC4Shape.text);switch(r.fontSize=r.fontSize-2,s.typeC4Shape.width=wt("«"+s.typeC4Shape.text+"»",r),s.typeC4Shape.height=r.fontSize+2,s.typeC4Shape.Y=b.c4ShapePadding,l=s.typeC4Shape.Y+s.typeC4Shape.height-4,s.image={width:0,height:0,Y:0},s.typeC4Shape.text){case"person":case"external_person":s.image.width=48,s.image.height=48,s.image.Y=l,l=s.image.Y+s.image.height;break}s.sprite&&(s.image.width=48,s.image.height=48,s.image.Y=l,l=s.image.Y+s.image.height);let n=s.wrap&&b.wrap,h=b.width-b.c4ShapePadding*2,f=Rt(b,s.typeC4Shape.text);if(f.fontSize=f.fontSize+2,f.fontWeight="bold",I("label",s,n,f,h),s.label.Y=l+8,l=s.label.Y+s.label.height,s.type&&s.type.text!==""){s.type.text="["+s.type.text+"]";let E=Rt(b,s.typeC4Shape.text);I("type",s,n,E,h),s.type.Y=l+5,l=s.type.Y+s.type.height}else if(s.techn&&s.techn.text!==""){s.techn.text="["+s.techn.text+"]";let E=Rt(b,s.techn.text);I("techn",s,n,E,h),s.techn.Y=l+5,l=s.techn.Y+s.techn.height}let d=l,p=s.label.width;if(s.descr&&s.descr.text!==""){let E=Rt(b,s.typeC4Shape.text);I("descr",s,n,E,h),s.descr.Y=l+20,l=s.descr.Y+s.descr.height,p=Math.max(s.label.width,s.descr.width),d=l-s.descr.textLines*5}p=p+b.c4ShapePadding,s.width=Math.max(s.width||b.width,p,b.width),s.height=Math.max(s.height||b.height,d,b.height),s.margin=s.margin||b.c4ShapeMargin,e.insert(s),F.drawC4Shape(t,s,b)}e.bumpLastMargin(b.c4ShapeMargin)};class B{constructor(t,a){this.x=t,this.y=a}}let ce=function(e,t){let a=e.x,o=e.y,l=t.x,i=t.y,s=a+e.width/2,r=o+e.height/2,n=Math.abs(a-l),h=Math.abs(o-i),f=h/n,d=e.height/e.width,p=null;return o==i&&al?p=new B(a,r):a==l&&oi&&(p=new B(s,o)),a>l&&o=f?p=new B(a,r+f*e.width/2):p=new B(s-n/h*e.height/2,o+e.height):a=f?p=new B(a+e.width,r+f*e.width/2):p=new B(s+n/h*e.height/2,o+e.height):ai?d>=f?p=new B(a+e.width,r-f*e.width/2):p=new B(s+e.height/2*n/h,o):a>l&&o>i&&(d>=f?p=new B(a,r-e.width/2*f):p=new B(s-e.height/2*n/h,o)),p},A0=function(e,t){let a={x:0,y:0};a.x=t.x+t.width/2,a.y=t.y+t.height/2;let o=ce(e,a);a.x=e.x+e.width/2,a.y=e.y+e.height/2;let l=ce(t,a);return{startPoint:o,endPoint:l}};const C0=function(e,t,a,o){let l=0;for(let i of t){l=l+1;let s=i.wrap&&b.wrap,r=k0(b);o.db.getC4Type()==="C4Dynamic"&&(i.label.text=l+": "+i.label.text);let h=wt(i.label.text,r);I("label",i,s,r,h),i.techn&&i.techn.text!==""&&(h=wt(i.techn.text,r),I("techn",i,s,r,h)),i.descr&&i.descr.text!==""&&(h=wt(i.descr.text,r),I("descr",i,s,r,h));let f=a(i.from),d=a(i.to),p=A0(f,d);i.startPoint=p.startPoint,i.endPoint=p.endPoint}F.drawRels(e,t,b)};function me(e,t,a,o,l){let i=new be(l);i.data.widthLimit=a.data.widthLimit/Math.min(Zt,o.length);for(let[s,r]of o.entries()){let n=0;r.image={width:0,height:0,Y:0},r.sprite&&(r.image.width=48,r.image.height=48,r.image.Y=n,n=r.image.Y+r.image.height);let h=r.wrap&&b.wrap,f=Bt(b);if(f.fontSize=f.fontSize+2,f.fontWeight="bold",I("label",r,h,f,i.data.widthLimit),r.label.Y=n+8,n=r.label.Y+r.label.height,r.type&&r.type.text!==""){r.type.text="["+r.type.text+"]";let O=Bt(b);I("type",r,h,O,i.data.widthLimit),r.type.Y=n+5,n=r.type.Y+r.type.height}if(r.descr&&r.descr.text!==""){let O=Bt(b);O.fontSize=O.fontSize-2,I("descr",r,h,O,i.data.widthLimit),r.descr.Y=n+20,n=r.descr.Y+r.descr.height}if(s==0||s%Zt===0){let O=a.data.startx+b.diagramMarginX,R=a.data.stopy+b.diagramMarginY+n;i.setData(O,O,R,R)}else{let O=i.data.stopx!==i.data.startx?i.data.stopx+b.diagramMarginX:i.data.startx,R=i.data.starty;i.setData(O,O,R,R)}i.name=r.alias;let d=l.db.getC4ShapeArray(r.alias),p=l.db.getC4ShapeKeys(r.alias);p.length>0&&xe(i,e,d,p),t=r.alias;let E=l.db.getBoundarys(t);E.length>0&&me(e,t,i,E,l),r.alias!=="global"&&_e(e,r,i),a.data.stopy=Math.max(i.data.stopy+b.c4ShapeMargin,a.data.stopy),a.data.stopx=Math.max(i.data.stopx+b.c4ShapeMargin,a.data.stopx),Ut=Math.max(Ut,a.data.stopx),Ft=Math.max(Ft,a.data.stopy)}}const w0=function(e,t,a,o){b=Dt().c4;const l=Dt().securityLevel;let i;l==="sandbox"&&(i=Nt("#i"+t));const s=l==="sandbox"?Nt(i.nodes()[0].contentDocument.body):Nt("body");let r=o.db;o.db.setWrap(b.wrap),ge=r.getC4ShapeInRow(),Zt=r.getC4BoundaryInRow(),le.debug(`C:${JSON.stringify(b,null,2)}`);const n=l==="sandbox"?s.select(`[id="${t}"]`):Nt(`[id="${t}"]`);F.insertComputerIcon(n),F.insertDatabaseIcon(n),F.insertClockIcon(n);let h=new be(o);h.setData(b.diagramMarginX,b.diagramMarginX,b.diagramMarginY,b.diagramMarginY),h.data.widthLimit=screen.availWidth,Ut=b.diagramMarginX,Ft=b.diagramMarginY;const f=o.db.getTitle();let d=o.db.getBoundarys("");me(n,"",h,d,o),F.insertArrowHead(n),F.insertArrowEnd(n),F.insertArrowCrossHead(n),F.insertArrowFilledHead(n),C0(n,o.db.getRels(),o.db.getC4Shape,o),h.data.stopx=Ut,h.data.stopy=Ft;const p=h.data;let O=p.stopy-p.starty+2*b.diagramMarginY;const S=p.stopx-p.startx+2*b.diagramMarginX;f&&n.append("text").text(f).attr("x",(p.stopx-p.startx)/2-4*b.diagramMarginX).attr("y",p.starty+b.diagramMarginY),De(n,O,S,b.useMaxWidth);const L=f?60:0;n.attr("viewBox",p.startx-b.diagramMarginX+" -"+(b.diagramMarginY+L)+" "+S+" "+(O+L)),le.debug("models:",p)},he={drawPersonOrSystemArray:xe,drawBoundary:_e,setConf:$t,draw:w0},O0=e=>`.person {
+ stroke: ${e.personBorder};
+ fill: ${e.personBkg};
+ }
+`,T0=O0,S0={parser:Be,db:Jt,renderer:he,styles:T0,init:({c4:e,wrap:t})=>{he.setConf(e),Jt.setWrap(t)}};export{S0 as diagram};
diff --git a/frontend-dist/assets/channel-DsKT-zfZ.js b/frontend-dist/assets/channel-DsKT-zfZ.js
new file mode 100644
index 0000000000000000000000000000000000000000..02354bdafd7de0a4816e72b50944997235ef92af
--- /dev/null
+++ b/frontend-dist/assets/channel-DsKT-zfZ.js
@@ -0,0 +1 @@
+import{aH as o,aI as n}from"./index-BCNM9-Ly.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
diff --git a/frontend-dist/assets/classDiagram-beda092f-wmkRqnN2.js b/frontend-dist/assets/classDiagram-beda092f-wmkRqnN2.js
new file mode 100644
index 0000000000000000000000000000000000000000..45aa332e7a8803c3c2a52153a14fce346d91d5b7
--- /dev/null
+++ b/frontend-dist/assets/classDiagram-beda092f-wmkRqnN2.js
@@ -0,0 +1,2 @@
+import{s as A,d as S,p as G}from"./styles-b4e223ce-CtHeUc7h.js";import{c as v,l as y,d as B,e as W,F as $,A as M,G as I}from"./index-BCNM9-Ly.js";import{G as O}from"./graph-CY8eBbAS.js";import{l as P}from"./layout-CUwpW5wl.js";import{l as X}from"./line-DdWeXrJe.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";let H=0;const Y=function(i,a,t,o,p){const g=function(e){switch(e){case p.db.relationType.AGGREGATION:return"aggregation";case p.db.relationType.EXTENSION:return"extension";case p.db.relationType.COMPOSITION:return"composition";case p.db.relationType.DEPENDENCY:return"dependency";case p.db.relationType.LOLLIPOP:return"lollipop"}};a.points=a.points.filter(e=>!Number.isNaN(e.y));const s=a.points,c=X().x(function(e){return e.x}).y(function(e){return e.y}).curve($),n=i.append("path").attr("d",c(s)).attr("id","edge"+H).attr("class","relation");let r="";o.arrowMarkerAbsolute&&(r=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,r=r.replace(/\(/g,"\\("),r=r.replace(/\)/g,"\\)")),t.relation.lineType==1&&n.attr("class","relation dashed-line"),t.relation.lineType==10&&n.attr("class","relation dotted-line"),t.relation.type1!=="none"&&n.attr("marker-start","url("+r+"#"+g(t.relation.type1)+"Start)"),t.relation.type2!=="none"&&n.attr("marker-end","url("+r+"#"+g(t.relation.type2)+"End)");let f,h;const x=a.points.length;let b=M.calcLabelPosition(a.points);f=b.x,h=b.y;let u,m,w,k;if(x%2!==0&&x>1){let e=M.calcCardinalityPosition(t.relation.type1!=="none",a.points,a.points[0]),d=M.calcCardinalityPosition(t.relation.type2!=="none",a.points,a.points[x-1]);y.debug("cardinality_1_point "+JSON.stringify(e)),y.debug("cardinality_2_point "+JSON.stringify(d)),u=e.x,m=e.y,w=d.x,k=d.y}if(t.title!==void 0){const e=i.append("g").attr("class","classLabel"),d=e.append("text").attr("class","label").attr("x",f).attr("y",h).attr("fill","red").attr("text-anchor","middle").text(t.title);window.label=d;const l=d.node().getBBox();e.insert("rect",":first-child").attr("class","box").attr("x",l.x-o.padding/2).attr("y",l.y-o.padding/2).attr("width",l.width+o.padding).attr("height",l.height+o.padding)}y.info("Rendering relation "+JSON.stringify(t)),t.relationTitle1!==void 0&&t.relationTitle1!=="none"&&i.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",u).attr("y",m).attr("fill","black").attr("font-size","6").text(t.relationTitle1),t.relationTitle2!==void 0&&t.relationTitle2!=="none"&&i.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",w).attr("y",k).attr("fill","black").attr("font-size","6").text(t.relationTitle2),H++},J=function(i,a,t,o){y.debug("Rendering class ",a,t);const p=a.id,g={id:p,label:a.id,width:0,height:0},s=i.append("g").attr("id",o.db.lookUpDomId(p)).attr("class","classGroup");let c;a.link?c=s.append("svg:a").attr("xlink:href",a.link).attr("target",a.linkTarget).append("text").attr("y",t.textHeight+t.padding).attr("x",0):c=s.append("text").attr("y",t.textHeight+t.padding).attr("x",0);let n=!0;a.annotations.forEach(function(d){const l=c.append("tspan").text("«"+d+"»");n||l.attr("dy",t.textHeight),n=!1});let r=C(a);const f=c.append("tspan").text(r).attr("class","title");n||f.attr("dy",t.textHeight);const h=c.node().getBBox().height;let x,b,u;if(a.members.length>0){x=s.append("line").attr("x1",0).attr("y1",t.padding+h+t.dividerMargin/2).attr("y2",t.padding+h+t.dividerMargin/2);const d=s.append("text").attr("x",t.padding).attr("y",h+t.dividerMargin+t.textHeight).attr("fill","white").attr("class","classText");n=!0,a.members.forEach(function(l){_(d,l,n,t),n=!1}),b=d.node().getBBox()}if(a.methods.length>0){u=s.append("line").attr("x1",0).attr("y1",t.padding+h+t.dividerMargin+b.height).attr("y2",t.padding+h+t.dividerMargin+b.height);const d=s.append("text").attr("x",t.padding).attr("y",h+2*t.dividerMargin+b.height+t.textHeight).attr("fill","white").attr("class","classText");n=!0,a.methods.forEach(function(l){_(d,l,n,t),n=!1})}const m=s.node().getBBox();var w=" ";a.cssClasses.length>0&&(w=w+a.cssClasses.join(" "));const e=s.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",m.width+2*t.padding).attr("height",m.height+t.padding+.5*t.dividerMargin).attr("class",w).node().getBBox().width;return c.node().childNodes.forEach(function(d){d.setAttribute("x",(e-d.getBBox().width)/2)}),a.tooltip&&c.insert("title").text(a.tooltip),x&&x.attr("x2",e),u&&u.attr("x2",e),g.width=e,g.height=m.height+t.padding+.5*t.dividerMargin,g},C=function(i){let a=i.id;return i.type&&(a+="<"+I(i.type)+">"),a},Z=function(i,a,t,o){y.debug("Rendering note ",a,t);const p=a.id,g={id:p,text:a.text,width:0,height:0},s=i.append("g").attr("id",p).attr("class","classGroup");let c=s.append("text").attr("y",t.textHeight+t.padding).attr("x",0);const n=JSON.parse(`"${a.text}"`).split(`
+`);n.forEach(function(x){y.debug(`Adding line: ${x}`),c.append("tspan").text(x).attr("class","title").attr("dy",t.textHeight)});const r=s.node().getBBox(),h=s.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",r.width+2*t.padding).attr("height",r.height+n.length*t.textHeight+t.padding+.5*t.dividerMargin).node().getBBox().width;return c.node().childNodes.forEach(function(x){x.setAttribute("x",(h-x.getBBox().width)/2)}),g.width=h,g.height=r.height+n.length*t.textHeight+t.padding+.5*t.dividerMargin,g},_=function(i,a,t,o){const{displayText:p,cssStyle:g}=a.getDisplayDetails(),s=i.append("tspan").attr("x",o.padding).text(p);g!==""&&s.attr("style",a.cssStyle),t||s.attr("dy",o.textHeight)},N={getClassTitleString:C,drawClass:J,drawEdge:Y,drawNote:Z};let T={};const E=20,L=function(i){const a=Object.entries(T).find(t=>t[1].label===i);if(a)return a[0]},R=function(i){i.append("defs").append("marker").attr("id","extensionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),i.append("defs").append("marker").attr("id","extensionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z"),i.append("defs").append("marker").attr("id","compositionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),i.append("defs").append("marker").attr("id","compositionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),i.append("defs").append("marker").attr("id","aggregationStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),i.append("defs").append("marker").attr("id","aggregationEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),i.append("defs").append("marker").attr("id","dependencyStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),i.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},F=function(i,a,t,o){const p=v().class;T={},y.info("Rendering diagram "+i);const g=v().securityLevel;let s;g==="sandbox"&&(s=B("#i"+a));const c=g==="sandbox"?B(s.nodes()[0].contentDocument.body):B("body"),n=c.select(`[id='${a}']`);R(n);const r=new O({multigraph:!0});r.setGraph({isMultiGraph:!0}),r.setDefaultEdgeLabel(function(){return{}});const f=o.db.getClasses(),h=Object.keys(f);for(const e of h){const d=f[e],l=N.drawClass(n,d,p,o);T[l.id]=l,r.setNode(l.id,l),y.info("Org height: "+l.height)}o.db.getRelations().forEach(function(e){y.info("tjoho"+L(e.id1)+L(e.id2)+JSON.stringify(e)),r.setEdge(L(e.id1),L(e.id2),{relation:e},e.title||"DEFAULT")}),o.db.getNotes().forEach(function(e){y.debug(`Adding note: ${JSON.stringify(e)}`);const d=N.drawNote(n,e,p,o);T[d.id]=d,r.setNode(d.id,d),e.class&&e.class in f&&r.setEdge(e.id,L(e.class),{relation:{id1:e.id,id2:e.class,relation:{type1:"none",type2:"none",lineType:10}}},"DEFAULT")}),P(r),r.nodes().forEach(function(e){e!==void 0&&r.node(e)!==void 0&&(y.debug("Node "+e+": "+JSON.stringify(r.node(e))),c.select("#"+(o.db.lookUpDomId(e)||e)).attr("transform","translate("+(r.node(e).x-r.node(e).width/2)+","+(r.node(e).y-r.node(e).height/2)+" )"))}),r.edges().forEach(function(e){e!==void 0&&r.edge(e)!==void 0&&(y.debug("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(r.edge(e))),N.drawEdge(n,r.edge(e),r.edge(e).relation,p,o))});const u=n.node().getBBox(),m=u.width+E*2,w=u.height+E*2;W(n,w,m,p.useMaxWidth);const k=`${u.x-E} ${u.y-E} ${m} ${w}`;y.debug(`viewBox ${k}`),n.attr("viewBox",k)},U={draw:F},tt={parser:G,db:S,renderer:U,styles:A,init:i=>{i.class||(i.class={}),i.class.arrowMarkerAbsolute=i.arrowMarkerAbsolute,S.clear()}};export{tt as diagram};
diff --git a/frontend-dist/assets/classDiagram-v2-2358418a-D17uXNnU.js b/frontend-dist/assets/classDiagram-v2-2358418a-D17uXNnU.js
new file mode 100644
index 0000000000000000000000000000000000000000..29dc9863121c3592726d4ef8a43abfe3d535f2e3
--- /dev/null
+++ b/frontend-dist/assets/classDiagram-v2-2358418a-D17uXNnU.js
@@ -0,0 +1,2 @@
+import{s as M,d as _,p as R}from"./styles-b4e223ce-CtHeUc7h.js";import{l as d,c,d as w,A as B,o as G,p as D,q as E,n as A,k as C}from"./index-BCNM9-Ly.js";import{G as q}from"./graph-CY8eBbAS.js";import{r as z}from"./index-5325376f-CbH2QcFV.js";import"./layout-CUwpW5wl.js";import"./clone-C4pHamD7.js";import"./edges-96097737-CqpaF4BI.js";import"./createText-1719965b-BZ0xZVnk.js";import"./line-DdWeXrJe.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";const S=s=>C.sanitizeText(s,c());let k={dividerMargin:10,padding:5,textHeight:10,curve:void 0};const P=function(s,t,y,a){const e=Object.keys(s);d.info("keys:",e),d.info(s),e.forEach(function(i){var o,r;const l=s[i],p={shape:"rect",id:l.id,domId:l.domId,labelText:S(l.id),labelStyle:"",style:"fill: none; stroke: black",padding:((o=c().flowchart)==null?void 0:o.padding)??((r=c().class)==null?void 0:r.padding)};t.setNode(l.id,p),$(l.classes,t,y,a,l.id),d.info("setNode",p)})},$=function(s,t,y,a,e){const i=Object.keys(s);d.info("keys:",i),d.info(s),i.filter(o=>s[o].parent==e).forEach(function(o){var r,l;const n=s[o],p=n.cssClasses.join(" "),f=D(n.styles),h=n.label??n.id,u=0,b={labelStyle:f.labelStyle,shape:"class_box",labelText:S(h),classData:n,rx:u,ry:u,class:p,style:f.style,id:n.id,domId:n.domId,tooltip:a.db.getTooltip(n.id,e)||"",haveCallback:n.haveCallback,link:n.link,width:n.type==="group"?500:void 0,type:n.type,padding:((r=c().flowchart)==null?void 0:r.padding)??((l=c().class)==null?void 0:l.padding)};t.setNode(n.id,b),e&&t.setParent(n.id,e),d.info("setNode",b)})},F=function(s,t,y,a){d.info(s),s.forEach(function(e,i){var o,r;const l=e,n="",p={labelStyle:"",style:""},f=l.text,h=0,m={labelStyle:p.labelStyle,shape:"note",labelText:S(f),noteData:l,rx:h,ry:h,class:n,style:p.style,id:l.id,domId:l.id,tooltip:"",type:"note",padding:((o=c().flowchart)==null?void 0:o.padding)??((r=c().class)==null?void 0:r.padding)};if(t.setNode(l.id,m),d.info("setNode",m),!l.class||!(l.class in a))return;const b=y+i,x={id:`edgeNote${b}`,classes:"relation",pattern:"dotted",arrowhead:"none",startLabelRight:"",endLabelLeft:"",arrowTypeStart:"none",arrowTypeEnd:"none",style:"fill:none",labelStyle:"",curve:E(k.curve,A)};t.setEdge(l.id,l.class,x,b)})},H=function(s,t){const y=c().flowchart;let a=0;s.forEach(function(e){var i;a++;const o={classes:"relation",pattern:e.relation.lineType==1?"dashed":"solid",id:`id_${e.id1}_${e.id2}_${a}`,arrowhead:e.type==="arrow_open"?"none":"normal",startLabelRight:e.relationTitle1==="none"?"":e.relationTitle1,endLabelLeft:e.relationTitle2==="none"?"":e.relationTitle2,arrowTypeStart:N(e.relation.type1),arrowTypeEnd:N(e.relation.type2),style:"fill:none",labelStyle:"",curve:E(y==null?void 0:y.curve,A)};if(d.info(o,e),e.style!==void 0){const r=D(e.style);o.style=r.style,o.labelStyle=r.labelStyle}e.text=e.title,e.text===void 0?e.style!==void 0&&(o.arrowheadStyle="fill: #333"):(o.arrowheadStyle="fill: #333",o.labelpos="c",((i=c().flowchart)==null?void 0:i.htmlLabels)??c().htmlLabels?(o.labelType="html",o.label=''+e.text+""):(o.labelType="text",o.label=e.text.replace(C.lineBreakRegex,`
+`),e.style===void 0&&(o.style=o.style||"stroke: #333; stroke-width: 1.5px;fill:none"),o.labelStyle=o.labelStyle.replace("color:","fill:"))),t.setEdge(e.id1,e.id2,o,a)})},V=function(s){k={...k,...s}},W=async function(s,t,y,a){d.info("Drawing class - ",t);const e=c().flowchart??c().class,i=c().securityLevel;d.info("config:",e);const o=(e==null?void 0:e.nodeSpacing)??50,r=(e==null?void 0:e.rankSpacing)??50,l=new q({multigraph:!0,compound:!0}).setGraph({rankdir:a.db.getDirection(),nodesep:o,ranksep:r,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}}),n=a.db.getNamespaces(),p=a.db.getClasses(),f=a.db.getRelations(),h=a.db.getNotes();d.info(f),P(n,l,t,a),$(p,l,t,a),H(f,l),F(h,l,f.length+1,p);let u;i==="sandbox"&&(u=w("#i"+t));const m=i==="sandbox"?w(u.nodes()[0].contentDocument.body):w("body"),b=m.select(`[id="${t}"]`),x=m.select("#"+t+" g");if(await z(x,l,["aggregation","extension","composition","dependency","lollipop"],"classDiagram",t),B.insertTitle(b,"classTitleText",(e==null?void 0:e.titleTopMargin)??5,a.db.getDiagramTitle()),G(l,b,e==null?void 0:e.diagramPadding,e==null?void 0:e.useMaxWidth),!(e!=null&&e.htmlLabels)){const T=i==="sandbox"?u.nodes()[0].contentDocument:document,I=T.querySelectorAll('[id="'+t+'"] .edgeLabel .label');for(const g of I){const L=g.getBBox(),v=T.createElementNS("http://www.w3.org/2000/svg","rect");v.setAttribute("rx",0),v.setAttribute("ry",0),v.setAttribute("width",L.width),v.setAttribute("height",L.height),g.insertBefore(v,g.firstChild)}}};function N(s){let t;switch(s){case 0:t="aggregation";break;case 1:t="extension";break;case 2:t="composition";break;case 3:t="dependency";break;case 4:t="lollipop";break;default:t="none"}return t}const J={setConf:V,draw:W},le={parser:R,db:_,renderer:J,styles:M,init:s=>{s.class||(s.class={}),s.class.arrowMarkerAbsolute=s.arrowMarkerAbsolute,_.clear()}};export{le as diagram};
diff --git a/frontend-dist/assets/clone-C4pHamD7.js b/frontend-dist/assets/clone-C4pHamD7.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ad83868a0c3a88767d5f8d57a8eed37daef7e61
--- /dev/null
+++ b/frontend-dist/assets/clone-C4pHamD7.js
@@ -0,0 +1 @@
+import{c as r}from"./graph-CY8eBbAS.js";var e=4;function a(o){return r(o,e)}export{a as c};
diff --git a/frontend-dist/assets/createText-1719965b-BZ0xZVnk.js b/frontend-dist/assets/createText-1719965b-BZ0xZVnk.js
new file mode 100644
index 0000000000000000000000000000000000000000..01b01049c7457b15eabf6567c0039e93c9dad722
--- /dev/null
+++ b/frontend-dist/assets/createText-1719965b-BZ0xZVnk.js
@@ -0,0 +1,7 @@
+import{l as At,b6 as zt,i as It,_ as Tt,b7 as Bt}from"./index-BCNM9-Ly.js";const Lt={};function Ot(n,r){const t=Lt,e=typeof t.includeImageAlt=="boolean"?t.includeImageAlt:!0,u=typeof t.includeHtml=="boolean"?t.includeHtml:!0;return et(n,e,u)}function et(n,r,t){if(Dt(n)){if("value"in n)return n.type==="html"&&!t?"":n.value;if(r&&"alt"in n&&n.alt)return n.alt;if("children"in n)return Vn(n.children,r,t)}return Array.isArray(n)?Vn(n,r,t):""}function Vn(n,r,t){const e=[];let u=-1;for(;++uu?0:u+r:r=r>u?u:r,t=t>0?t:0,e.length<1e4)l=Array.from(e),l.unshift(r,t),n.splice(...l);else for(t&&n.splice(r,t);i0?(tn(n,n.length,0,r),n):r}const Wn={}.hasOwnProperty;function Pt(n){const r={};let t=-1;for(;++tl))return;const T=r.events.length;let H=T,N,V;for(;H--;)if(r.events[H][0]==="exit"&&r.events[H][1].type==="chunkFlow"){if(N){V=r.events[H][1].end;break}N=!0}for(b(e),k=T;kF;){const _=t[D];r.containerState=_[1],_[0].exit.call(r,n)}t.length=F}function j(){u.write([null]),i=void 0,u=void 0,r.containerState._closeFlow=void 0}}function Zt(n,r,t){return O(n,n.attempt(this.parser.constructs.document,r,t),"linePrefix",this.parser.constructs.disable.null.includes("codeIndented")?void 0:4)}function Un(n){if(n===null||Z(n)||Vt(n))return 1;if(Nt(n))return 2}function Ln(n,r,t){const e=[];let u=-1;for(;++u1&&n[t][1].end.offset-n[t][1].start.offset>1?2:1;const f=Object.assign({},n[e][1].end),x=Object.assign({},n[t][1].start);$n(f,-m),$n(x,m),l={type:m>1?"strongSequence":"emphasisSequence",start:f,end:Object.assign({},n[e][1].end)},a={type:m>1?"strongSequence":"emphasisSequence",start:Object.assign({},n[t][1].start),end:x},i={type:m>1?"strongText":"emphasisText",start:Object.assign({},n[e][1].end),end:Object.assign({},n[t][1].start)},u={type:m>1?"strong":"emphasis",start:Object.assign({},l.start),end:Object.assign({},a.end)},n[e][1].end=Object.assign({},l.start),n[t][1].start=Object.assign({},a.end),c=[],n[e][1].end.offset-n[e][1].start.offset&&(c=Y(c,[["enter",n[e][1],r],["exit",n[e][1],r]])),c=Y(c,[["enter",u,r],["enter",l,r],["exit",l,r],["enter",i,r]]),c=Y(c,Ln(r.parser.constructs.insideSpan.null,n.slice(e+1,t),r)),c=Y(c,[["exit",i,r],["enter",a,r],["exit",a,r],["exit",u,r]]),n[t][1].end.offset-n[t][1].start.offset?(p=2,c=Y(c,[["enter",n[t][1],r],["exit",n[t][1],r]])):p=0,tn(n,e-1,t-e+3,c),t=e+c.length-p-2;break}}for(t=-1;++t0&&z(k)?O(n,j,"linePrefix",i+1)(k):j(k)}function j(k){return k===null||C(k)?n.check(Yn,I,D)(k):(n.enter("codeFlowValue"),F(k))}function F(k){return k===null||C(k)?(n.exit("codeFlowValue"),j(k)):(n.consume(k),F)}function D(k){return n.exit("codeFenced"),r(k)}function _(k,T,H){let N=0;return V;function V(w){return k.enter("lineEnding"),k.consume(w),k.exit("lineEnding"),y}function y(w){return k.enter("codeFencedFence"),z(w)?O(k,S,"linePrefix",e.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(w):S(w)}function S(w){return w===a?(k.enter("codeFencedFenceSequence"),P(w)):H(w)}function P(w){return w===a?(N++,k.consume(w),P):N>=l?(k.exit("codeFencedFenceSequence"),z(w)?O(k,R,"whitespace")(w):R(w)):H(w)}function R(w){return w===null||C(w)?(k.exit("codeFencedFence"),T(w)):H(w)}}}function ue(n,r,t){const e=this;return u;function u(l){return l===null?t(l):(n.enter("lineEnding"),n.consume(l),n.exit("lineEnding"),i)}function i(l){return e.parser.lazy[e.now().line]?t(l):r(l)}}const Cn={name:"codeIndented",tokenize:ae},le={tokenize:oe,partial:!0};function ae(n,r,t){const e=this;return u;function u(c){return n.enter("codeIndented"),O(n,i,"linePrefix",5)(c)}function i(c){const p=e.events[e.events.length-1];return p&&p[1].type==="linePrefix"&&p[2].sliceSerialize(p[1],!0).length>=4?l(c):t(c)}function l(c){return c===null?m(c):C(c)?n.attempt(le,l,m)(c):(n.enter("codeFlowValue"),a(c))}function a(c){return c===null||C(c)?(n.exit("codeFlowValue"),l(c)):(n.consume(c),a)}function m(c){return n.exit("codeIndented"),r(c)}}function oe(n,r,t){const e=this;return u;function u(l){return e.parser.lazy[e.now().line]?t(l):C(l)?(n.enter("lineEnding"),n.consume(l),n.exit("lineEnding"),u):O(n,i,"linePrefix",5)(l)}function i(l){const a=e.events[e.events.length-1];return a&&a[1].type==="linePrefix"&&a[2].sliceSerialize(a[1],!0).length>=4?r(l):C(l)?u(l):t(l)}}const se={name:"codeText",tokenize:pe,resolve:ce,previous:he};function ce(n){let r=n.length-4,t=3,e,u;if((n[t][1].type==="lineEnding"||n[t][1].type==="space")&&(n[r][1].type==="lineEnding"||n[r][1].type==="space")){for(e=t;++e=4?r(l):n.interrupt(e.parser.constructs.flow,t,r)(l)}}function at(n,r,t,e,u,i,l,a,m){const c=m||Number.POSITIVE_INFINITY;let p=0;return f;function f(b){return b===60?(n.enter(e),n.enter(u),n.enter(i),n.consume(b),n.exit(i),x):b===null||b===32||b===41||An(b)?t(b):(n.enter(e),n.enter(l),n.enter(a),n.enter("chunkString",{contentType:"string"}),I(b))}function x(b){return b===62?(n.enter(i),n.consume(b),n.exit(i),n.exit(u),n.exit(e),r):(n.enter(a),n.enter("chunkString",{contentType:"string"}),h(b))}function h(b){return b===62?(n.exit("chunkString"),n.exit(a),x(b)):b===null||b===60||C(b)?t(b):(n.consume(b),b===92?A:h)}function A(b){return b===60||b===62||b===92?(n.consume(b),h):h(b)}function I(b){return!p&&(b===null||b===41||Z(b))?(n.exit("chunkString"),n.exit(a),n.exit(l),n.exit(e),r(b)):p999||h===null||h===91||h===93&&!m||h===94&&!a&&"_hiddenFootnoteSupport"in l.parser.constructs?t(h):h===93?(n.exit(i),n.enter(u),n.consume(h),n.exit(u),n.exit(e),r):C(h)?(n.enter("lineEnding"),n.consume(h),n.exit("lineEnding"),p):(n.enter("chunkString",{contentType:"string"}),f(h))}function f(h){return h===null||h===91||h===93||C(h)||a++>999?(n.exit("chunkString"),p(h)):(n.consume(h),m||(m=!z(h)),h===92?x:f)}function x(h){return h===91||h===92||h===93?(n.consume(h),a++,f):f(h)}}function st(n,r,t,e,u,i){let l;return a;function a(x){return x===34||x===39||x===40?(n.enter(e),n.enter(u),n.consume(x),n.exit(u),l=x===40?41:x,m):t(x)}function m(x){return x===l?(n.enter(u),n.consume(x),n.exit(u),n.exit(e),r):(n.enter(i),c(x))}function c(x){return x===l?(n.exit(i),m(l)):x===null?t(x):C(x)?(n.enter("lineEnding"),n.consume(x),n.exit("lineEnding"),O(n,c,"linePrefix")):(n.enter("chunkString",{contentType:"string"}),p(x))}function p(x){return x===l||x===null||C(x)?(n.exit("chunkString"),c(x)):(n.consume(x),x===92?f:p)}function f(x){return x===l||x===92?(n.consume(x),p):p(x)}}function dn(n,r){let t;return e;function e(u){return C(u)?(n.enter("lineEnding"),n.consume(u),n.exit("lineEnding"),t=!0,e):z(u)?O(n,e,t?"linePrefix":"lineSuffix")(u):r(u)}}function xn(n){return n.replace(/[\t\n\r ]+/g," ").replace(/^ | $/g,"").toLowerCase().toUpperCase()}const be={name:"definition",tokenize:Se},ye={tokenize:Fe,partial:!0};function Se(n,r,t){const e=this;let u;return i;function i(h){return n.enter("definition"),l(h)}function l(h){return ot.call(e,n,a,t,"definitionLabel","definitionLabelMarker","definitionLabelString")(h)}function a(h){return u=xn(e.sliceSerialize(e.events[e.events.length-1][1]).slice(1,-1)),h===58?(n.enter("definitionMarker"),n.consume(h),n.exit("definitionMarker"),m):t(h)}function m(h){return Z(h)?dn(n,c)(h):c(h)}function c(h){return at(n,p,t,"definitionDestination","definitionDestinationLiteral","definitionDestinationLiteralMarker","definitionDestinationRaw","definitionDestinationString")(h)}function p(h){return n.attempt(ye,f,f)(h)}function f(h){return z(h)?O(n,x,"whitespace")(h):x(h)}function x(h){return h===null||C(h)?(n.exit("definition"),e.parser.defined.push(u),r(h)):t(h)}}function Fe(n,r,t){return e;function e(a){return Z(a)?dn(n,u)(a):t(a)}function u(a){return st(n,i,t,"definitionTitle","definitionTitleMarker","definitionTitleString")(a)}function i(a){return z(a)?O(n,l,"whitespace")(a):l(a)}function l(a){return a===null||C(a)?r(a):t(a)}}const Ee={name:"hardBreakEscape",tokenize:Ce};function Ce(n,r,t){return e;function e(i){return n.enter("hardBreakEscape"),n.consume(i),u}function u(i){return C(i)?(n.exit("hardBreakEscape"),r(i)):t(i)}}const we={name:"headingAtx",tokenize:ze,resolve:Ae};function Ae(n,r){let t=n.length-2,e=3,u,i;return n[e][1].type==="whitespace"&&(e+=2),t-2>e&&n[t][1].type==="whitespace"&&(t-=2),n[t][1].type==="atxHeadingSequence"&&(e===t-1||t-4>e&&n[t-2][1].type==="whitespace")&&(t-=e+1===t?2:4),t>e&&(u={type:"atxHeadingText",start:n[e][1].start,end:n[t][1].end},i={type:"chunkText",start:n[e][1].start,end:n[t][1].end,contentType:"text"},tn(n,e,t-e+1,[["enter",u,r],["enter",i,r],["exit",i,r],["exit",u,r]])),n}function ze(n,r,t){let e=0;return u;function u(p){return n.enter("atxHeading"),i(p)}function i(p){return n.enter("atxHeadingSequence"),l(p)}function l(p){return p===35&&e++<6?(n.consume(p),l):p===null||Z(p)?(n.exit("atxHeadingSequence"),a(p)):t(p)}function a(p){return p===35?(n.enter("atxHeadingSequence"),m(p)):p===null||C(p)?(n.exit("atxHeading"),r(p)):z(p)?O(n,a,"whitespace")(p):(n.enter("atxHeadingText"),c(p))}function m(p){return p===35?(n.consume(p),m):(n.exit("atxHeadingSequence"),a(p))}function c(p){return p===null||p===35||Z(p)?(n.exit("atxHeadingText"),a(p)):(n.consume(p),c)}}const Ie=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","search","section","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"],Jn=["pre","script","style","textarea"],Te={name:"htmlFlow",tokenize:De,resolveTo:Oe,concrete:!0},Be={tokenize:_e,partial:!0},Le={tokenize:Pe,partial:!0};function Oe(n){let r=n.length;for(;r--&&!(n[r][0]==="enter"&&n[r][1].type==="htmlFlow"););return r>1&&n[r-2][1].type==="linePrefix"&&(n[r][1].start=n[r-2][1].start,n[r+1][1].start=n[r-2][1].start,n.splice(r-2,2)),n}function De(n,r,t){const e=this;let u,i,l,a,m;return c;function c(s){return p(s)}function p(s){return n.enter("htmlFlow"),n.enter("htmlFlowData"),n.consume(s),f}function f(s){return s===33?(n.consume(s),x):s===47?(n.consume(s),i=!0,I):s===63?(n.consume(s),u=3,e.interrupt?r:o):nn(s)?(n.consume(s),l=String.fromCharCode(s),M):t(s)}function x(s){return s===45?(n.consume(s),u=2,h):s===91?(n.consume(s),u=5,a=0,A):nn(s)?(n.consume(s),u=4,e.interrupt?r:o):t(s)}function h(s){return s===45?(n.consume(s),e.interrupt?r:o):t(s)}function A(s){const K="CDATA[";return s===K.charCodeAt(a++)?(n.consume(s),a===K.length?e.interrupt?r:S:A):t(s)}function I(s){return nn(s)?(n.consume(s),l=String.fromCharCode(s),M):t(s)}function M(s){if(s===null||s===47||s===62||Z(s)){const K=s===47,hn=l.toLowerCase();return!K&&!i&&Jn.includes(hn)?(u=1,e.interrupt?r(s):S(s)):Ie.includes(l.toLowerCase())?(u=6,K?(n.consume(s),b):e.interrupt?r(s):S(s)):(u=7,e.interrupt&&!e.parser.lazy[e.now().line]?t(s):i?j(s):F(s))}return s===45||v(s)?(n.consume(s),l+=String.fromCharCode(s),M):t(s)}function b(s){return s===62?(n.consume(s),e.interrupt?r:S):t(s)}function j(s){return z(s)?(n.consume(s),j):V(s)}function F(s){return s===47?(n.consume(s),V):s===58||s===95||nn(s)?(n.consume(s),D):z(s)?(n.consume(s),F):V(s)}function D(s){return s===45||s===46||s===58||s===95||v(s)?(n.consume(s),D):_(s)}function _(s){return s===61?(n.consume(s),k):z(s)?(n.consume(s),_):F(s)}function k(s){return s===null||s===60||s===61||s===62||s===96?t(s):s===34||s===39?(n.consume(s),m=s,T):z(s)?(n.consume(s),k):H(s)}function T(s){return s===m?(n.consume(s),m=null,N):s===null||C(s)?t(s):(n.consume(s),T)}function H(s){return s===null||s===34||s===39||s===47||s===60||s===61||s===62||s===96||Z(s)?_(s):(n.consume(s),H)}function N(s){return s===47||s===62||z(s)?F(s):t(s)}function V(s){return s===62?(n.consume(s),y):t(s)}function y(s){return s===null||C(s)?S(s):z(s)?(n.consume(s),y):t(s)}function S(s){return s===45&&u===2?(n.consume(s),U):s===60&&u===1?(n.consume(s),W):s===62&&u===4?(n.consume(s),J):s===63&&u===3?(n.consume(s),o):s===93&&u===5?(n.consume(s),en):C(s)&&(u===6||u===7)?(n.exit("htmlFlowData"),n.check(Be,rn,P)(s)):s===null||C(s)?(n.exit("htmlFlowData"),P(s)):(n.consume(s),S)}function P(s){return n.check(Le,R,rn)(s)}function R(s){return n.enter("lineEnding"),n.consume(s),n.exit("lineEnding"),w}function w(s){return s===null||C(s)?P(s):(n.enter("htmlFlowData"),S(s))}function U(s){return s===45?(n.consume(s),o):S(s)}function W(s){return s===47?(n.consume(s),l="",G):S(s)}function G(s){if(s===62){const K=l.toLowerCase();return Jn.includes(K)?(n.consume(s),J):S(s)}return nn(s)&&l.length<8?(n.consume(s),l+=String.fromCharCode(s),G):S(s)}function en(s){return s===93?(n.consume(s),o):S(s)}function o(s){return s===62?(n.consume(s),J):s===45&&u===2?(n.consume(s),o):S(s)}function J(s){return s===null||C(s)?(n.exit("htmlFlowData"),rn(s)):(n.consume(s),J)}function rn(s){return n.exit("htmlFlow"),r(s)}}function Pe(n,r,t){const e=this;return u;function u(l){return C(l)?(n.enter("lineEnding"),n.consume(l),n.exit("lineEnding"),i):t(l)}function i(l){return e.parser.lazy[e.now().line]?t(l):r(l)}}function _e(n,r,t){return e;function e(u){return n.enter("lineEnding"),n.consume(u),n.exit("lineEnding"),n.attempt(Sn,r,t)}}const Me={name:"htmlText",tokenize:je};function je(n,r,t){const e=this;let u,i,l;return a;function a(o){return n.enter("htmlText"),n.enter("htmlTextData"),n.consume(o),m}function m(o){return o===33?(n.consume(o),c):o===47?(n.consume(o),_):o===63?(n.consume(o),F):nn(o)?(n.consume(o),H):t(o)}function c(o){return o===45?(n.consume(o),p):o===91?(n.consume(o),i=0,A):nn(o)?(n.consume(o),j):t(o)}function p(o){return o===45?(n.consume(o),h):t(o)}function f(o){return o===null?t(o):o===45?(n.consume(o),x):C(o)?(l=f,W(o)):(n.consume(o),f)}function x(o){return o===45?(n.consume(o),h):f(o)}function h(o){return o===62?U(o):o===45?x(o):f(o)}function A(o){const J="CDATA[";return o===J.charCodeAt(i++)?(n.consume(o),i===J.length?I:A):t(o)}function I(o){return o===null?t(o):o===93?(n.consume(o),M):C(o)?(l=I,W(o)):(n.consume(o),I)}function M(o){return o===93?(n.consume(o),b):I(o)}function b(o){return o===62?U(o):o===93?(n.consume(o),b):I(o)}function j(o){return o===null||o===62?U(o):C(o)?(l=j,W(o)):(n.consume(o),j)}function F(o){return o===null?t(o):o===63?(n.consume(o),D):C(o)?(l=F,W(o)):(n.consume(o),F)}function D(o){return o===62?U(o):F(o)}function _(o){return nn(o)?(n.consume(o),k):t(o)}function k(o){return o===45||v(o)?(n.consume(o),k):T(o)}function T(o){return C(o)?(l=T,W(o)):z(o)?(n.consume(o),T):U(o)}function H(o){return o===45||v(o)?(n.consume(o),H):o===47||o===62||Z(o)?N(o):t(o)}function N(o){return o===47?(n.consume(o),U):o===58||o===95||nn(o)?(n.consume(o),V):C(o)?(l=N,W(o)):z(o)?(n.consume(o),N):U(o)}function V(o){return o===45||o===46||o===58||o===95||v(o)?(n.consume(o),V):y(o)}function y(o){return o===61?(n.consume(o),S):C(o)?(l=y,W(o)):z(o)?(n.consume(o),y):N(o)}function S(o){return o===null||o===60||o===61||o===62||o===96?t(o):o===34||o===39?(n.consume(o),u=o,P):C(o)?(l=S,W(o)):z(o)?(n.consume(o),S):(n.consume(o),R)}function P(o){return o===u?(n.consume(o),u=void 0,w):o===null?t(o):C(o)?(l=P,W(o)):(n.consume(o),P)}function R(o){return o===null||o===34||o===39||o===60||o===61||o===96?t(o):o===47||o===62||Z(o)?N(o):(n.consume(o),R)}function w(o){return o===47||o===62||Z(o)?N(o):t(o)}function U(o){return o===62?(n.consume(o),n.exit("htmlTextData"),n.exit("htmlText"),r):t(o)}function W(o){return n.exit("htmlTextData"),n.enter("lineEnding"),n.consume(o),n.exit("lineEnding"),G}function G(o){return z(o)?O(n,en,"linePrefix",e.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(o):en(o)}function en(o){return n.enter("htmlTextData"),l(o)}}const Dn={name:"labelEnd",tokenize:We,resolveTo:Ve,resolveAll:Ne},Re={tokenize:Qe},qe={tokenize:Ue},He={tokenize:$e};function Ne(n){let r=-1;for(;++r=3&&(c===null||C(c))?(n.exit("thematicBreak"),r(c)):t(c)}function m(c){return c===u?(n.consume(c),e++,m):(n.exit("thematicBreakSequence"),z(c)?O(n,a,"whitespace")(c):a(c))}}const $={name:"list",tokenize:tr,continuation:{tokenize:er},exit:ir},ve={tokenize:ur,partial:!0},nr={tokenize:rr,partial:!0};function tr(n,r,t){const e=this,u=e.events[e.events.length-1];let i=u&&u[1].type==="linePrefix"?u[2].sliceSerialize(u[1],!0).length:0,l=0;return a;function a(h){const A=e.containerState.type||(h===42||h===43||h===45?"listUnordered":"listOrdered");if(A==="listUnordered"?!e.containerState.marker||h===e.containerState.marker:zn(h)){if(e.containerState.type||(e.containerState.type=A,n.enter(A,{_container:!0})),A==="listUnordered")return n.enter("listItemPrefix"),h===42||h===45?n.check(bn,t,c)(h):c(h);if(!e.interrupt||h===49)return n.enter("listItemPrefix"),n.enter("listItemValue"),m(h)}return t(h)}function m(h){return zn(h)&&++l<10?(n.consume(h),m):(!e.interrupt||l<2)&&(e.containerState.marker?h===e.containerState.marker:h===41||h===46)?(n.exit("listItemValue"),c(h)):t(h)}function c(h){return n.enter("listItemMarker"),n.consume(h),n.exit("listItemMarker"),e.containerState.marker=e.containerState.marker||h,n.check(Sn,e.interrupt?t:p,n.attempt(ve,x,f))}function p(h){return e.containerState.initialBlankLine=!0,i++,x(h)}function f(h){return z(h)?(n.enter("listItemPrefixWhitespace"),n.consume(h),n.exit("listItemPrefixWhitespace"),x):t(h)}function x(h){return e.containerState.size=i+e.sliceSerialize(n.exit("listItemPrefix"),!0).length,r(h)}}function er(n,r,t){const e=this;return e.containerState._closeFlow=void 0,n.check(Sn,u,i);function u(a){return e.containerState.furtherBlankLines=e.containerState.furtherBlankLines||e.containerState.initialBlankLine,O(n,r,"listItemIndent",e.containerState.size+1)(a)}function i(a){return e.containerState.furtherBlankLines||!z(a)?(e.containerState.furtherBlankLines=void 0,e.containerState.initialBlankLine=void 0,l(a)):(e.containerState.furtherBlankLines=void 0,e.containerState.initialBlankLine=void 0,n.attempt(nr,r,l)(a))}function l(a){return e.containerState._closeFlow=!0,e.interrupt=void 0,O(n,n.attempt($,r,t),"linePrefix",e.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(a)}}function rr(n,r,t){const e=this;return O(n,u,"listItemIndent",e.containerState.size+1);function u(i){const l=e.events[e.events.length-1];return l&&l[1].type==="listItemIndent"&&l[2].sliceSerialize(l[1],!0).length===e.containerState.size?r(i):t(i)}}function ir(n){n.exit(this.containerState.type)}function ur(n,r,t){const e=this;return O(n,u,"listItemPrefixWhitespace",e.parser.constructs.disable.null.includes("codeIndented")?void 0:5);function u(i){const l=e.events[e.events.length-1];return!z(i)&&l&&l[1].type==="listItemPrefixWhitespace"?r(i):t(i)}}const Kn={name:"setextUnderline",tokenize:ar,resolveTo:lr};function lr(n,r){let t=n.length,e,u,i;for(;t--;)if(n[t][0]==="enter"){if(n[t][1].type==="content"){e=t;break}n[t][1].type==="paragraph"&&(u=t)}else n[t][1].type==="content"&&n.splice(t,1),!i&&n[t][1].type==="definition"&&(i=t);const l={type:"setextHeading",start:Object.assign({},n[u][1].start),end:Object.assign({},n[n.length-1][1].end)};return n[u][1].type="setextHeadingText",i?(n.splice(u,0,["enter",l,r]),n.splice(i+1,0,["exit",n[e][1],r]),n[e][1].end=Object.assign({},n[i][1].end)):n[e][1]=l,n.push(["exit",l,r]),n}function ar(n,r,t){const e=this;let u;return i;function i(c){let p=e.events.length,f;for(;p--;)if(e.events[p][1].type!=="lineEnding"&&e.events[p][1].type!=="linePrefix"&&e.events[p][1].type!=="content"){f=e.events[p][1].type==="paragraph";break}return!e.parser.lazy[e.now().line]&&(e.interrupt||f)?(n.enter("setextHeadingLine"),u=c,l(c)):t(c)}function l(c){return n.enter("setextHeadingLineSequence"),a(c)}function a(c){return c===u?(n.consume(c),a):(n.exit("setextHeadingLineSequence"),z(c)?O(n,m,"lineSuffix")(c):m(c))}function m(c){return c===null||C(c)?(n.exit("setextHeadingLine"),r(c)):t(c)}}const or={tokenize:sr};function sr(n){const r=this,t=n.attempt(Sn,e,n.attempt(this.parser.constructs.flowInitial,u,O(n,n.attempt(this.parser.constructs.flow,u,n.attempt(me,u)),"linePrefix")));return t;function e(i){if(i===null){n.consume(i);return}return n.enter("lineEndingBlank"),n.consume(i),n.exit("lineEndingBlank"),r.currentConstruct=void 0,t}function u(i){if(i===null){n.consume(i);return}return n.enter("lineEnding"),n.consume(i),n.exit("lineEnding"),r.currentConstruct=void 0,t}}const cr={resolveAll:ht()},hr=ct("string"),pr=ct("text");function ct(n){return{tokenize:r,resolveAll:ht(n==="text"?fr:void 0)};function r(t){const e=this,u=this.parser.constructs[n],i=t.attempt(u,l,a);return l;function l(p){return c(p)?i(p):a(p)}function a(p){if(p===null){t.consume(p);return}return t.enter("data"),t.consume(p),m}function m(p){return c(p)?(t.exit("data"),i(p)):(t.consume(p),m)}function c(p){if(p===null)return!0;const f=u[p];let x=-1;if(f)for(;++x-1){const a=l[0];typeof a=="string"?l[0]=a.slice(e):l.shift()}i>0&&l.push(n[u].slice(0,i))}return l}function gr(n,r){let t=-1;const e=[];let u;for(;++t13&&t<32||t>126&&t<160||t>55295&&t<57344||t>64975&&t<65008||(t&65535)===65535||(t&65535)===65534||t>1114111?"�":String.fromCharCode(t)}const Br=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function Lr(n){return n.replace(Br,Or)}function Or(n,r,t){if(r)return r;if(t.charCodeAt(0)===35){const u=t.charCodeAt(1),i=u===120||u===88;return pt(t.slice(i?2:1),i?16:10)}return On(t)||n}function yn(n){return!n||typeof n!="object"?"":"position"in n||"type"in n?vn(n.position):"start"in n||"end"in n?vn(n):"line"in n||"column"in n?Tn(n):""}function Tn(n){return nt(n&&n.line)+":"+nt(n&&n.column)}function vn(n){return Tn(n&&n.start)+"-"+Tn(n&&n.end)}function nt(n){return n&&typeof n=="number"?n:1}const ft={}.hasOwnProperty,mt=function(n,r,t){return typeof r!="string"&&(t=r,r=void 0),Dr(t)(Tr(zr(t).document().write(Ir()(n,r,!0))))};function Dr(n){const r={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:a(Hn),autolinkProtocol:y,autolinkEmail:y,atxHeading:a(jn),blockQuote:a(Fn),characterEscape:y,characterReference:y,codeFenced:a(Mn),codeFencedFenceInfo:m,codeFencedFenceMeta:m,codeIndented:a(Mn,m),codeText:a(kt,m),codeTextData:y,data:y,codeFlowValue:y,definition:a(dt),definitionDestinationString:m,definitionLabelString:m,definitionTitleString:m,emphasis:a(bt),hardBreakEscape:a(Rn),hardBreakTrailing:a(Rn),htmlFlow:a(qn,m),htmlFlowData:y,htmlText:a(qn,m),htmlTextData:y,image:a(yt),label:m,link:a(Hn),listItem:a(St),listItemValue:A,listOrdered:a(Nn,h),listUnordered:a(Nn),paragraph:a(Ft),reference:hn,referenceString:m,resourceDestinationString:m,resourceTitleString:m,setextHeading:a(jn),strong:a(Et),thematicBreak:a(wt)},exit:{atxHeading:p(),atxHeadingSequence:T,autolink:p(),autolinkEmail:mn,autolinkProtocol:fn,blockQuote:p(),characterEscapeValue:S,characterReferenceMarkerHexadecimal:pn,characterReferenceMarkerNumeric:pn,characterReferenceValue:an,codeFenced:p(j),codeFencedFence:b,codeFencedFenceInfo:I,codeFencedFenceMeta:M,codeFlowValue:S,codeIndented:p(F),codeText:p(W),codeTextData:S,data:S,definition:p(),definitionDestinationString:k,definitionLabelString:D,definitionTitleString:_,emphasis:p(),hardBreakEscape:p(R),hardBreakTrailing:p(R),htmlFlow:p(w),htmlFlowData:S,htmlText:p(U),htmlTextData:S,image:p(en),label:J,labelText:o,lineEnding:P,link:p(G),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:Q,resourceDestinationString:rn,resourceTitleString:s,resource:K,setextHeading:p(V),setextHeadingLineSequence:N,setextHeadingText:H,strong:p(),thematicBreak:p()}};xt(r,(n||{}).mdastExtensions||[]);const t={};return e;function e(g){let d={type:"root",children:[]};const E={stack:[d],tokenStack:[],config:r,enter:c,exit:f,buffer:m,resume:x,setData:i,getData:l},B=[];let L=-1;for(;++L0){const X=E.tokenStack[E.tokenStack.length-1];(X[1]||tt).call(E,void 0,X[0])}for(d.position={start:sn(g.length>0?g[0][1].start:{line:1,column:1,offset:0}),end:sn(g.length>0?g[g.length-2][1].end:{line:1,column:1,offset:0})},L=-1;++L{p!==0&&(u++,e.push([])),c.split(" ").forEach(f=>{f&&e[u].push({content:f,type:a})})}):(l.type==="strong"||l.type==="emphasis")&&l.children.forEach(m=>{i(m,l.type)})}return t.forEach(l=>{l.type==="paragraph"&&l.children.forEach(a=>{i(a)})}),e}function jr(n){const{children:r}=mt(n);function t(e){return e.type==="text"?e.value.replace(/\n/g,"
"):e.type==="strong"?`${e.children.map(t).join("")}`:e.type==="emphasis"?`${e.children.map(t).join("")}`:e.type==="paragraph"?`${e.children.map(t).join("")}
`:`Unsupported markdown: ${e.type}`}return r.map(t).join("")}function Rr(n){return Intl.Segmenter?[...new Intl.Segmenter().segment(n)].map(r=>r.segment):[...n]}function qr(n,r){const t=Rr(r.content);return gt(n,[],t,r.type)}function gt(n,r,t,e){if(t.length===0)return[{content:r.join(""),type:e},{content:"",type:e}];const[u,...i]=t,l=[...r,u];return n([{content:l.join(""),type:e}])?gt(n,l,i,e):(r.length===0&&u&&(r.push(u),t.shift()),[{content:r.join(""),type:e},{content:t.join(""),type:e}])}function Hr(n,r){if(n.some(({content:t})=>t.includes(`
+`)))throw new Error("splitLineToFitWidth does not support newlines in the line");return Bn(n,r)}function Bn(n,r,t=[],e=[]){if(n.length===0)return e.length>0&&t.push(e),t.length>0?t:[];let u="";n[0].content===" "&&(u=" ",n.shift());const i=n.shift()??{content:" ",type:"normal"},l=[...e];if(u!==""&&l.push({content:u,type:"normal"}),l.push(i),r(l))return Bn(n,r,t,l);if(e.length>0)t.push(e),n.unshift(i);else if(i.content){const[a,m]=qr(r,i);t.push([a]),m.content&&n.unshift(m)}return Bn(n,r,t)}function Nr(n,r){r&&n.attr("style",r)}function Vr(n,r,t,e,u=!1){const i=n.append("foreignObject"),l=i.append("xhtml:div"),a=r.label,m=r.isNode?"nodeLabel":"edgeLabel";l.html(It(`
+ "+a+"",Tt())),Nr(l,r.labelStyle),l.style("display","table-cell"),l.style("white-space","nowrap"),l.style("max-width",t+"px"),l.attr("xmlns","http://www.w3.org/1999/xhtml"),u&&l.attr("class","labelBkg");let c=l.node().getBoundingClientRect();return c.width===t&&(l.style("display","table"),l.style("white-space","break-spaces"),l.style("width",t+"px"),c=l.node().getBoundingClientRect()),i.style("width",c.width),i.style("height",c.height),i.node()}function Pn(n,r,t){return n.append("tspan").attr("class","text-outer-tspan").attr("x",0).attr("y",r*t-.1+"em").attr("dy",t+"em")}function Wr(n,r,t){const e=n.append("text"),u=Pn(e,1,r);_n(u,t);const i=u.node().getComputedTextLength();return e.remove(),i}function $r(n,r,t){var e;const u=n.append("text"),i=Pn(u,1,r);_n(i,[{content:t,type:"normal"}]);const l=(e=i.node())==null?void 0:e.getBoundingClientRect();return l&&u.remove(),l}function Qr(n,r,t,e=!1){const i=r.append("g"),l=i.insert("rect").attr("class","background"),a=i.append("text").attr("y","-10.1");let m=0;for(const c of t){const p=x=>Wr(i,1.1,x)<=n,f=p(c)?[c]:Hr(c,p);for(const x of f){const h=Pn(a,m,1.1);_n(h,x),m++}}if(e){const c=a.node().getBBox(),p=2;return l.attr("x",-p).attr("y",-p).attr("width",c.width+2*p).attr("height",c.height+2*p),i.node()}else return a.node()}function _n(n,r){n.text(""),r.forEach((t,e)=>{const u=n.append("tspan").attr("font-style",t.type==="emphasis"?"italic":"normal").attr("class","text-inner-tspan").attr("font-weight",t.type==="strong"?"bold":"normal");e===0?u.text(t.content):u.text(" "+t.content)})}const Zr=(n,r="",{style:t="",isTitle:e=!1,classes:u="",useHtmlLabels:i=!0,isNode:l=!0,width:a=200,addSvgBackground:m=!1}={})=>{if(At.info("createText",r,t,e,u,i,l,m),i){const c=jr(r),p={isNode:l,label:zt(c).replace(/fa[blrs]?:fa-[\w-]+/g,x=>``),labelStyle:t.replace("fill:","color:")};return Vr(n,p,a,u,m)}else{const c=Mr(r);return Qr(a,n,c,m)}};export{Zr as a,$r as c};
diff --git a/frontend-dist/assets/edges-96097737-CqpaF4BI.js b/frontend-dist/assets/edges-96097737-CqpaF4BI.js
new file mode 100644
index 0000000000000000000000000000000000000000..64d08a5b44516921f24445ca730d2738c3bf0591
--- /dev/null
+++ b/frontend-dist/assets/edges-96097737-CqpaF4BI.js
@@ -0,0 +1,4 @@
+import{l as g,F as lt,c as b,r as H,d as E,A as j,i as Q,b6 as V}from"./index-BCNM9-Ly.js";import{a as st}from"./createText-1719965b-BZ0xZVnk.js";import{l as ct}from"./line-DdWeXrJe.js";const ht=(a,t,e,i)=>{t.forEach(l=>{wt[l](a,e,i)})},ot=(a,t,e)=>{g.trace("Making markers for ",e),a.append("defs").append("marker").attr("id",e+"_"+t+"-extensionStart").attr("class","marker extension "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),a.append("defs").append("marker").attr("id",e+"_"+t+"-extensionEnd").attr("class","marker extension "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},yt=(a,t,e)=>{a.append("defs").append("marker").attr("id",e+"_"+t+"-compositionStart").attr("class","marker composition "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),a.append("defs").append("marker").attr("id",e+"_"+t+"-compositionEnd").attr("class","marker composition "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},pt=(a,t,e)=>{a.append("defs").append("marker").attr("id",e+"_"+t+"-aggregationStart").attr("class","marker aggregation "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),a.append("defs").append("marker").attr("id",e+"_"+t+"-aggregationEnd").attr("class","marker aggregation "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},ft=(a,t,e)=>{a.append("defs").append("marker").attr("id",e+"_"+t+"-dependencyStart").attr("class","marker dependency "+t).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),a.append("defs").append("marker").attr("id",e+"_"+t+"-dependencyEnd").attr("class","marker dependency "+t).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},xt=(a,t,e)=>{a.append("defs").append("marker").attr("id",e+"_"+t+"-lollipopStart").attr("class","marker lollipop "+t).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),a.append("defs").append("marker").attr("id",e+"_"+t+"-lollipopEnd").attr("class","marker lollipop "+t).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},dt=(a,t,e)=>{a.append("marker").attr("id",e+"_"+t+"-pointEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",6).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),a.append("marker").attr("id",e+"_"+t+"-pointStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},gt=(a,t,e)=>{a.append("marker").attr("id",e+"_"+t+"-circleEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),a.append("marker").attr("id",e+"_"+t+"-circleStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},ut=(a,t,e)=>{a.append("marker").attr("id",e+"_"+t+"-crossEnd").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),a.append("marker").attr("id",e+"_"+t+"-crossStart").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},bt=(a,t,e)=>{a.append("defs").append("marker").attr("id",e+"_"+t+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","strokeWidth").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},wt={extension:ot,composition:yt,aggregation:pt,dependency:ft,lollipop:xt,point:dt,circle:gt,cross:ut,barb:bt},hr=ht;function mt(a,t){t&&a.attr("style",t)}function kt(a,t){const e=E(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),i=e.append("xhtml:div"),l=a.label,r=a.isNode?"nodeLabel":"edgeLabel";return i.html(Q('"+l+"",t)),mt(i,a.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap"),i.attr("xmlns","http://www.w3.org/1999/xhtml"),e.node()}const vt=(a,t,e,i)=>{let l=a||"";typeof l=="object"&&(l=l[0]);const r=b();if(H(r.flowchart.htmlLabels)){l=l.replace(/\\n|\n/g,"
"),g.debug("vertexText"+l);const s={isNode:i,label:V(l).replace(/fa[blrs]?:fa-[\w-]+/g,c=>``),labelStyle:t.replace("fill:","color:")};return kt(s,r)}else{const s=document.createElementNS("http://www.w3.org/2000/svg","text");s.setAttribute("style",t.replace("color:","fill:"));let n=[];typeof l=="string"?n=l.split(/\\n|\n|
/gi):Array.isArray(l)?n=l:n=[];for(const c of n){const o=document.createElementNS("http://www.w3.org/2000/svg","tspan");o.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),o.setAttribute("dy","1em"),o.setAttribute("x","0"),e?o.setAttribute("class","title-row"):o.setAttribute("class","row"),o.textContent=c.trim(),s.appendChild(o)}return s}},R=vt,M=async(a,t,e,i)=>{let l;const r=t.useHtmlLabels||H(b().flowchart.htmlLabels);e?l=e:l="node default";const s=a.insert("g").attr("class",l).attr("id",t.domId||t.id),n=s.insert("g").attr("class","label").attr("style",t.labelStyle);let c;t.labelText===void 0?c="":c=typeof t.labelText=="string"?t.labelText:t.labelText[0];const o=n.node();let h;t.labelType==="markdown"?h=st(n,Q(V(c),b()),{useHtmlLabels:r,width:t.width||b().flowchart.wrappingWidth,classes:"markdown-node-label"}):h=o.appendChild(R(Q(V(c),b()),t.labelStyle,!1,i));let y=h.getBBox();const f=t.padding/2;if(H(b().flowchart.htmlLabels)){const p=h.children[0],d=E(h),k=p.getElementsByTagName("img");if(k){const x=c.replace(/
]*>/g,"").trim()==="";await Promise.all([...k].map(u=>new Promise(S=>{function B(){if(u.style.display="flex",u.style.flexDirection="column",x){const C=b().fontSize?b().fontSize:window.getComputedStyle(document.body).fontSize,A=parseInt(C,10)*5+"px";u.style.minWidth=A,u.style.maxWidth=A}else u.style.width="100%";S(u)}setTimeout(()=>{u.complete&&B()}),u.addEventListener("error",B),u.addEventListener("load",B)})))}y=p.getBoundingClientRect(),d.attr("width",y.width),d.attr("height",y.height)}return r?n.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"):n.attr("transform","translate(0, "+-y.height/2+")"),t.centerLabel&&n.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),n.insert("rect",":first-child"),{shapeSvg:s,bbox:y,halfPadding:f,label:n}},m=(a,t)=>{const e=t.node().getBBox();a.width=e.width,a.height=e.height};function I(a,t,e,i){return a.insert("polygon",":first-child").attr("points",i.map(function(l){return l.x+","+l.y}).join(" ")).attr("class","label-container").attr("transform","translate("+-t/2+","+e/2+")")}function Lt(a,t){return a.intersect(t)}function it(a,t,e,i){var l=a.x,r=a.y,s=l-i.x,n=r-i.y,c=Math.sqrt(t*t*n*n+e*e*s*s),o=Math.abs(t*e*s/c);i.x0}function Tt(a,t,e){var i=a.x,l=a.y,r=[],s=Number.POSITIVE_INFINITY,n=Number.POSITIVE_INFINITY;typeof t.forEach=="function"?t.forEach(function(d){s=Math.min(s,d.x),n=Math.min(n,d.y)}):(s=Math.min(s,t.x),n=Math.min(n,t.y));for(var c=i-a.width/2-s,o=l-a.height/2-n,h=0;h1&&r.sort(function(d,k){var x=d.x-e.x,u=d.y-e.y,S=Math.sqrt(x*x+u*u),B=k.x-e.x,C=k.y-e.y,X=Math.sqrt(B*B+C*C);return S{var e=a.x,i=a.y,l=t.x-e,r=t.y-i,s=a.width/2,n=a.height/2,c,o;return Math.abs(r)*s>Math.abs(l)*n?(r<0&&(n=-n),c=r===0?0:n*l/r,o=n):(l<0&&(s=-s),c=s,o=l===0?0:s*r/l),{x:e+c,y:i+o}},Et=Bt,w={node:Lt,circle:St,ellipse:it,polygon:Tt,rect:Et},Ct=async(a,t)=>{t.useHtmlLabels||b().flowchart.htmlLabels||(t.centerLabel=!0);const{shapeSvg:i,bbox:l,halfPadding:r}=await M(a,t,"node "+t.classes,!0);g.info("Classes = ",t.classes);const s=i.insert("rect",":first-child");return s.attr("rx",t.rx).attr("ry",t.ry).attr("x",-l.width/2-r).attr("y",-l.height/2-r).attr("width",l.width+t.padding).attr("height",l.height+t.padding),m(t,s),t.intersect=function(n){return w.rect(t,n)},i},$t=Ct,_t=a=>{const t=new Set;for(const e of a)switch(e){case"x":t.add("right"),t.add("left");break;case"y":t.add("up"),t.add("down");break;default:t.add(e);break}return t},Rt=(a,t,e)=>{const i=_t(a),l=2,r=t.height+2*e.padding,s=r/l,n=t.width+2*s+e.padding,c=e.padding/2;return i.has("right")&&i.has("left")&&i.has("up")&&i.has("down")?[{x:0,y:0},{x:s,y:0},{x:n/2,y:2*c},{x:n-s,y:0},{x:n,y:0},{x:n,y:-r/3},{x:n+2*c,y:-r/2},{x:n,y:-2*r/3},{x:n,y:-r},{x:n-s,y:-r},{x:n/2,y:-r-2*c},{x:s,y:-r},{x:0,y:-r},{x:0,y:-2*r/3},{x:-2*c,y:-r/2},{x:0,y:-r/3}]:i.has("right")&&i.has("left")&&i.has("up")?[{x:s,y:0},{x:n-s,y:0},{x:n,y:-r/2},{x:n-s,y:-r},{x:s,y:-r},{x:0,y:-r/2}]:i.has("right")&&i.has("left")&&i.has("down")?[{x:0,y:0},{x:s,y:-r},{x:n-s,y:-r},{x:n,y:0}]:i.has("right")&&i.has("up")&&i.has("down")?[{x:0,y:0},{x:n,y:-s},{x:n,y:-r+s},{x:0,y:-r}]:i.has("left")&&i.has("up")&&i.has("down")?[{x:n,y:0},{x:0,y:-s},{x:0,y:-r+s},{x:n,y:-r}]:i.has("right")&&i.has("left")?[{x:s,y:0},{x:s,y:-c},{x:n-s,y:-c},{x:n-s,y:0},{x:n,y:-r/2},{x:n-s,y:-r},{x:n-s,y:-r+c},{x:s,y:-r+c},{x:s,y:-r},{x:0,y:-r/2}]:i.has("up")&&i.has("down")?[{x:n/2,y:0},{x:0,y:-c},{x:s,y:-c},{x:s,y:-r+c},{x:0,y:-r+c},{x:n/2,y:-r},{x:n,y:-r+c},{x:n-s,y:-r+c},{x:n-s,y:-c},{x:n,y:-c}]:i.has("right")&&i.has("up")?[{x:0,y:0},{x:n,y:-s},{x:0,y:-r}]:i.has("right")&&i.has("down")?[{x:0,y:0},{x:n,y:0},{x:0,y:-r}]:i.has("left")&&i.has("up")?[{x:n,y:0},{x:0,y:-s},{x:n,y:-r}]:i.has("left")&&i.has("down")?[{x:n,y:0},{x:0,y:0},{x:n,y:-r}]:i.has("right")?[{x:s,y:-c},{x:s,y:-c},{x:n-s,y:-c},{x:n-s,y:0},{x:n,y:-r/2},{x:n-s,y:-r},{x:n-s,y:-r+c},{x:s,y:-r+c},{x:s,y:-r+c}]:i.has("left")?[{x:s,y:0},{x:s,y:-c},{x:n-s,y:-c},{x:n-s,y:-r+c},{x:s,y:-r+c},{x:s,y:-r},{x:0,y:-r/2}]:i.has("up")?[{x:s,y:-c},{x:s,y:-r+c},{x:0,y:-r+c},{x:n/2,y:-r},{x:n,y:-r+c},{x:n-s,y:-r+c},{x:n-s,y:-c}]:i.has("down")?[{x:n/2,y:0},{x:0,y:-c},{x:s,y:-c},{x:s,y:-r+c},{x:n-s,y:-r+c},{x:n-s,y:-c},{x:n,y:-c}]:[{x:0,y:0}]},K=a=>a?" "+a:"",_=(a,t)=>`node default${K(a.classes)} ${K(a.class)}`,P=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=l+r,n=[{x:s/2,y:0},{x:s,y:-s/2},{x:s/2,y:-s},{x:0,y:-s/2}];g.info("Question main (Circle)");const c=I(e,s,s,n);return c.attr("style",t.style),m(t,c),t.intersect=function(o){return g.warn("Intersect called"),w.polygon(t,n,o)},e},Ht=(a,t)=>{const e=a.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=28,l=[{x:0,y:i/2},{x:i/2,y:0},{x:0,y:-i/2},{x:-i/2,y:0}];return e.insert("polygon",":first-child").attr("points",l.map(function(s){return s.x+","+s.y}).join(" ")).attr("class","state-start").attr("r",7).attr("width",28).attr("height",28),t.width=28,t.height=28,t.intersect=function(s){return w.circle(t,14,s)},e},It=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=4,r=i.height+t.padding,s=r/l,n=i.width+2*s+t.padding,c=[{x:s,y:0},{x:n-s,y:0},{x:n,y:-r/2},{x:n-s,y:-r},{x:s,y:-r},{x:0,y:-r/2}],o=I(e,n,r,c);return o.attr("style",t.style),m(t,o),t.intersect=function(h){return w.polygon(t,c,h)},e},Nt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,void 0,!0),l=2,r=i.height+2*t.padding,s=r/l,n=i.width+2*s+t.padding,c=Rt(t.directions,i,t),o=I(e,n,r,c);return o.attr("style",t.style),m(t,o),t.intersect=function(h){return w.polygon(t,c,h)},e},Ot=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:-r/2,y:0},{x:l,y:0},{x:l,y:-r},{x:-r/2,y:-r},{x:0,y:-r/2}];return I(e,l,r,s).attr("style",t.style),t.width=l+r,t.height=r,t.intersect=function(c){return w.polygon(t,s,c)},e},Wt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:-2*r/6,y:0},{x:l-r/6,y:0},{x:l+2*r/6,y:-r},{x:r/6,y:-r}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},Xt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:2*r/6,y:0},{x:l+r/6,y:0},{x:l-2*r/6,y:-r},{x:-r/6,y:-r}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},Yt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:-2*r/6,y:0},{x:l+2*r/6,y:0},{x:l-r/6,y:-r},{x:r/6,y:-r}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},At=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:r/6,y:0},{x:l-r/6,y:0},{x:l+2*r/6,y:-r},{x:-2*r/6,y:-r}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},Dt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:0,y:0},{x:l+r/2,y:0},{x:l,y:-r/2},{x:l+r/2,y:-r},{x:0,y:-r}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},jt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=l/2,s=r/(2.5+l/50),n=i.height+s+t.padding,c="M 0,"+s+" a "+r+","+s+" 0,0,0 "+l+" 0 a "+r+","+s+" 0,0,0 "+-l+" 0 l 0,"+n+" a "+r+","+s+" 0,0,0 "+l+" 0 l 0,"+-n,o=e.attr("label-offset-y",s).insert("path",":first-child").attr("style",t.style).attr("d",c).attr("transform","translate("+-l/2+","+-(n/2+s)+")");return m(t,o),t.intersect=function(h){const y=w.rect(t,h),f=y.x-t.x;if(r!=0&&(Math.abs(f)t.height/2-s)){let p=s*s*(1-f*f/(r*r));p!=0&&(p=Math.sqrt(p)),p=s-p,h.y-t.y>0&&(p=-p),y.y+=p}return y},e},Ut=async(a,t)=>{const{shapeSvg:e,bbox:i,halfPadding:l}=await M(a,t,"node "+t.classes+" "+t.class,!0),r=e.insert("rect",":first-child"),s=t.positioned?t.width:i.width+t.padding,n=t.positioned?t.height:i.height+t.padding,c=t.positioned?-s/2:-i.width/2-l,o=t.positioned?-n/2:-i.height/2-l;if(r.attr("class","basic label-container").attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("x",c).attr("y",o).attr("width",s).attr("height",n),t.props){const h=new Set(Object.keys(t.props));t.props.borders&&(q(r,t.props.borders,s,n),h.delete("borders")),h.forEach(y=>{g.warn(`Unknown node property ${y}`)})}return m(t,r),t.intersect=function(h){return w.rect(t,h)},e},zt=async(a,t)=>{const{shapeSvg:e,bbox:i,halfPadding:l}=await M(a,t,"node "+t.classes,!0),r=e.insert("rect",":first-child"),s=t.positioned?t.width:i.width+t.padding,n=t.positioned?t.height:i.height+t.padding,c=t.positioned?-s/2:-i.width/2-l,o=t.positioned?-n/2:-i.height/2-l;if(r.attr("class","basic cluster composite label-container").attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("x",c).attr("y",o).attr("width",s).attr("height",n),t.props){const h=new Set(Object.keys(t.props));t.props.borders&&(q(r,t.props.borders,s,n),h.delete("borders")),h.forEach(y=>{g.warn(`Unknown node property ${y}`)})}return m(t,r),t.intersect=function(h){return w.rect(t,h)},e},Zt=async(a,t)=>{const{shapeSvg:e}=await M(a,t,"label",!0);g.trace("Classes = ",t.class);const i=e.insert("rect",":first-child"),l=0,r=0;if(i.attr("width",l).attr("height",r),e.attr("class","label edgeLabel"),t.props){const s=new Set(Object.keys(t.props));t.props.borders&&(q(i,t.props.borders,l,r),s.delete("borders")),s.forEach(n=>{g.warn(`Unknown node property ${n}`)})}return m(t,i),t.intersect=function(s){return w.rect(t,s)},e};function q(a,t,e,i){const l=[],r=n=>{l.push(n,0)},s=n=>{l.push(0,n)};t.includes("t")?(g.debug("add top border"),r(e)):s(e),t.includes("r")?(g.debug("add right border"),r(i)):s(i),t.includes("b")?(g.debug("add bottom border"),r(e)):s(e),t.includes("l")?(g.debug("add left border"),r(i)):s(i),a.attr("stroke-dasharray",l.join(" "))}const Ft=(a,t)=>{let e;t.classes?e="node "+t.classes:e="node default";const i=a.insert("g").attr("class",e).attr("id",t.domId||t.id),l=i.insert("rect",":first-child"),r=i.insert("line"),s=i.insert("g").attr("class","label"),n=t.labelText.flat?t.labelText.flat():t.labelText;let c="";typeof n=="object"?c=n[0]:c=n,g.info("Label text abc79",c,n,typeof n=="object");const o=s.node().appendChild(R(c,t.labelStyle,!0,!0));let h={width:0,height:0};if(H(b().flowchart.htmlLabels)){const k=o.children[0],x=E(o);h=k.getBoundingClientRect(),x.attr("width",h.width),x.attr("height",h.height)}g.info("Text 2",n);const y=n.slice(1,n.length);let f=o.getBBox();const p=s.node().appendChild(R(y.join?y.join("
"):y,t.labelStyle,!0,!0));if(H(b().flowchart.htmlLabels)){const k=p.children[0],x=E(p);h=k.getBoundingClientRect(),x.attr("width",h.width),x.attr("height",h.height)}const d=t.padding/2;return E(p).attr("transform","translate( "+(h.width>f.width?0:(f.width-h.width)/2)+", "+(f.height+d+5)+")"),E(o).attr("transform","translate( "+(h.width{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.height+t.padding,r=i.width+l/4+t.padding,s=e.insert("rect",":first-child").attr("style",t.style).attr("rx",l/2).attr("ry",l/2).attr("x",-r/2).attr("y",-l/2).attr("width",r).attr("height",l);return m(t,s),t.intersect=function(n){return w.rect(t,n)},e},Qt=async(a,t)=>{const{shapeSvg:e,bbox:i,halfPadding:l}=await M(a,t,_(t),!0),r=e.insert("circle",":first-child");return r.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l).attr("width",i.width+t.padding).attr("height",i.height+t.padding),g.info("Circle main"),m(t,r),t.intersect=function(s){return g.info("Circle intersect",t,i.width/2+l,s),w.circle(t,i.width/2+l,s)},e},Vt=async(a,t)=>{const{shapeSvg:e,bbox:i,halfPadding:l}=await M(a,t,_(t),!0),r=5,s=e.insert("g",":first-child"),n=s.insert("circle"),c=s.insert("circle");return s.attr("class",t.class),n.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l+r).attr("width",i.width+t.padding+r*2).attr("height",i.height+t.padding+r*2),c.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l).attr("width",i.width+t.padding).attr("height",i.height+t.padding),g.info("DoubleCircle main"),m(t,n),t.intersect=function(o){return g.info("DoubleCircle intersect",t,i.width/2+l+r,o),w.circle(t,i.width/2+l+r,o)},e},qt=async(a,t)=>{const{shapeSvg:e,bbox:i}=await M(a,t,_(t),!0),l=i.width+t.padding,r=i.height+t.padding,s=[{x:0,y:0},{x:l,y:0},{x:l,y:-r},{x:0,y:-r},{x:0,y:0},{x:-8,y:0},{x:l+8,y:0},{x:l+8,y:-r},{x:-8,y:-r},{x:-8,y:0}],n=I(e,l,r,s);return n.attr("style",t.style),m(t,n),t.intersect=function(c){return w.polygon(t,s,c)},e},Jt=(a,t)=>{const e=a.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=e.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),m(t,i),t.intersect=function(l){return w.circle(t,7,l)},e},tt=(a,t,e)=>{const i=a.insert("g").attr("class","node default").attr("id",t.domId||t.id);let l=70,r=10;e==="LR"&&(l=10,r=70);const s=i.append("rect").attr("x",-1*l/2).attr("y",-1*r/2).attr("width",l).attr("height",r).attr("class","fork-join");return m(t,s),t.height=t.height+t.padding/2,t.width=t.width+t.padding/2,t.intersect=function(n){return w.rect(t,n)},i},Kt=(a,t)=>{const e=a.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=e.insert("circle",":first-child"),l=e.insert("circle",":first-child");return l.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),i.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),m(t,l),t.intersect=function(r){return w.circle(t,7,r)},e},Pt=(a,t)=>{const e=t.padding/2,i=4,l=8;let r;t.classes?r="node "+t.classes:r="node default";const s=a.insert("g").attr("class",r).attr("id",t.domId||t.id),n=s.insert("rect",":first-child"),c=s.insert("line"),o=s.insert("line");let h=0,y=i;const f=s.insert("g").attr("class","label");let p=0;const d=t.classData.annotations&&t.classData.annotations[0],k=t.classData.annotations[0]?"«"+t.classData.annotations[0]+"»":"",x=f.node().appendChild(R(k,t.labelStyle,!0,!0));let u=x.getBBox();if(H(b().flowchart.htmlLabels)){const v=x.children[0],L=E(x);u=v.getBoundingClientRect(),L.attr("width",u.width),L.attr("height",u.height)}t.classData.annotations[0]&&(y+=u.height+i,h+=u.width);let S=t.classData.label;t.classData.type!==void 0&&t.classData.type!==""&&(b().flowchart.htmlLabels?S+="<"+t.classData.type+">":S+="<"+t.classData.type+">");const B=f.node().appendChild(R(S,t.labelStyle,!0,!0));E(B).attr("class","classTitle");let C=B.getBBox();if(H(b().flowchart.htmlLabels)){const v=B.children[0],L=E(B);C=v.getBoundingClientRect(),L.attr("width",C.width),L.attr("height",C.height)}y+=C.height+i,C.width>h&&(h=C.width);const X=[];t.classData.members.forEach(v=>{const L=v.getDisplayDetails();let W=L.displayText;b().flowchart.htmlLabels&&(W=W.replace(//g,">"));const N=f.node().appendChild(R(W,L.cssStyle?L.cssStyle:t.labelStyle,!0,!0));let $=N.getBBox();if(H(b().flowchart.htmlLabels)){const G=N.children[0],D=E(N);$=G.getBoundingClientRect(),D.attr("width",$.width),D.attr("height",$.height)}$.width>h&&(h=$.width),y+=$.height+i,X.push(N)}),y+=l;const A=[];if(t.classData.methods.forEach(v=>{const L=v.getDisplayDetails();let W=L.displayText;b().flowchart.htmlLabels&&(W=W.replace(//g,">"));const N=f.node().appendChild(R(W,L.cssStyle?L.cssStyle:t.labelStyle,!0,!0));let $=N.getBBox();if(H(b().flowchart.htmlLabels)){const G=N.children[0],D=E(N);$=G.getBoundingClientRect(),D.attr("width",$.width),D.attr("height",$.height)}$.width>h&&(h=$.width),y+=$.height+i,A.push(N)}),y+=l,d){let v=(h-u.width)/2;E(x).attr("transform","translate( "+(-1*h/2+v)+", "+-1*y/2+")"),p=u.height+i}let nt=(h-C.width)/2;return E(B).attr("transform","translate( "+(-1*h/2+nt)+", "+(-1*y/2+p)+")"),p+=C.height+i,c.attr("class","divider").attr("x1",-h/2-e).attr("x2",h/2+e).attr("y1",-y/2-e+l+p).attr("y2",-y/2-e+l+p),p+=l,X.forEach(v=>{E(v).attr("transform","translate( "+-h/2+", "+(-1*y/2+p+l/2)+")");const L=v==null?void 0:v.getBBox();p+=((L==null?void 0:L.height)??0)+i}),p+=l,o.attr("class","divider").attr("x1",-h/2-e).attr("x2",h/2+e).attr("y1",-y/2-e+l+p).attr("y2",-y/2-e+l+p),p+=l,A.forEach(v=>{E(v).attr("transform","translate( "+-h/2+", "+(-1*y/2+p)+")");const L=v==null?void 0:v.getBBox();p+=((L==null?void 0:L.height)??0)+i}),n.attr("style",t.style).attr("class","outer title-state").attr("x",-h/2-e).attr("y",-(y/2)-e).attr("width",h+t.padding).attr("height",y+t.padding),m(t,n),t.intersect=function(v){return w.rect(t,v)},s},rt={rhombus:P,composite:zt,question:P,rect:Ut,labelRect:Zt,rectWithTitle:Ft,choice:Ht,circle:Qt,doublecircle:Vt,stadium:Gt,hexagon:It,block_arrow:Nt,rect_left_inv_arrow:Ot,lean_right:Wt,lean_left:Xt,trapezoid:Yt,inv_trapezoid:At,rect_right_inv_arrow:Dt,cylinder:jt,start:Jt,end:Kt,note:$t,subroutine:qt,fork:tt,join:tt,class_box:Pt};let Y={};const or=async(a,t,e)=>{let i,l;if(t.link){let r;b().securityLevel==="sandbox"?r="_top":t.linkTarget&&(r=t.linkTarget||"_blank"),i=a.insert("svg:a").attr("xlink:href",t.link).attr("target",r),l=await rt[t.shape](i,t,e)}else l=await rt[t.shape](a,t,e),i=l;return t.tooltip&&l.attr("title",t.tooltip),t.class&&l.attr("class","node default "+t.class),i.attr("data-node","true"),i.attr("data-id",t.id),Y[t.id]=i,t.haveCallback&&Y[t.id].attr("class",Y[t.id].attr("class")+" clickable"),i},yr=(a,t)=>{Y[t.id]=a},pr=()=>{Y={}},fr=a=>{const t=Y[a.id];g.trace("Transforming node",a.diff,a,"translate("+(a.x-a.width/2-5)+", "+a.width/2+")");const e=8,i=a.diff||0;return a.clusterNode?t.attr("transform","translate("+(a.x+i-a.width/2)+", "+(a.y-a.height/2-e)+")"):t.attr("transform","translate("+a.x+", "+a.y+")"),i},tr=({flowchart:a})=>{var t,e;const i=((t=a==null?void 0:a.subGraphTitleMargin)==null?void 0:t.top)??0,l=((e=a==null?void 0:a.subGraphTitleMargin)==null?void 0:e.bottom)??0,r=i+l;return{subGraphTitleTopMargin:i,subGraphTitleBottomMargin:l,subGraphTitleTotalMargin:r}},O={aggregation:18,extension:18,composition:18,dependency:6,lollipop:13.5,arrow_point:5.3};function U(a,t){if(a===void 0||t===void 0)return{angle:0,deltaX:0,deltaY:0};a=Z(a),t=Z(t);const[e,i]=[a.x,a.y],[l,r]=[t.x,t.y],s=l-e,n=r-i;return{angle:Math.atan(n/s),deltaX:s,deltaY:n}}const Z=a=>Array.isArray(a)?{x:a[0],y:a[1]}:a,rr=a=>({x:function(t,e,i){let l=0;if(e===0&&Object.hasOwn(O,a.arrowTypeStart)){const{angle:r,deltaX:s}=U(i[0],i[1]);l=O[a.arrowTypeStart]*Math.cos(r)*(s>=0?1:-1)}else if(e===i.length-1&&Object.hasOwn(O,a.arrowTypeEnd)){const{angle:r,deltaX:s}=U(i[i.length-1],i[i.length-2]);l=O[a.arrowTypeEnd]*Math.cos(r)*(s>=0?1:-1)}return Z(t).x+l},y:function(t,e,i){let l=0;if(e===0&&Object.hasOwn(O,a.arrowTypeStart)){const{angle:r,deltaY:s}=U(i[0],i[1]);l=O[a.arrowTypeStart]*Math.abs(Math.sin(r))*(s>=0?1:-1)}else if(e===i.length-1&&Object.hasOwn(O,a.arrowTypeEnd)){const{angle:r,deltaY:s}=U(i[i.length-1],i[i.length-2]);l=O[a.arrowTypeEnd]*Math.abs(Math.sin(r))*(s>=0?1:-1)}return Z(t).y+l}}),ar=(a,t,e,i,l)=>{t.arrowTypeStart&&at(a,"start",t.arrowTypeStart,e,i,l),t.arrowTypeEnd&&at(a,"end",t.arrowTypeEnd,e,i,l)},er={arrow_cross:"cross",arrow_point:"point",arrow_barb:"barb",arrow_circle:"circle",aggregation:"aggregation",extension:"extension",composition:"composition",dependency:"dependency",lollipop:"lollipop"},at=(a,t,e,i,l,r)=>{const s=er[e];if(!s){g.warn(`Unknown arrow type: ${e}`);return}const n=t==="start"?"Start":"End";a.attr(`marker-${t}`,`url(${i}#${l}_${r}-${s}${n})`)};let F={},T={};const xr=()=>{F={},T={}},dr=(a,t)=>{const e=H(b().flowchart.htmlLabels),i=t.labelType==="markdown"?st(a,t.label,{style:t.labelStyle,useHtmlLabels:e,addSvgBackground:!0}):R(t.label,t.labelStyle),l=a.insert("g").attr("class","edgeLabel"),r=l.insert("g").attr("class","label");r.node().appendChild(i);let s=i.getBBox();if(e){const c=i.children[0],o=E(i);s=c.getBoundingClientRect(),o.attr("width",s.width),o.attr("height",s.height)}r.attr("transform","translate("+-s.width/2+", "+-s.height/2+")"),F[t.id]=l,t.width=s.width,t.height=s.height;let n;if(t.startLabelLeft){const c=R(t.startLabelLeft,t.labelStyle),o=a.insert("g").attr("class","edgeTerminals"),h=o.insert("g").attr("class","inner");n=h.node().appendChild(c);const y=c.getBBox();h.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),T[t.id]||(T[t.id]={}),T[t.id].startLeft=o,z(n,t.startLabelLeft)}if(t.startLabelRight){const c=R(t.startLabelRight,t.labelStyle),o=a.insert("g").attr("class","edgeTerminals"),h=o.insert("g").attr("class","inner");n=o.node().appendChild(c),h.node().appendChild(c);const y=c.getBBox();h.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),T[t.id]||(T[t.id]={}),T[t.id].startRight=o,z(n,t.startLabelRight)}if(t.endLabelLeft){const c=R(t.endLabelLeft,t.labelStyle),o=a.insert("g").attr("class","edgeTerminals"),h=o.insert("g").attr("class","inner");n=h.node().appendChild(c);const y=c.getBBox();h.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),o.node().appendChild(c),T[t.id]||(T[t.id]={}),T[t.id].endLeft=o,z(n,t.endLabelLeft)}if(t.endLabelRight){const c=R(t.endLabelRight,t.labelStyle),o=a.insert("g").attr("class","edgeTerminals"),h=o.insert("g").attr("class","inner");n=h.node().appendChild(c);const y=c.getBBox();h.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),o.node().appendChild(c),T[t.id]||(T[t.id]={}),T[t.id].endRight=o,z(n,t.endLabelRight)}return i};function z(a,t){b().flowchart.htmlLabels&&a&&(a.style.width=t.length*9+"px",a.style.height="12px")}const gr=(a,t)=>{g.debug("Moving label abc88 ",a.id,a.label,F[a.id],t);let e=t.updatedPath?t.updatedPath:t.originalPath;const i=b(),{subGraphTitleTotalMargin:l}=tr(i);if(a.label){const r=F[a.id];let s=a.x,n=a.y;if(e){const c=j.calcLabelPosition(e);g.debug("Moving label "+a.label+" from (",s,",",n,") to (",c.x,",",c.y,") abc88"),t.updatedPath&&(s=c.x,n=c.y)}r.attr("transform",`translate(${s}, ${n+l/2})`)}if(a.startLabelLeft){const r=T[a.id].startLeft;let s=a.x,n=a.y;if(e){const c=j.calcTerminalLabelPosition(a.arrowTypeStart?10:0,"start_left",e);s=c.x,n=c.y}r.attr("transform",`translate(${s}, ${n})`)}if(a.startLabelRight){const r=T[a.id].startRight;let s=a.x,n=a.y;if(e){const c=j.calcTerminalLabelPosition(a.arrowTypeStart?10:0,"start_right",e);s=c.x,n=c.y}r.attr("transform",`translate(${s}, ${n})`)}if(a.endLabelLeft){const r=T[a.id].endLeft;let s=a.x,n=a.y;if(e){const c=j.calcTerminalLabelPosition(a.arrowTypeEnd?10:0,"end_left",e);s=c.x,n=c.y}r.attr("transform",`translate(${s}, ${n})`)}if(a.endLabelRight){const r=T[a.id].endRight;let s=a.x,n=a.y;if(e){const c=j.calcTerminalLabelPosition(a.arrowTypeEnd?10:0,"end_right",e);s=c.x,n=c.y}r.attr("transform",`translate(${s}, ${n})`)}},sr=(a,t)=>{const e=a.x,i=a.y,l=Math.abs(t.x-e),r=Math.abs(t.y-i),s=a.width/2,n=a.height/2;return l>=s||r>=n},ir=(a,t,e)=>{g.debug(`intersection calc abc89:
+ outsidePoint: ${JSON.stringify(t)}
+ insidePoint : ${JSON.stringify(e)}
+ node : x:${a.x} y:${a.y} w:${a.width} h:${a.height}`);const i=a.x,l=a.y,r=Math.abs(i-e.x),s=a.width/2;let n=e.xMath.abs(i-t.x)*c){let y=e.y{g.debug("abc88 cutPathAtIntersect",a,t);let e=[],i=a[0],l=!1;return a.forEach(r=>{if(!sr(t,r)&&!l){const s=ir(t,i,r);let n=!1;e.forEach(c=>{n=n||c.x===s.x&&c.y===s.y}),e.some(c=>c.x===s.x&&c.y===s.y)||e.push(s),l=!0}else i=r,l||e.push(r)}),e},ur=function(a,t,e,i,l,r,s){let n=e.points;g.debug("abc88 InsertEdge: edge=",e,"e=",t);let c=!1;const o=r.node(t.v);var h=r.node(t.w);h!=null&&h.intersect&&(o!=null&&o.intersect)&&(n=n.slice(1,e.points.length-1),n.unshift(o.intersect(n[0])),n.push(h.intersect(n[n.length-1]))),e.toCluster&&(g.debug("to cluster abc88",i[e.toCluster]),n=et(e.points,i[e.toCluster].node),c=!0),e.fromCluster&&(g.debug("from cluster abc88",i[e.fromCluster]),n=et(n.reverse(),i[e.fromCluster].node).reverse(),c=!0);const y=n.filter(C=>!Number.isNaN(C.y));let f=lt;e.curve&&(l==="graph"||l==="flowchart")&&(f=e.curve);const{x:p,y:d}=rr(e),k=ct().x(p).y(d).curve(f);let x;switch(e.thickness){case"normal":x="edge-thickness-normal";break;case"thick":x="edge-thickness-thick";break;case"invisible":x="edge-thickness-thick";break;default:x=""}switch(e.pattern){case"solid":x+=" edge-pattern-solid";break;case"dotted":x+=" edge-pattern-dotted";break;case"dashed":x+=" edge-pattern-dashed";break}const u=a.append("path").attr("d",k(y)).attr("id",e.id).attr("class"," "+x+(e.classes?" "+e.classes:"")).attr("style",e.style);let S="";(b().flowchart.arrowMarkerAbsolute||b().state.arrowMarkerAbsolute)&&(S=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,S=S.replace(/\(/g,"\\("),S=S.replace(/\)/g,"\\)")),ar(u,e,S,s,l);let B={};return c&&(B.updatedPath=n),B.originalPath=e.points,B};export{or as a,dr as b,ur as c,gr as d,Et as e,R as f,tr as g,pr as h,hr as i,xr as j,rr as k,M as l,ar as m,fr as p,yr as s,m as u};
diff --git a/frontend-dist/assets/erDiagram-0228fc6a-DHOtPsx4.js b/frontend-dist/assets/erDiagram-0228fc6a-DHOtPsx4.js
new file mode 100644
index 0000000000000000000000000000000000000000..5e3c626010fcf0a40928ac107dfc0e66de8d2d68
--- /dev/null
+++ b/frontend-dist/assets/erDiagram-0228fc6a-DHOtPsx4.js
@@ -0,0 +1,51 @@
+import{C as Et,D as mt,g as gt,s as kt,a as xt,b as Rt,c as Z,l as V,d as rt,A as Ot,e as bt,E as Nt,F as Tt,G as At}from"./index-BCNM9-Ly.js";import{G as Mt}from"./graph-CY8eBbAS.js";import{l as St}from"./layout-CUwpW5wl.js";import{l as wt}from"./line-DdWeXrJe.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";const It=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function Dt(t){return typeof t=="string"&&It.test(t)}const A=[];for(let t=0;t<256;++t)A.push((t+256).toString(16).slice(1));function vt(t,e=0){return A[t[e+0]]+A[t[e+1]]+A[t[e+2]]+A[t[e+3]]+"-"+A[t[e+4]]+A[t[e+5]]+"-"+A[t[e+6]]+A[t[e+7]]+"-"+A[t[e+8]]+A[t[e+9]]+"-"+A[t[e+10]]+A[t[e+11]]+A[t[e+12]]+A[t[e+13]]+A[t[e+14]]+A[t[e+15]]}function Lt(t){if(!Dt(t))throw TypeError("Invalid UUID");let e;const r=new Uint8Array(16);return r[0]=(e=parseInt(t.slice(0,8),16))>>>24,r[1]=e>>>16&255,r[2]=e>>>8&255,r[3]=e&255,r[4]=(e=parseInt(t.slice(9,13),16))>>>8,r[5]=e&255,r[6]=(e=parseInt(t.slice(14,18),16))>>>8,r[7]=e&255,r[8]=(e=parseInt(t.slice(19,23),16))>>>8,r[9]=e&255,r[10]=(e=parseInt(t.slice(24,36),16))/1099511627776&255,r[11]=e/4294967296&255,r[12]=e>>>24&255,r[13]=e>>>16&255,r[14]=e>>>8&255,r[15]=e&255,r}function Bt(t){t=unescape(encodeURIComponent(t));const e=[];for(let r=0;r>>32-e}function Ft(t){const e=[1518500249,1859775393,2400959708,3395469782],r=[1732584193,4023233417,2562383102,271733878,3285377520];if(typeof t=="string"){const f=unescape(encodeURIComponent(t));t=[];for(let o=0;o>>0;x=g,g=m,m=it(_,30)>>>0,_=h,h=I}r[0]=r[0]+h>>>0,r[1]=r[1]+_>>>0,r[2]=r[2]+m>>>0,r[3]=r[3]+g>>>0,r[4]=r[4]+x>>>0}return[r[0]>>24&255,r[0]>>16&255,r[0]>>8&255,r[0]&255,r[1]>>24&255,r[1]>>16&255,r[1]>>8&255,r[1]&255,r[2]>>24&255,r[2]>>16&255,r[2]>>8&255,r[2]&255,r[3]>>24&255,r[3]>>16&255,r[3]>>8&255,r[3]&255,r[4]>>24&255,r[4]>>16&255,r[4]>>8&255,r[4]&255]}const Wt=Yt("v5",80,Ft);var at=function(){var t=function(S,a,n,c){for(n=n||{},c=S.length;c--;n[S[c]]=a);return n},e=[6,8,10,20,22,24,26,27,28],r=[1,10],u=[1,11],l=[1,12],p=[1,13],f=[1,14],o=[1,15],h=[1,21],_=[1,22],m=[1,23],g=[1,24],x=[1,25],y=[6,8,10,13,15,18,19,20,22,24,26,27,28,41,42,43,44,45],N=[1,34],I=[27,28,46,47],F=[41,42,43,44,45],W=[17,34],C=[1,54],T=[1,53],M=[17,34,36,38],R={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,entityName:11,relSpec:12,":":13,role:14,BLOCK_START:15,attributes:16,BLOCK_STOP:17,SQS:18,SQE:19,title:20,title_value:21,acc_title:22,acc_title_value:23,acc_descr:24,acc_descr_value:25,acc_descr_multiline_value:26,ALPHANUM:27,ENTITY_NAME:28,attribute:29,attributeType:30,attributeName:31,attributeKeyTypeList:32,attributeComment:33,ATTRIBUTE_WORD:34,attributeKeyType:35,COMMA:36,ATTRIBUTE_KEY:37,COMMENT:38,cardinality:39,relType:40,ZERO_OR_ONE:41,ZERO_OR_MORE:42,ONE_OR_MORE:43,ONLY_ONE:44,MD_PARENT:45,NON_IDENTIFYING:46,IDENTIFYING:47,WORD:48,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",8:"SPACE",10:"NEWLINE",13:":",15:"BLOCK_START",17:"BLOCK_STOP",18:"SQS",19:"SQE",20:"title",21:"title_value",22:"acc_title",23:"acc_title_value",24:"acc_descr",25:"acc_descr_value",26:"acc_descr_multiline_value",27:"ALPHANUM",28:"ENTITY_NAME",34:"ATTRIBUTE_WORD",36:"COMMA",37:"ATTRIBUTE_KEY",38:"COMMENT",41:"ZERO_OR_ONE",42:"ZERO_OR_MORE",43:"ONE_OR_MORE",44:"ONLY_ONE",45:"MD_PARENT",46:"NON_IDENTIFYING",47:"IDENTIFYING",48:"WORD"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,5],[9,4],[9,3],[9,1],[9,7],[9,6],[9,4],[9,2],[9,2],[9,2],[9,1],[11,1],[11,1],[16,1],[16,2],[29,2],[29,3],[29,3],[29,4],[30,1],[31,1],[32,1],[32,3],[35,1],[33,1],[12,3],[39,1],[39,1],[39,1],[39,1],[39,1],[40,1],[40,1],[14,1],[14,1],[14,1]],performAction:function(a,n,c,d,E,i,K){var s=i.length-1;switch(E){case 1:break;case 2:this.$=[];break;case 3:i[s-1].push(i[s]),this.$=i[s-1];break;case 4:case 5:this.$=i[s];break;case 6:case 7:this.$=[];break;case 8:d.addEntity(i[s-4]),d.addEntity(i[s-2]),d.addRelationship(i[s-4],i[s],i[s-2],i[s-3]);break;case 9:d.addEntity(i[s-3]),d.addAttributes(i[s-3],i[s-1]);break;case 10:d.addEntity(i[s-2]);break;case 11:d.addEntity(i[s]);break;case 12:d.addEntity(i[s-6],i[s-4]),d.addAttributes(i[s-6],i[s-1]);break;case 13:d.addEntity(i[s-5],i[s-3]);break;case 14:d.addEntity(i[s-3],i[s-1]);break;case 15:case 16:this.$=i[s].trim(),d.setAccTitle(this.$);break;case 17:case 18:this.$=i[s].trim(),d.setAccDescription(this.$);break;case 19:case 43:this.$=i[s];break;case 20:case 41:case 42:this.$=i[s].replace(/"/g,"");break;case 21:case 29:this.$=[i[s]];break;case 22:i[s].push(i[s-1]),this.$=i[s];break;case 23:this.$={attributeType:i[s-1],attributeName:i[s]};break;case 24:this.$={attributeType:i[s-2],attributeName:i[s-1],attributeKeyTypeList:i[s]};break;case 25:this.$={attributeType:i[s-2],attributeName:i[s-1],attributeComment:i[s]};break;case 26:this.$={attributeType:i[s-3],attributeName:i[s-2],attributeKeyTypeList:i[s-1],attributeComment:i[s]};break;case 27:case 28:case 31:this.$=i[s];break;case 30:i[s-2].push(i[s]),this.$=i[s-2];break;case 32:this.$=i[s].replace(/"/g,"");break;case 33:this.$={cardA:i[s],relType:i[s-1],cardB:i[s-2]};break;case 34:this.$=d.Cardinality.ZERO_OR_ONE;break;case 35:this.$=d.Cardinality.ZERO_OR_MORE;break;case 36:this.$=d.Cardinality.ONE_OR_MORE;break;case 37:this.$=d.Cardinality.ONLY_ONE;break;case 38:this.$=d.Cardinality.MD_PARENT;break;case 39:this.$=d.Identification.NON_IDENTIFYING;break;case 40:this.$=d.Identification.IDENTIFYING;break}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:9,20:r,22:u,24:l,26:p,27:f,28:o},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:16,11:9,20:r,22:u,24:l,26:p,27:f,28:o},t(e,[2,5]),t(e,[2,6]),t(e,[2,11],{12:17,39:20,15:[1,18],18:[1,19],41:h,42:_,43:m,44:g,45:x}),{21:[1,26]},{23:[1,27]},{25:[1,28]},t(e,[2,18]),t(y,[2,19]),t(y,[2,20]),t(e,[2,4]),{11:29,27:f,28:o},{16:30,17:[1,31],29:32,30:33,34:N},{11:35,27:f,28:o},{40:36,46:[1,37],47:[1,38]},t(I,[2,34]),t(I,[2,35]),t(I,[2,36]),t(I,[2,37]),t(I,[2,38]),t(e,[2,15]),t(e,[2,16]),t(e,[2,17]),{13:[1,39]},{17:[1,40]},t(e,[2,10]),{16:41,17:[2,21],29:32,30:33,34:N},{31:42,34:[1,43]},{34:[2,27]},{19:[1,44]},{39:45,41:h,42:_,43:m,44:g,45:x},t(F,[2,39]),t(F,[2,40]),{14:46,27:[1,49],28:[1,48],48:[1,47]},t(e,[2,9]),{17:[2,22]},t(W,[2,23],{32:50,33:51,35:52,37:C,38:T}),t([17,34,37,38],[2,28]),t(e,[2,14],{15:[1,55]}),t([27,28],[2,33]),t(e,[2,8]),t(e,[2,41]),t(e,[2,42]),t(e,[2,43]),t(W,[2,24],{33:56,36:[1,57],38:T}),t(W,[2,25]),t(M,[2,29]),t(W,[2,32]),t(M,[2,31]),{16:58,17:[1,59],29:32,30:33,34:N},t(W,[2,26]),{35:60,37:C},{17:[1,61]},t(e,[2,13]),t(M,[2,30]),t(e,[2,12])],defaultActions:{34:[2,27],41:[2,22]},parseError:function(a,n){if(n.recoverable)this.trace(a);else{var c=new Error(a);throw c.hash=n,c}},parse:function(a){var n=this,c=[0],d=[],E=[null],i=[],K=this.table,s="",Q=0,st=0,ft=2,ot=1,yt=i.slice.call(arguments,1),b=Object.create(this.lexer),G={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(G.yy[J]=this.yy[J]);b.setInput(a,G.yy),G.yy.lexer=b,G.yy.parser=this,typeof b.yylloc>"u"&&(b.yylloc={});var $=b.yylloc;i.push($);var pt=b.options&&b.options.ranges;typeof G.yy.parseError=="function"?this.parseError=G.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function _t(){var Y;return Y=d.pop()||b.lex()||ot,typeof Y!="number"&&(Y instanceof Array&&(d=Y,Y=d.pop()),Y=n.symbols_[Y]||Y),Y}for(var w,H,D,tt,z={},j,P,lt,q;;){if(H=c[c.length-1],this.defaultActions[H]?D=this.defaultActions[H]:((w===null||typeof w>"u")&&(w=_t()),D=K[H]&&K[H][w]),typeof D>"u"||!D.length||!D[0]){var et="";q=[];for(j in K[H])this.terminals_[j]&&j>ft&&q.push("'"+this.terminals_[j]+"'");b.showPosition?et="Parse error on line "+(Q+1)+`:
+`+b.showPosition()+`
+Expecting `+q.join(", ")+", got '"+(this.terminals_[w]||w)+"'":et="Parse error on line "+(Q+1)+": Unexpected "+(w==ot?"end of input":"'"+(this.terminals_[w]||w)+"'"),this.parseError(et,{text:b.match,token:this.terminals_[w]||w,line:b.yylineno,loc:$,expected:q})}if(D[0]instanceof Array&&D.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+w);switch(D[0]){case 1:c.push(w),E.push(b.yytext),i.push(b.yylloc),c.push(D[1]),w=null,st=b.yyleng,s=b.yytext,Q=b.yylineno,$=b.yylloc;break;case 2:if(P=this.productions_[D[1]][1],z.$=E[E.length-P],z._$={first_line:i[i.length-(P||1)].first_line,last_line:i[i.length-1].last_line,first_column:i[i.length-(P||1)].first_column,last_column:i[i.length-1].last_column},pt&&(z._$.range=[i[i.length-(P||1)].range[0],i[i.length-1].range[1]]),tt=this.performAction.apply(z,[s,st,Q,G.yy,D[1],E,i].concat(yt)),typeof tt<"u")return tt;P&&(c=c.slice(0,-1*P*2),E=E.slice(0,-1*P),i=i.slice(0,-1*P)),c.push(this.productions_[D[1]][0]),E.push(z.$),i.push(z._$),lt=K[c[c.length-2]][c[c.length-1]],c.push(lt);break;case 3:return!0}}return!0}},O=function(){var S={EOF:1,parseError:function(n,c){if(this.yy.parser)this.yy.parser.parseError(n,c);else throw new Error(n)},setInput:function(a,n){return this.yy=n||this.yy||{},this._input=a,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var n=a.match(/(?:\r\n?|\n).*/g);return n?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var n=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-n),this.offset-=n;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var E=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-n},this.options.ranges&&(this.yylloc.range=[E[0],E[0]+this.yyleng-n]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
+`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),n=new Array(a.length+1).join("-");return a+this.upcomingInput()+`
+`+n+"^"},test_match:function(a,n){var c,d,E;if(this.options.backtrack_lexer&&(E={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(E.yylloc.range=this.yylloc.range.slice(0))),d=a[0].match(/(?:\r\n?|\n).*/g),d&&(this.yylineno+=d.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:d?d[d.length-1].length-d[d.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+a[0].length},this.yytext+=a[0],this.match+=a[0],this.matches=a,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(a[0].length),this.matched+=a[0],c=this.performAction.call(this,this.yy,this,n,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),c)return c;if(this._backtrack){for(var i in E)this[i]=E[i];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,n,c,d;this._more||(this.yytext="",this.match="");for(var E=this._currentRules(),i=0;in[0].length)){if(n=c,d=i,this.options.backtrack_lexer){if(a=this.test_match(c,E[i]),a!==!1)return a;if(this._backtrack){n=!1;continue}else return!1}else if(!this.options.flex)break}return n?(a=this.test_match(n,E[d]),a!==!1?a:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text.
+`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var n=this.next();return n||this.lex()},begin:function(n){this.conditionStack.push(n)},popState:function(){var n=this.conditionStack.length-1;return n>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(n){return n=this.conditionStack.length-1-Math.abs(n||0),n>=0?this.conditionStack[n]:"INITIAL"},pushState:function(n){this.begin(n)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(n,c,d,E){switch(d){case 0:return this.begin("acc_title"),22;case 1:return this.popState(),"acc_title_value";case 2:return this.begin("acc_descr"),24;case 3:return this.popState(),"acc_descr_value";case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:return 10;case 8:break;case 9:return 8;case 10:return 28;case 11:return 48;case 12:return 4;case 13:return this.begin("block"),15;case 14:return 36;case 15:break;case 16:return 37;case 17:return 34;case 18:return 34;case 19:return 38;case 20:break;case 21:return this.popState(),17;case 22:return c.yytext[0];case 23:return 18;case 24:return 19;case 25:return 41;case 26:return 43;case 27:return 43;case 28:return 43;case 29:return 41;case 30:return 41;case 31:return 42;case 32:return 42;case 33:return 42;case 34:return 42;case 35:return 42;case 36:return 43;case 37:return 42;case 38:return 43;case 39:return 44;case 40:return 44;case 41:return 44;case 42:return 44;case 43:return 41;case 44:return 42;case 45:return 43;case 46:return 45;case 47:return 46;case 48:return 47;case 49:return 47;case 50:return 46;case 51:return 46;case 52:return 46;case 53:return 27;case 54:return c.yytext[0];case 55:return 6}},rules:[/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"%\r\n\v\b\\]+")/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:,)/i,/^(?:\s+)/i,/^(?:\b((?:PK)|(?:FK)|(?:UK))\b)/i,/^(?:(.*?)[~](.*?)*[~])/i,/^(?:[\*A-Za-z_][A-Za-z0-9\-_\[\]\(\)]*)/i,/^(?:"[^"]*")/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\[)/i,/^(?:\])/i,/^(?:one or zero\b)/i,/^(?:one or more\b)/i,/^(?:one or many\b)/i,/^(?:1\+)/i,/^(?:\|o\b)/i,/^(?:zero or one\b)/i,/^(?:zero or more\b)/i,/^(?:zero or many\b)/i,/^(?:0\+)/i,/^(?:\}o\b)/i,/^(?:many\(0\))/i,/^(?:many\(1\))/i,/^(?:many\b)/i,/^(?:\}\|)/i,/^(?:one\b)/i,/^(?:only one\b)/i,/^(?:1\b)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\s*u\b)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:to\b)/i,/^(?:optionally to\b)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z_][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{acc_descr_multiline:{rules:[5,6],inclusive:!1},acc_descr:{rules:[3],inclusive:!1},acc_title:{rules:[1],inclusive:!1},block:{rules:[14,15,16,17,18,19,20,21,22],inclusive:!1},INITIAL:{rules:[0,2,4,7,8,9,10,11,12,13,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55],inclusive:!0}}};return S}();R.lexer=O;function v(){this.yy={}}return v.prototype=R,R.Parser=v,new v}();at.parser=at;const Ut=at;let U={},nt=[];const Gt={ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE",MD_PARENT:"MD_PARENT"},Ht={NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},dt=function(t,e=void 0){return U[t]===void 0?(U[t]={attributes:[],alias:e},V.info("Added new entity :",t)):U[t]&&!U[t].alias&&e&&(U[t].alias=e,V.info(`Add alias '${e}' to entity '${t}'`)),U[t]},zt=()=>U,Kt=function(t,e){let r=dt(t),u;for(u=e.length-1;u>=0;u--)r.attributes.push(e[u]),V.debug("Added attribute ",e[u].attributeName)},Vt=function(t,e,r,u){let l={entityA:t,roleA:e,entityB:r,relSpec:u};nt.push(l),V.debug("Added new relationship :",l)},Xt=()=>nt,Qt=function(){U={},nt=[],Nt()},jt={Cardinality:Gt,Identification:Ht,getConfig:()=>Z().er,addEntity:dt,addAttributes:Kt,getEntities:zt,addRelationship:Vt,getRelationships:Xt,clear:Qt,setAccTitle:Rt,getAccTitle:xt,setAccDescription:kt,getAccDescription:gt,setDiagramTitle:mt,getDiagramTitle:Et},L={ONLY_ONE_START:"ONLY_ONE_START",ONLY_ONE_END:"ONLY_ONE_END",ZERO_OR_ONE_START:"ZERO_OR_ONE_START",ZERO_OR_ONE_END:"ZERO_OR_ONE_END",ONE_OR_MORE_START:"ONE_OR_MORE_START",ONE_OR_MORE_END:"ONE_OR_MORE_END",ZERO_OR_MORE_START:"ZERO_OR_MORE_START",ZERO_OR_MORE_END:"ZERO_OR_MORE_END",MD_PARENT_END:"MD_PARENT_END",MD_PARENT_START:"MD_PARENT_START"},qt=function(t,e){let r;t.append("defs").append("marker").attr("id",L.MD_PARENT_START).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",L.MD_PARENT_END).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",L.ONLY_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18 M15,0 L15,18"),t.append("defs").append("marker").attr("id",L.ONLY_ONE_END).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,0 L3,18 M9,0 L9,18"),r=t.append("defs").append("marker").attr("id",L.ZERO_OR_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18"),r=t.append("defs").append("marker").attr("id",L.ZERO_OR_ONE_END).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,0 L21,18"),t.append("defs").append("marker").attr("id",L.ONE_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),t.append("defs").append("marker").attr("id",L.ONE_OR_MORE_END).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18"),r=t.append("defs").append("marker").attr("id",L.ZERO_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18"),r=t.append("defs").append("marker").attr("id",L.ZERO_OR_MORE_END).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},B={ERMarkers:L,insertMarkers:qt},Jt=/[^\dA-Za-z](\W)*/g;let k={},X=new Map;const $t=function(t){const e=Object.keys(t);for(const r of e)k[r]=t[r]},te=(t,e,r)=>{const u=k.entityPadding/3,l=k.entityPadding/3,p=k.fontSize*.85,f=e.node().getBBox(),o=[];let h=!1,_=!1,m=0,g=0,x=0,y=0,N=f.height+u*2,I=1;r.forEach(T=>{T.attributeKeyTypeList!==void 0&&T.attributeKeyTypeList.length>0&&(h=!0),T.attributeComment!==void 0&&(_=!0)}),r.forEach(T=>{const M=`${e.node().id}-attr-${I}`;let R=0;const O=At(T.attributeType),v=t.append("text").classed("er entityLabel",!0).attr("id",`${M}-type`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",Z().fontFamily).style("font-size",p+"px").text(O),S=t.append("text").classed("er entityLabel",!0).attr("id",`${M}-name`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",Z().fontFamily).style("font-size",p+"px").text(T.attributeName),a={};a.tn=v,a.nn=S;const n=v.node().getBBox(),c=S.node().getBBox();if(m=Math.max(m,n.width),g=Math.max(g,c.width),R=Math.max(n.height,c.height),h){const d=T.attributeKeyTypeList!==void 0?T.attributeKeyTypeList.join(","):"",E=t.append("text").classed("er entityLabel",!0).attr("id",`${M}-key`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",Z().fontFamily).style("font-size",p+"px").text(d);a.kn=E;const i=E.node().getBBox();x=Math.max(x,i.width),R=Math.max(R,i.height)}if(_){const d=t.append("text").classed("er entityLabel",!0).attr("id",`${M}-comment`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",Z().fontFamily).style("font-size",p+"px").text(T.attributeComment||"");a.cn=d;const E=d.node().getBBox();y=Math.max(y,E.width),R=Math.max(R,E.height)}a.height=R,o.push(a),N+=R+u*2,I+=1});let F=4;h&&(F+=2),_&&(F+=2);const W=m+g+x+y,C={width:Math.max(k.minEntityWidth,Math.max(f.width+k.entityPadding*2,W+l*F)),height:r.length>0?N:Math.max(k.minEntityHeight,f.height+k.entityPadding*2)};if(r.length>0){const T=Math.max(0,(C.width-W-l*F)/(F/2));e.attr("transform","translate("+C.width/2+","+(u+f.height/2)+")");let M=f.height+u*2,R="attributeBoxOdd";o.forEach(O=>{const v=M+u+O.height/2;O.tn.attr("transform","translate("+l+","+v+")");const S=t.insert("rect","#"+O.tn.node().id).classed(`er ${R}`,!0).attr("x",0).attr("y",M).attr("width",m+l*2+T).attr("height",O.height+u*2),a=parseFloat(S.attr("x"))+parseFloat(S.attr("width"));O.nn.attr("transform","translate("+(a+l)+","+v+")");const n=t.insert("rect","#"+O.nn.node().id).classed(`er ${R}`,!0).attr("x",a).attr("y",M).attr("width",g+l*2+T).attr("height",O.height+u*2);let c=parseFloat(n.attr("x"))+parseFloat(n.attr("width"));if(h){O.kn.attr("transform","translate("+(c+l)+","+v+")");const d=t.insert("rect","#"+O.kn.node().id).classed(`er ${R}`,!0).attr("x",c).attr("y",M).attr("width",x+l*2+T).attr("height",O.height+u*2);c=parseFloat(d.attr("x"))+parseFloat(d.attr("width"))}_&&(O.cn.attr("transform","translate("+(c+l)+","+v+")"),t.insert("rect","#"+O.cn.node().id).classed(`er ${R}`,"true").attr("x",c).attr("y",M).attr("width",y+l*2+T).attr("height",O.height+u*2)),M+=O.height+u*2,R=R==="attributeBoxOdd"?"attributeBoxEven":"attributeBoxOdd"})}else C.height=Math.max(k.minEntityHeight,N),e.attr("transform","translate("+C.width/2+","+C.height/2+")");return C},ee=function(t,e,r){const u=Object.keys(e);let l;return u.forEach(function(p){const f=oe(p,"entity");X.set(p,f);const o=t.append("g").attr("id",f);l=l===void 0?f:l;const h="text-"+f,_=o.append("text").classed("er entityLabel",!0).attr("id",h).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","middle").style("font-family",Z().fontFamily).style("font-size",k.fontSize+"px").text(e[p].alias??p),{width:m,height:g}=te(o,_,e[p].attributes),y=o.insert("rect","#"+h).classed("er entityBox",!0).attr("x",0).attr("y",0).attr("width",m).attr("height",g).node().getBBox();r.setNode(f,{width:y.width,height:y.height,shape:"rect",id:f})}),l},re=function(t,e){e.nodes().forEach(function(r){r!==void 0&&e.node(r)!==void 0&&t.select("#"+r).attr("transform","translate("+(e.node(r).x-e.node(r).width/2)+","+(e.node(r).y-e.node(r).height/2)+" )")})},ut=function(t){return(t.entityA+t.roleA+t.entityB).replace(/\s/g,"")},ie=function(t,e){return t.forEach(function(r){e.setEdge(X.get(r.entityA),X.get(r.entityB),{relationship:r},ut(r))}),t};let ct=0;const ae=function(t,e,r,u,l){ct++;const p=r.edge(X.get(e.entityA),X.get(e.entityB),ut(e)),f=wt().x(function(N){return N.x}).y(function(N){return N.y}).curve(Tt),o=t.insert("path","#"+u).classed("er relationshipLine",!0).attr("d",f(p.points)).style("stroke",k.stroke).style("fill","none");e.relSpec.relType===l.db.Identification.NON_IDENTIFYING&&o.attr("stroke-dasharray","8,8");let h="";switch(k.arrowMarkerAbsolute&&(h=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,h=h.replace(/\(/g,"\\("),h=h.replace(/\)/g,"\\)")),e.relSpec.cardA){case l.db.Cardinality.ZERO_OR_ONE:o.attr("marker-end","url("+h+"#"+B.ERMarkers.ZERO_OR_ONE_END+")");break;case l.db.Cardinality.ZERO_OR_MORE:o.attr("marker-end","url("+h+"#"+B.ERMarkers.ZERO_OR_MORE_END+")");break;case l.db.Cardinality.ONE_OR_MORE:o.attr("marker-end","url("+h+"#"+B.ERMarkers.ONE_OR_MORE_END+")");break;case l.db.Cardinality.ONLY_ONE:o.attr("marker-end","url("+h+"#"+B.ERMarkers.ONLY_ONE_END+")");break;case l.db.Cardinality.MD_PARENT:o.attr("marker-end","url("+h+"#"+B.ERMarkers.MD_PARENT_END+")");break}switch(e.relSpec.cardB){case l.db.Cardinality.ZERO_OR_ONE:o.attr("marker-start","url("+h+"#"+B.ERMarkers.ZERO_OR_ONE_START+")");break;case l.db.Cardinality.ZERO_OR_MORE:o.attr("marker-start","url("+h+"#"+B.ERMarkers.ZERO_OR_MORE_START+")");break;case l.db.Cardinality.ONE_OR_MORE:o.attr("marker-start","url("+h+"#"+B.ERMarkers.ONE_OR_MORE_START+")");break;case l.db.Cardinality.ONLY_ONE:o.attr("marker-start","url("+h+"#"+B.ERMarkers.ONLY_ONE_START+")");break;case l.db.Cardinality.MD_PARENT:o.attr("marker-start","url("+h+"#"+B.ERMarkers.MD_PARENT_START+")");break}const _=o.node().getTotalLength(),m=o.node().getPointAtLength(_*.5),g="rel"+ct,y=t.append("text").classed("er relationshipLabel",!0).attr("id",g).attr("x",m.x).attr("y",m.y).style("text-anchor","middle").style("dominant-baseline","middle").style("font-family",Z().fontFamily).style("font-size",k.fontSize+"px").text(e.roleA).node().getBBox();t.insert("rect","#"+g).classed("er relationshipLabelBox",!0).attr("x",m.x-y.width/2).attr("y",m.y-y.height/2).attr("width",y.width).attr("height",y.height)},ne=function(t,e,r,u){k=Z().er,V.info("Drawing ER diagram");const l=Z().securityLevel;let p;l==="sandbox"&&(p=rt("#i"+e));const o=(l==="sandbox"?rt(p.nodes()[0].contentDocument.body):rt("body")).select(`[id='${e}']`);B.insertMarkers(o,k);let h;h=new Mt({multigraph:!0,directed:!0,compound:!1}).setGraph({rankdir:k.layoutDirection,marginx:20,marginy:20,nodesep:100,edgesep:100,ranksep:100}).setDefaultEdgeLabel(function(){return{}});const _=ee(o,u.db.getEntities(),h),m=ie(u.db.getRelationships(),h);St(h),re(o,h),m.forEach(function(I){ae(o,I,h,_,u)});const g=k.diagramPadding;Ot.insertTitle(o,"entityTitleText",k.titleTopMargin,u.db.getDiagramTitle());const x=o.node().getBBox(),y=x.width+g*2,N=x.height+g*2;bt(o,N,y,k.useMaxWidth),o.attr("viewBox",`${x.x-g} ${x.y-g} ${y} ${N}`)},se="28e9f9db-3c8d-5aa5-9faf-44286ae5937c";function oe(t="",e=""){const r=t.replace(Jt,"");return`${ht(e)}${ht(r)}${Wt(t,se)}`}function ht(t=""){return t.length>0?`${t}-`:""}const le={setConf:$t,draw:ne},ce=t=>`
+ .entityBox {
+ fill: ${t.mainBkg};
+ stroke: ${t.nodeBorder};
+ }
+
+ .attributeBoxOdd {
+ fill: ${t.attributeBackgroundColorOdd};
+ stroke: ${t.nodeBorder};
+ }
+
+ .attributeBoxEven {
+ fill: ${t.attributeBackgroundColorEven};
+ stroke: ${t.nodeBorder};
+ }
+
+ .relationshipLabelBox {
+ fill: ${t.tertiaryColor};
+ opacity: 0.7;
+ background-color: ${t.tertiaryColor};
+ rect {
+ opacity: 0.5;
+ }
+ }
+
+ .relationshipLine {
+ stroke: ${t.lineColor};
+ }
+
+ .entityTitleText {
+ text-anchor: middle;
+ font-size: 18px;
+ fill: ${t.textColor};
+ }
+ #MD_PARENT_START {
+ fill: #f5f5f5 !important;
+ stroke: ${t.lineColor} !important;
+ stroke-width: 1;
+ }
+ #MD_PARENT_END {
+ fill: #f5f5f5 !important;
+ stroke: ${t.lineColor} !important;
+ stroke-width: 1;
+ }
+
+`,he=ce,Ee={parser:Ut,db:jt,renderer:le,styles:he};export{Ee as diagram};
diff --git a/frontend-dist/assets/flowDb-c6c81e3f-DSM4lr0v.js b/frontend-dist/assets/flowDb-c6c81e3f-DSM4lr0v.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a8dd0538b6585da300a1c529c69c8c3c68ba8c5
--- /dev/null
+++ b/frontend-dist/assets/flowDb-c6c81e3f-DSM4lr0v.js
@@ -0,0 +1,10 @@
+import{C as me,D as ye,s as ve,g as Ve,a as Le,b as Ie,aq as Re,l as J1,c as et,E as Ne,A as dt,k as we,d as w1}from"./index-BCNM9-Ly.js";var pt=function(){var e=function(f1,a,o,f){for(o=o||{},f=f1.length;f--;o[f1[f]]=a);return o},u=[1,4],i=[1,3],n=[1,5],c=[1,8,9,10,11,27,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],l=[2,2],h=[1,13],U=[1,14],F=[1,15],w=[1,16],X=[1,23],o1=[1,25],p1=[1,26],A1=[1,27],S=[1,49],k=[1,48],l1=[1,29],U1=[1,30],G1=[1,31],M1=[1,32],K1=[1,33],x=[1,44],B=[1,46],m=[1,42],y=[1,47],v=[1,43],V=[1,50],L=[1,45],I=[1,51],R=[1,52],Y1=[1,34],j1=[1,35],z1=[1,36],X1=[1,37],I1=[1,57],b=[1,8,9,10,11,27,32,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],W=[1,61],Q=[1,60],Z=[1,62],H1=[8,9,11,73,75],k1=[1,88],b1=[1,93],g1=[1,92],D1=[1,89],F1=[1,85],T1=[1,91],C1=[1,87],S1=[1,94],_1=[1,90],x1=[1,95],B1=[1,86],q1=[8,9,10,11,73,75],N=[8,9,10,11,44,73,75],M=[8,9,10,11,29,42,44,46,48,50,52,54,56,58,61,63,65,66,68,73,75,86,99,102,103,106,108,111,112,113],Et=[8,9,11,42,58,73,75,86,99,102,103,106,108,111,112,113],R1=[42,58,86,99,102,103,106,108,111,112,113],kt=[1,121],bt=[1,120],gt=[1,128],Dt=[1,142],Ft=[1,143],Tt=[1,144],Ct=[1,145],St=[1,130],_t=[1,132],xt=[1,136],Bt=[1,137],mt=[1,138],yt=[1,139],vt=[1,140],Vt=[1,141],Lt=[1,146],It=[1,147],Rt=[1,126],Nt=[1,127],wt=[1,134],Ot=[1,129],Pt=[1,133],Ut=[1,131],nt=[8,9,10,11,27,32,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],Gt=[1,149],T=[8,9,11],K=[8,9,10,11,14,42,58,86,102,103,106,108,111,112,113],p=[1,169],O=[1,165],P=[1,166],A=[1,170],d=[1,167],E=[1,168],m1=[75,113,116],g=[8,9,10,11,12,14,27,29,32,42,58,73,81,82,83,84,85,86,87,102,106,108,111,112,113],Mt=[10,103],h1=[31,47,49,51,53,55,60,62,64,65,67,69,113,114,115],J=[1,235],$=[1,233],t1=[1,237],e1=[1,231],s1=[1,232],u1=[1,234],i1=[1,236],r1=[1,238],y1=[1,255],Kt=[8,9,11,103],q=[8,9,10,11,58,81,102,103,106,107,108,109],at={trace:function(){},yy:{},symbols_:{error:2,start:3,graphConfig:4,document:5,line:6,statement:7,SEMI:8,NEWLINE:9,SPACE:10,EOF:11,GRAPH:12,NODIR:13,DIR:14,FirstStmtSeparator:15,ending:16,endToken:17,spaceList:18,spaceListNewline:19,vertexStatement:20,separator:21,styleStatement:22,linkStyleStatement:23,classDefStatement:24,classStatement:25,clickStatement:26,subgraph:27,textNoTags:28,SQS:29,text:30,SQE:31,end:32,direction:33,acc_title:34,acc_title_value:35,acc_descr:36,acc_descr_value:37,acc_descr_multiline_value:38,link:39,node:40,styledVertex:41,AMP:42,vertex:43,STYLE_SEPARATOR:44,idString:45,DOUBLECIRCLESTART:46,DOUBLECIRCLEEND:47,PS:48,PE:49,"(-":50,"-)":51,STADIUMSTART:52,STADIUMEND:53,SUBROUTINESTART:54,SUBROUTINEEND:55,VERTEX_WITH_PROPS_START:56,"NODE_STRING[field]":57,COLON:58,"NODE_STRING[value]":59,PIPE:60,CYLINDERSTART:61,CYLINDEREND:62,DIAMOND_START:63,DIAMOND_STOP:64,TAGEND:65,TRAPSTART:66,TRAPEND:67,INVTRAPSTART:68,INVTRAPEND:69,linkStatement:70,arrowText:71,TESTSTR:72,START_LINK:73,edgeText:74,LINK:75,edgeTextToken:76,STR:77,MD_STR:78,textToken:79,keywords:80,STYLE:81,LINKSTYLE:82,CLASSDEF:83,CLASS:84,CLICK:85,DOWN:86,UP:87,textNoTagsToken:88,stylesOpt:89,"idString[vertex]":90,"idString[class]":91,CALLBACKNAME:92,CALLBACKARGS:93,HREF:94,LINK_TARGET:95,"STR[link]":96,"STR[tooltip]":97,alphaNum:98,DEFAULT:99,numList:100,INTERPOLATE:101,NUM:102,COMMA:103,style:104,styleComponent:105,NODE_STRING:106,UNIT:107,BRKT:108,PCT:109,idStringToken:110,MINUS:111,MULT:112,UNICODE_TEXT:113,TEXT:114,TAGSTART:115,EDGE_TEXT:116,alphaNumToken:117,direction_tb:118,direction_bt:119,direction_rl:120,direction_lr:121,$accept:0,$end:1},terminals_:{2:"error",8:"SEMI",9:"NEWLINE",10:"SPACE",11:"EOF",12:"GRAPH",13:"NODIR",14:"DIR",27:"subgraph",29:"SQS",31:"SQE",32:"end",34:"acc_title",35:"acc_title_value",36:"acc_descr",37:"acc_descr_value",38:"acc_descr_multiline_value",42:"AMP",44:"STYLE_SEPARATOR",46:"DOUBLECIRCLESTART",47:"DOUBLECIRCLEEND",48:"PS",49:"PE",50:"(-",51:"-)",52:"STADIUMSTART",53:"STADIUMEND",54:"SUBROUTINESTART",55:"SUBROUTINEEND",56:"VERTEX_WITH_PROPS_START",57:"NODE_STRING[field]",58:"COLON",59:"NODE_STRING[value]",60:"PIPE",61:"CYLINDERSTART",62:"CYLINDEREND",63:"DIAMOND_START",64:"DIAMOND_STOP",65:"TAGEND",66:"TRAPSTART",67:"TRAPEND",68:"INVTRAPSTART",69:"INVTRAPEND",72:"TESTSTR",73:"START_LINK",75:"LINK",77:"STR",78:"MD_STR",81:"STYLE",82:"LINKSTYLE",83:"CLASSDEF",84:"CLASS",85:"CLICK",86:"DOWN",87:"UP",90:"idString[vertex]",91:"idString[class]",92:"CALLBACKNAME",93:"CALLBACKARGS",94:"HREF",95:"LINK_TARGET",96:"STR[link]",97:"STR[tooltip]",99:"DEFAULT",101:"INTERPOLATE",102:"NUM",103:"COMMA",106:"NODE_STRING",107:"UNIT",108:"BRKT",109:"PCT",111:"MINUS",112:"MULT",113:"UNICODE_TEXT",114:"TEXT",115:"TAGSTART",116:"EDGE_TEXT",118:"direction_tb",119:"direction_bt",120:"direction_rl",121:"direction_lr"},productions_:[0,[3,2],[5,0],[5,2],[6,1],[6,1],[6,1],[6,1],[6,1],[4,2],[4,2],[4,2],[4,3],[16,2],[16,1],[17,1],[17,1],[17,1],[15,1],[15,1],[15,2],[19,2],[19,2],[19,1],[19,1],[18,2],[18,1],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,9],[7,6],[7,4],[7,1],[7,2],[7,2],[7,1],[21,1],[21,1],[21,1],[20,3],[20,4],[20,2],[20,1],[40,1],[40,5],[41,1],[41,3],[43,4],[43,4],[43,6],[43,4],[43,4],[43,4],[43,8],[43,4],[43,4],[43,4],[43,6],[43,4],[43,4],[43,4],[43,4],[43,4],[43,1],[39,2],[39,3],[39,3],[39,1],[39,3],[74,1],[74,2],[74,1],[74,1],[70,1],[71,3],[30,1],[30,2],[30,1],[30,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[28,1],[28,2],[28,1],[28,1],[24,5],[25,5],[26,2],[26,4],[26,3],[26,5],[26,3],[26,5],[26,5],[26,7],[26,2],[26,4],[26,2],[26,4],[26,4],[26,6],[22,5],[23,5],[23,5],[23,9],[23,9],[23,7],[23,7],[100,1],[100,3],[89,1],[89,3],[104,1],[104,2],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[79,1],[79,1],[79,1],[79,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[76,1],[76,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[45,1],[45,2],[98,1],[98,2],[33,1],[33,1],[33,1],[33,1]],performAction:function(a,o,f,r,C,t,N1){var s=t.length-1;switch(C){case 2:this.$=[];break;case 3:(!Array.isArray(t[s])||t[s].length>0)&&t[s-1].push(t[s]),this.$=t[s-1];break;case 4:case 176:this.$=t[s];break;case 11:r.setDirection("TB"),this.$="TB";break;case 12:r.setDirection(t[s-1]),this.$=t[s-1];break;case 27:this.$=t[s-1].nodes;break;case 28:case 29:case 30:case 31:case 32:this.$=[];break;case 33:this.$=r.addSubGraph(t[s-6],t[s-1],t[s-4]);break;case 34:this.$=r.addSubGraph(t[s-3],t[s-1],t[s-3]);break;case 35:this.$=r.addSubGraph(void 0,t[s-1],void 0);break;case 37:this.$=t[s].trim(),r.setAccTitle(this.$);break;case 38:case 39:this.$=t[s].trim(),r.setAccDescription(this.$);break;case 43:r.addLink(t[s-2].stmt,t[s],t[s-1]),this.$={stmt:t[s],nodes:t[s].concat(t[s-2].nodes)};break;case 44:r.addLink(t[s-3].stmt,t[s-1],t[s-2]),this.$={stmt:t[s-1],nodes:t[s-1].concat(t[s-3].nodes)};break;case 45:this.$={stmt:t[s-1],nodes:t[s-1]};break;case 46:this.$={stmt:t[s],nodes:t[s]};break;case 47:this.$=[t[s]];break;case 48:this.$=t[s-4].concat(t[s]);break;case 49:this.$=t[s];break;case 50:this.$=t[s-2],r.setClass(t[s-2],t[s]);break;case 51:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"square");break;case 52:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"doublecircle");break;case 53:this.$=t[s-5],r.addVertex(t[s-5],t[s-2],"circle");break;case 54:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"ellipse");break;case 55:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"stadium");break;case 56:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"subroutine");break;case 57:this.$=t[s-7],r.addVertex(t[s-7],t[s-1],"rect",void 0,void 0,void 0,Object.fromEntries([[t[s-5],t[s-3]]]));break;case 58:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"cylinder");break;case 59:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"round");break;case 60:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"diamond");break;case 61:this.$=t[s-5],r.addVertex(t[s-5],t[s-2],"hexagon");break;case 62:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"odd");break;case 63:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"trapezoid");break;case 64:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"inv_trapezoid");break;case 65:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"lean_right");break;case 66:this.$=t[s-3],r.addVertex(t[s-3],t[s-1],"lean_left");break;case 67:this.$=t[s],r.addVertex(t[s]);break;case 68:t[s-1].text=t[s],this.$=t[s-1];break;case 69:case 70:t[s-2].text=t[s-1],this.$=t[s-2];break;case 71:this.$=t[s];break;case 72:var Y=r.destructLink(t[s],t[s-2]);this.$={type:Y.type,stroke:Y.stroke,length:Y.length,text:t[s-1]};break;case 73:this.$={text:t[s],type:"text"};break;case 74:this.$={text:t[s-1].text+""+t[s],type:t[s-1].type};break;case 75:this.$={text:t[s],type:"string"};break;case 76:this.$={text:t[s],type:"markdown"};break;case 77:var Y=r.destructLink(t[s]);this.$={type:Y.type,stroke:Y.stroke,length:Y.length};break;case 78:this.$=t[s-1];break;case 79:this.$={text:t[s],type:"text"};break;case 80:this.$={text:t[s-1].text+""+t[s],type:t[s-1].type};break;case 81:this.$={text:t[s],type:"string"};break;case 82:case 97:this.$={text:t[s],type:"markdown"};break;case 94:this.$={text:t[s],type:"text"};break;case 95:this.$={text:t[s-1].text+""+t[s],type:t[s-1].type};break;case 96:this.$={text:t[s],type:"text"};break;case 98:this.$=t[s-4],r.addClass(t[s-2],t[s]);break;case 99:this.$=t[s-4],r.setClass(t[s-2],t[s]);break;case 100:case 108:this.$=t[s-1],r.setClickEvent(t[s-1],t[s]);break;case 101:case 109:this.$=t[s-3],r.setClickEvent(t[s-3],t[s-2]),r.setTooltip(t[s-3],t[s]);break;case 102:this.$=t[s-2],r.setClickEvent(t[s-2],t[s-1],t[s]);break;case 103:this.$=t[s-4],r.setClickEvent(t[s-4],t[s-3],t[s-2]),r.setTooltip(t[s-4],t[s]);break;case 104:this.$=t[s-2],r.setLink(t[s-2],t[s]);break;case 105:this.$=t[s-4],r.setLink(t[s-4],t[s-2]),r.setTooltip(t[s-4],t[s]);break;case 106:this.$=t[s-4],r.setLink(t[s-4],t[s-2],t[s]);break;case 107:this.$=t[s-6],r.setLink(t[s-6],t[s-4],t[s]),r.setTooltip(t[s-6],t[s-2]);break;case 110:this.$=t[s-1],r.setLink(t[s-1],t[s]);break;case 111:this.$=t[s-3],r.setLink(t[s-3],t[s-2]),r.setTooltip(t[s-3],t[s]);break;case 112:this.$=t[s-3],r.setLink(t[s-3],t[s-2],t[s]);break;case 113:this.$=t[s-5],r.setLink(t[s-5],t[s-4],t[s]),r.setTooltip(t[s-5],t[s-2]);break;case 114:this.$=t[s-4],r.addVertex(t[s-2],void 0,void 0,t[s]);break;case 115:this.$=t[s-4],r.updateLink([t[s-2]],t[s]);break;case 116:this.$=t[s-4],r.updateLink(t[s-2],t[s]);break;case 117:this.$=t[s-8],r.updateLinkInterpolate([t[s-6]],t[s-2]),r.updateLink([t[s-6]],t[s]);break;case 118:this.$=t[s-8],r.updateLinkInterpolate(t[s-6],t[s-2]),r.updateLink(t[s-6],t[s]);break;case 119:this.$=t[s-6],r.updateLinkInterpolate([t[s-4]],t[s]);break;case 120:this.$=t[s-6],r.updateLinkInterpolate(t[s-4],t[s]);break;case 121:case 123:this.$=[t[s]];break;case 122:case 124:t[s-2].push(t[s]),this.$=t[s-2];break;case 126:this.$=t[s-1]+t[s];break;case 174:this.$=t[s];break;case 175:this.$=t[s-1]+""+t[s];break;case 177:this.$=t[s-1]+""+t[s];break;case 178:this.$={stmt:"dir",value:"TB"};break;case 179:this.$={stmt:"dir",value:"BT"};break;case 180:this.$={stmt:"dir",value:"RL"};break;case 181:this.$={stmt:"dir",value:"LR"};break}},table:[{3:1,4:2,9:u,10:i,12:n},{1:[3]},e(c,l,{5:6}),{4:7,9:u,10:i,12:n},{4:8,9:u,10:i,12:n},{13:[1,9],14:[1,10]},{1:[2,1],6:11,7:12,8:h,9:U,10:F,11:w,20:17,22:18,23:19,24:20,25:21,26:22,27:X,33:24,34:o1,36:p1,38:A1,40:28,41:38,42:S,43:39,45:40,58:k,81:l1,82:U1,83:G1,84:M1,85:K1,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R,118:Y1,119:j1,120:z1,121:X1},e(c,[2,9]),e(c,[2,10]),e(c,[2,11]),{8:[1,54],9:[1,55],10:I1,15:53,18:56},e(b,[2,3]),e(b,[2,4]),e(b,[2,5]),e(b,[2,6]),e(b,[2,7]),e(b,[2,8]),{8:W,9:Q,11:Z,21:58,39:59,70:63,73:[1,64],75:[1,65]},{8:W,9:Q,11:Z,21:66},{8:W,9:Q,11:Z,21:67},{8:W,9:Q,11:Z,21:68},{8:W,9:Q,11:Z,21:69},{8:W,9:Q,11:Z,21:70},{8:W,9:Q,10:[1,71],11:Z,21:72},e(b,[2,36]),{35:[1,73]},{37:[1,74]},e(b,[2,39]),e(H1,[2,46],{18:75,10:I1}),{10:[1,76]},{10:[1,77]},{10:[1,78]},{10:[1,79]},{14:k1,42:b1,58:g1,77:[1,83],86:D1,92:[1,80],94:[1,81],98:82,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1,117:84},e(b,[2,178]),e(b,[2,179]),e(b,[2,180]),e(b,[2,181]),e(q1,[2,47]),e(q1,[2,49],{44:[1,96]}),e(N,[2,67],{110:109,29:[1,97],42:S,46:[1,98],48:[1,99],50:[1,100],52:[1,101],54:[1,102],56:[1,103],58:k,61:[1,104],63:[1,105],65:[1,106],66:[1,107],68:[1,108],86:x,99:B,102:m,103:y,106:v,108:V,111:L,112:I,113:R}),e(M,[2,174]),e(M,[2,135]),e(M,[2,136]),e(M,[2,137]),e(M,[2,138]),e(M,[2,139]),e(M,[2,140]),e(M,[2,141]),e(M,[2,142]),e(M,[2,143]),e(M,[2,144]),e(M,[2,145]),e(c,[2,12]),e(c,[2,18]),e(c,[2,19]),{9:[1,110]},e(Et,[2,26],{18:111,10:I1}),e(b,[2,27]),{40:112,41:38,42:S,43:39,45:40,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},e(b,[2,40]),e(b,[2,41]),e(b,[2,42]),e(R1,[2,71],{71:113,60:[1,115],72:[1,114]}),{74:116,76:117,77:[1,118],78:[1,119],113:kt,116:bt},e([42,58,60,72,86,99,102,103,106,108,111,112,113],[2,77]),e(b,[2,28]),e(b,[2,29]),e(b,[2,30]),e(b,[2,31]),e(b,[2,32]),{10:gt,12:Dt,14:Ft,27:Tt,28:122,32:Ct,42:St,58:_t,73:xt,77:[1,124],78:[1,125],80:135,81:Bt,82:mt,83:yt,84:vt,85:Vt,86:Lt,87:It,88:123,102:Rt,106:Nt,108:wt,111:Ot,112:Pt,113:Ut},e(nt,l,{5:148}),e(b,[2,37]),e(b,[2,38]),e(H1,[2,45],{42:Gt}),{42:S,45:150,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},{99:[1,151],100:152,102:[1,153]},{42:S,45:154,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},{42:S,45:155,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},e(T,[2,100],{10:[1,156],93:[1,157]}),{77:[1,158]},e(T,[2,108],{117:160,10:[1,159],14:k1,42:b1,58:g1,86:D1,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1}),e(T,[2,110],{10:[1,161]}),e(K,[2,176]),e(K,[2,163]),e(K,[2,164]),e(K,[2,165]),e(K,[2,166]),e(K,[2,167]),e(K,[2,168]),e(K,[2,169]),e(K,[2,170]),e(K,[2,171]),e(K,[2,172]),e(K,[2,173]),{42:S,45:162,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},{30:163,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:171,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:173,48:[1,172],65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:174,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:175,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:176,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{106:[1,177]},{30:178,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:179,63:[1,180],65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:181,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:182,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{30:183,65:p,77:O,78:P,79:164,113:A,114:d,115:E},e(M,[2,175]),e(c,[2,20]),e(Et,[2,25]),e(H1,[2,43],{18:184,10:I1}),e(R1,[2,68],{10:[1,185]}),{10:[1,186]},{30:187,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{75:[1,188],76:189,113:kt,116:bt},e(m1,[2,73]),e(m1,[2,75]),e(m1,[2,76]),e(m1,[2,161]),e(m1,[2,162]),{8:W,9:Q,10:gt,11:Z,12:Dt,14:Ft,21:191,27:Tt,29:[1,190],32:Ct,42:St,58:_t,73:xt,80:135,81:Bt,82:mt,83:yt,84:vt,85:Vt,86:Lt,87:It,88:192,102:Rt,106:Nt,108:wt,111:Ot,112:Pt,113:Ut},e(g,[2,94]),e(g,[2,96]),e(g,[2,97]),e(g,[2,150]),e(g,[2,151]),e(g,[2,152]),e(g,[2,153]),e(g,[2,154]),e(g,[2,155]),e(g,[2,156]),e(g,[2,157]),e(g,[2,158]),e(g,[2,159]),e(g,[2,160]),e(g,[2,83]),e(g,[2,84]),e(g,[2,85]),e(g,[2,86]),e(g,[2,87]),e(g,[2,88]),e(g,[2,89]),e(g,[2,90]),e(g,[2,91]),e(g,[2,92]),e(g,[2,93]),{6:11,7:12,8:h,9:U,10:F,11:w,20:17,22:18,23:19,24:20,25:21,26:22,27:X,32:[1,193],33:24,34:o1,36:p1,38:A1,40:28,41:38,42:S,43:39,45:40,58:k,81:l1,82:U1,83:G1,84:M1,85:K1,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R,118:Y1,119:j1,120:z1,121:X1},{10:I1,18:194},{10:[1,195],42:S,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:109,111:L,112:I,113:R},{10:[1,196]},{10:[1,197],103:[1,198]},e(Mt,[2,121]),{10:[1,199],42:S,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:109,111:L,112:I,113:R},{10:[1,200],42:S,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:109,111:L,112:I,113:R},{77:[1,201]},e(T,[2,102],{10:[1,202]}),e(T,[2,104],{10:[1,203]}),{77:[1,204]},e(K,[2,177]),{77:[1,205],95:[1,206]},e(q1,[2,50],{110:109,42:S,58:k,86:x,99:B,102:m,103:y,106:v,108:V,111:L,112:I,113:R}),{31:[1,207],65:p,79:208,113:A,114:d,115:E},e(h1,[2,79]),e(h1,[2,81]),e(h1,[2,82]),e(h1,[2,146]),e(h1,[2,147]),e(h1,[2,148]),e(h1,[2,149]),{47:[1,209],65:p,79:208,113:A,114:d,115:E},{30:210,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{49:[1,211],65:p,79:208,113:A,114:d,115:E},{51:[1,212],65:p,79:208,113:A,114:d,115:E},{53:[1,213],65:p,79:208,113:A,114:d,115:E},{55:[1,214],65:p,79:208,113:A,114:d,115:E},{58:[1,215]},{62:[1,216],65:p,79:208,113:A,114:d,115:E},{64:[1,217],65:p,79:208,113:A,114:d,115:E},{30:218,65:p,77:O,78:P,79:164,113:A,114:d,115:E},{31:[1,219],65:p,79:208,113:A,114:d,115:E},{65:p,67:[1,220],69:[1,221],79:208,113:A,114:d,115:E},{65:p,67:[1,223],69:[1,222],79:208,113:A,114:d,115:E},e(H1,[2,44],{42:Gt}),e(R1,[2,70]),e(R1,[2,69]),{60:[1,224],65:p,79:208,113:A,114:d,115:E},e(R1,[2,72]),e(m1,[2,74]),{30:225,65:p,77:O,78:P,79:164,113:A,114:d,115:E},e(nt,l,{5:226}),e(g,[2,95]),e(b,[2,35]),{41:227,42:S,43:39,45:40,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},{10:J,58:$,81:t1,89:228,102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},{10:J,58:$,81:t1,89:239,101:[1,240],102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},{10:J,58:$,81:t1,89:241,101:[1,242],102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},{102:[1,243]},{10:J,58:$,81:t1,89:244,102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},{42:S,45:245,58:k,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R},e(T,[2,101]),{77:[1,246]},{77:[1,247],95:[1,248]},e(T,[2,109]),e(T,[2,111],{10:[1,249]}),e(T,[2,112]),e(N,[2,51]),e(h1,[2,80]),e(N,[2,52]),{49:[1,250],65:p,79:208,113:A,114:d,115:E},e(N,[2,59]),e(N,[2,54]),e(N,[2,55]),e(N,[2,56]),{106:[1,251]},e(N,[2,58]),e(N,[2,60]),{64:[1,252],65:p,79:208,113:A,114:d,115:E},e(N,[2,62]),e(N,[2,63]),e(N,[2,65]),e(N,[2,64]),e(N,[2,66]),e([10,42,58,86,99,102,103,106,108,111,112,113],[2,78]),{31:[1,253],65:p,79:208,113:A,114:d,115:E},{6:11,7:12,8:h,9:U,10:F,11:w,20:17,22:18,23:19,24:20,25:21,26:22,27:X,32:[1,254],33:24,34:o1,36:p1,38:A1,40:28,41:38,42:S,43:39,45:40,58:k,81:l1,82:U1,83:G1,84:M1,85:K1,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R,118:Y1,119:j1,120:z1,121:X1},e(q1,[2,48]),e(T,[2,114],{103:y1}),e(Kt,[2,123],{105:256,10:J,58:$,81:t1,102:e1,106:s1,107:u1,108:i1,109:r1}),e(q,[2,125]),e(q,[2,127]),e(q,[2,128]),e(q,[2,129]),e(q,[2,130]),e(q,[2,131]),e(q,[2,132]),e(q,[2,133]),e(q,[2,134]),e(T,[2,115],{103:y1}),{10:[1,257]},e(T,[2,116],{103:y1}),{10:[1,258]},e(Mt,[2,122]),e(T,[2,98],{103:y1}),e(T,[2,99],{110:109,42:S,58:k,86:x,99:B,102:m,103:y,106:v,108:V,111:L,112:I,113:R}),e(T,[2,103]),e(T,[2,105],{10:[1,259]}),e(T,[2,106]),{95:[1,260]},{49:[1,261]},{60:[1,262]},{64:[1,263]},{8:W,9:Q,11:Z,21:264},e(b,[2,34]),{10:J,58:$,81:t1,102:e1,104:265,105:230,106:s1,107:u1,108:i1,109:r1},e(q,[2,126]),{14:k1,42:b1,58:g1,86:D1,98:266,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1,117:84},{14:k1,42:b1,58:g1,86:D1,98:267,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1,117:84},{95:[1,268]},e(T,[2,113]),e(N,[2,53]),{30:269,65:p,77:O,78:P,79:164,113:A,114:d,115:E},e(N,[2,61]),e(nt,l,{5:270}),e(Kt,[2,124],{105:256,10:J,58:$,81:t1,102:e1,106:s1,107:u1,108:i1,109:r1}),e(T,[2,119],{117:160,10:[1,271],14:k1,42:b1,58:g1,86:D1,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1}),e(T,[2,120],{117:160,10:[1,272],14:k1,42:b1,58:g1,86:D1,102:F1,103:T1,106:C1,108:S1,111:_1,112:x1,113:B1}),e(T,[2,107]),{31:[1,273],65:p,79:208,113:A,114:d,115:E},{6:11,7:12,8:h,9:U,10:F,11:w,20:17,22:18,23:19,24:20,25:21,26:22,27:X,32:[1,274],33:24,34:o1,36:p1,38:A1,40:28,41:38,42:S,43:39,45:40,58:k,81:l1,82:U1,83:G1,84:M1,85:K1,86:x,99:B,102:m,103:y,106:v,108:V,110:41,111:L,112:I,113:R,118:Y1,119:j1,120:z1,121:X1},{10:J,58:$,81:t1,89:275,102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},{10:J,58:$,81:t1,89:276,102:e1,104:229,105:230,106:s1,107:u1,108:i1,109:r1},e(N,[2,57]),e(b,[2,33]),e(T,[2,117],{103:y1}),e(T,[2,118],{103:y1})],defaultActions:{},parseError:function(a,o){if(o.recoverable)this.trace(a);else{var f=new Error(a);throw f.hash=o,f}},parse:function(a){var o=this,f=[0],r=[],C=[null],t=[],N1=this.table,s="",Y=0,Yt=0,Se=2,jt=1,_e=t.slice.call(arguments,1),_=Object.create(this.lexer),d1={yy:{}};for(var ot in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ot)&&(d1.yy[ot]=this.yy[ot]);_.setInput(a,d1.yy),d1.yy.lexer=_,d1.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var lt=_.yylloc;t.push(lt);var xe=_.options&&_.options.ranges;typeof d1.yy.parseError=="function"?this.parseError=d1.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Be(){var a1;return a1=r.pop()||_.lex()||jt,typeof a1!="number"&&(a1 instanceof Array&&(r=a1,a1=r.pop()),a1=o.symbols_[a1]||a1),a1}for(var G,E1,j,ht,v1={},W1,n1,zt,Q1;;){if(E1=f[f.length-1],this.defaultActions[E1]?j=this.defaultActions[E1]:((G===null||typeof G>"u")&&(G=Be()),j=N1[E1]&&N1[E1][G]),typeof j>"u"||!j.length||!j[0]){var ft="";Q1=[];for(W1 in N1[E1])this.terminals_[W1]&&W1>Se&&Q1.push("'"+this.terminals_[W1]+"'");_.showPosition?ft="Parse error on line "+(Y+1)+`:
+`+_.showPosition()+`
+Expecting `+Q1.join(", ")+", got '"+(this.terminals_[G]||G)+"'":ft="Parse error on line "+(Y+1)+": Unexpected "+(G==jt?"end of input":"'"+(this.terminals_[G]||G)+"'"),this.parseError(ft,{text:_.match,token:this.terminals_[G]||G,line:_.yylineno,loc:lt,expected:Q1})}if(j[0]instanceof Array&&j.length>1)throw new Error("Parse Error: multiple actions possible at state: "+E1+", token: "+G);switch(j[0]){case 1:f.push(G),C.push(_.yytext),t.push(_.yylloc),f.push(j[1]),G=null,Yt=_.yyleng,s=_.yytext,Y=_.yylineno,lt=_.yylloc;break;case 2:if(n1=this.productions_[j[1]][1],v1.$=C[C.length-n1],v1._$={first_line:t[t.length-(n1||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(n1||1)].first_column,last_column:t[t.length-1].last_column},xe&&(v1._$.range=[t[t.length-(n1||1)].range[0],t[t.length-1].range[1]]),ht=this.performAction.apply(v1,[s,Yt,Y,d1.yy,j[1],C,t].concat(_e)),typeof ht<"u")return ht;n1&&(f=f.slice(0,-1*n1*2),C=C.slice(0,-1*n1),t=t.slice(0,-1*n1)),f.push(this.productions_[j[1]][0]),C.push(v1.$),t.push(v1._$),zt=N1[f[f.length-2]][f[f.length-1]],f.push(zt);break;case 3:return!0}}return!0}},Ce=function(){var f1={EOF:1,parseError:function(o,f){if(this.yy.parser)this.yy.parser.parseError(o,f);else throw new Error(o)},setInput:function(a,o){return this.yy=o||this.yy||{},this._input=a,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var o=a.match(/(?:\r\n?|\n).*/g);return o?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var o=a.length,f=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-o),this.offset-=o;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),f.length-1&&(this.yylineno-=f.length-1);var C=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:f?(f.length===r.length?this.yylloc.first_column:0)+r[r.length-f.length].length-f[0].length:this.yylloc.first_column-o},this.options.ranges&&(this.yylloc.range=[C[0],C[0]+this.yyleng-o]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
+`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),o=new Array(a.length+1).join("-");return a+this.upcomingInput()+`
+`+o+"^"},test_match:function(a,o){var f,r,C;if(this.options.backtrack_lexer&&(C={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(C.yylloc.range=this.yylloc.range.slice(0))),r=a[0].match(/(?:\r\n?|\n).*/g),r&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+a[0].length},this.yytext+=a[0],this.match+=a[0],this.matches=a,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(a[0].length),this.matched+=a[0],f=this.performAction.call(this,this.yy,this,o,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),f)return f;if(this._backtrack){for(var t in C)this[t]=C[t];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,o,f,r;this._more||(this.yytext="",this.match="");for(var C=this._currentRules(),t=0;to[0].length)){if(o=f,r=t,this.options.backtrack_lexer){if(a=this.test_match(f,C[t]),a!==!1)return a;if(this._backtrack){o=!1;continue}else return!1}else if(!this.options.flex)break}return o?(a=this.test_match(o,C[r]),a!==!1?a:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text.
+`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var o=this.next();return o||this.lex()},begin:function(o){this.conditionStack.push(o)},popState:function(){var o=this.conditionStack.length-1;return o>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(o){return o=this.conditionStack.length-1-Math.abs(o||0),o>=0?this.conditionStack[o]:"INITIAL"},pushState:function(o){this.begin(o)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(o,f,r,C){switch(r){case 0:return this.begin("acc_title"),34;case 1:return this.popState(),"acc_title_value";case 2:return this.begin("acc_descr"),36;case 3:return this.popState(),"acc_descr_value";case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:this.begin("callbackname");break;case 8:this.popState();break;case 9:this.popState(),this.begin("callbackargs");break;case 10:return 92;case 11:this.popState();break;case 12:return 93;case 13:return"MD_STR";case 14:this.popState();break;case 15:this.begin("md_string");break;case 16:return"STR";case 17:this.popState();break;case 18:this.pushState("string");break;case 19:return 81;case 20:return 99;case 21:return 82;case 22:return 101;case 23:return 83;case 24:return 84;case 25:return 94;case 26:this.begin("click");break;case 27:this.popState();break;case 28:return 85;case 29:return o.lex.firstGraph()&&this.begin("dir"),12;case 30:return o.lex.firstGraph()&&this.begin("dir"),12;case 31:return o.lex.firstGraph()&&this.begin("dir"),12;case 32:return 27;case 33:return 32;case 34:return 95;case 35:return 95;case 36:return 95;case 37:return 95;case 38:return this.popState(),13;case 39:return this.popState(),14;case 40:return this.popState(),14;case 41:return this.popState(),14;case 42:return this.popState(),14;case 43:return this.popState(),14;case 44:return this.popState(),14;case 45:return this.popState(),14;case 46:return this.popState(),14;case 47:return this.popState(),14;case 48:return this.popState(),14;case 49:return 118;case 50:return 119;case 51:return 120;case 52:return 121;case 53:return 102;case 54:return 108;case 55:return 44;case 56:return 58;case 57:return 42;case 58:return 8;case 59:return 103;case 60:return 112;case 61:return this.popState(),75;case 62:return this.pushState("edgeText"),73;case 63:return 116;case 64:return this.popState(),75;case 65:return this.pushState("thickEdgeText"),73;case 66:return 116;case 67:return this.popState(),75;case 68:return this.pushState("dottedEdgeText"),73;case 69:return 116;case 70:return 75;case 71:return this.popState(),51;case 72:return"TEXT";case 73:return this.pushState("ellipseText"),50;case 74:return this.popState(),53;case 75:return this.pushState("text"),52;case 76:return this.popState(),55;case 77:return this.pushState("text"),54;case 78:return 56;case 79:return this.pushState("text"),65;case 80:return this.popState(),62;case 81:return this.pushState("text"),61;case 82:return this.popState(),47;case 83:return this.pushState("text"),46;case 84:return this.popState(),67;case 85:return this.popState(),69;case 86:return 114;case 87:return this.pushState("trapText"),66;case 88:return this.pushState("trapText"),68;case 89:return 115;case 90:return 65;case 91:return 87;case 92:return"SEP";case 93:return 86;case 94:return 112;case 95:return 108;case 96:return 42;case 97:return 106;case 98:return 111;case 99:return 113;case 100:return this.popState(),60;case 101:return this.pushState("text"),60;case 102:return this.popState(),49;case 103:return this.pushState("text"),48;case 104:return this.popState(),31;case 105:return this.pushState("text"),29;case 106:return this.popState(),64;case 107:return this.pushState("text"),63;case 108:return"TEXT";case 109:return"QUOTE";case 110:return 9;case 111:return 10;case 112:return 11}},rules:[/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["][`])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:["])/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s])/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:flowchart-elk\b)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:[^-]|-(?!-)+)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:[^=]|=(?!))/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:[^\.]|\.(?!))/,/^(?:\s*~~[\~]+\s*)/,/^(?:[-/\)][\)])/,/^(?:[^\(\)\[\]\{\}]|!\)+)/,/^(?:\(-)/,/^(?:\]\))/,/^(?:\(\[)/,/^(?:\]\])/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:>)/,/^(?:\)\])/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\(\(\()/,/^(?:[\\(?=\])][\]])/,/^(?:\/(?=\])\])/,/^(?:\/(?!\])|\\(?!\])|[^\\\[\]\(\)\{\}\/]+)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:\*)/,/^(?:#)/,/^(?:&)/,/^(?:([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|-(?=[^\>\-\.])|(?!))+)/,/^(?:-)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\|)/,/^(?:\))/,/^(?:\()/,/^(?:\])/,/^(?:\[)/,/^(?:(\}))/,/^(?:\{)/,/^(?:[^\[\]\(\)\{\}\|\"]+)/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{callbackargs:{rules:[11,12,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},callbackname:{rules:[8,9,10,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},href:{rules:[15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},click:{rules:[15,18,27,28,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},dottedEdgeText:{rules:[15,18,67,69,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},thickEdgeText:{rules:[15,18,64,66,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},edgeText:{rules:[15,18,61,63,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},trapText:{rules:[15,18,70,73,75,77,81,83,84,85,86,87,88,101,103,105,107],inclusive:!1},ellipseText:{rules:[15,18,70,71,72,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},text:{rules:[15,18,70,73,74,75,76,77,80,81,82,83,87,88,100,101,102,103,104,105,106,107,108],inclusive:!1},vertex:{rules:[15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},dir:{rules:[15,18,38,39,40,41,42,43,44,45,46,47,48,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_descr_multiline:{rules:[5,6,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_descr:{rules:[3,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_title:{rules:[1,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},md_string:{rules:[13,14,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},string:{rules:[15,16,17,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},INITIAL:{rules:[0,2,4,7,15,18,19,20,21,22,23,24,25,26,29,30,31,32,33,34,35,36,37,49,50,51,52,53,54,55,56,57,58,59,60,61,62,64,65,67,68,70,73,75,77,78,79,81,83,87,88,89,90,91,92,93,94,95,96,97,98,99,101,103,105,107,109,110,111,112],inclusive:!0}}};return f1}();at.lexer=Ce;function ct(){this.yy={}}return ct.prototype=at,at.Parser=ct,new ct}();pt.parser=pt;const Xe=pt,Oe="flowchart-";let Xt=0,L1=et(),D={},H=[],V1={},c1=[],$1={},tt={},Z1=0,At=!0,z,st,ut=[];const it=e=>we.sanitizeText(e,L1),P1=function(e){const u=Object.keys(D);for(const i of u)if(D[i].id===e)return D[i].domId;return e},Ht=function(e,u,i,n,c,l,h={}){let U,F=e;F!==void 0&&F.trim().length!==0&&(D[F]===void 0&&(D[F]={id:F,labelType:"text",domId:Oe+F+"-"+Xt,styles:[],classes:[]}),Xt++,u!==void 0?(L1=et(),U=it(u.text.trim()),D[F].labelType=u.type,U[0]==='"'&&U[U.length-1]==='"'&&(U=U.substring(1,U.length-1)),D[F].text=U):D[F].text===void 0&&(D[F].text=e),i!==void 0&&(D[F].type=i),n!=null&&n.forEach(function(w){D[F].styles.push(w)}),c!=null&&c.forEach(function(w){D[F].classes.push(w)}),l!==void 0&&(D[F].dir=l),D[F].props===void 0?D[F].props=h:h!==void 0&&Object.assign(D[F].props,h))},qt=function(e,u,i){const l={start:e,end:u,type:void 0,text:"",labelType:"text"};J1.info("abc78 Got edge...",l);const h=i.text;if(h!==void 0&&(l.text=it(h.text.trim()),l.text[0]==='"'&&l.text[l.text.length-1]==='"'&&(l.text=l.text.substring(1,l.text.length-1)),l.labelType=h.type),i!==void 0&&(l.type=i.type,l.stroke=i.stroke,l.length=i.length),(l==null?void 0:l.length)>10&&(l.length=10),H.length<(L1.maxEdges??500))J1.info("abc78 pushing edge..."),H.push(l);else throw new Error(`Edge limit exceeded. ${H.length} edges found, but the limit is ${L1.maxEdges}.
+
+Initialize mermaid with maxEdges set to a higher number to allow more edges.
+You cannot set this config via configuration inside the diagram as it is a secure config.
+You have to call mermaid.initialize.`)},Wt=function(e,u,i){J1.info("addLink (abc78)",e,u,i);let n,c;for(n=0;n=H.length)throw new Error(`The index ${i} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${H.length-1}. (Help: Ensure that the index is within the range of existing edges.)`);i==="default"?H.defaultStyle=u:(dt.isSubstringInArray("fill",u)===-1&&u.push("fill:none"),H[i].style=u)})},Jt=function(e,u){e.split(",").forEach(function(i){V1[i]===void 0&&(V1[i]={id:i,styles:[],textStyles:[]}),u!=null&&u.forEach(function(n){if(n.match("color")){const c=n.replace("fill","bgFill").replace("color","fill");V1[i].textStyles.push(c)}V1[i].styles.push(n)})})},$t=function(e){z=e,z.match(/.*)&&(z="RL"),z.match(/.*\^/)&&(z="BT"),z.match(/.*>/)&&(z="LR"),z.match(/.*v/)&&(z="TB"),z==="TD"&&(z="TB")},rt=function(e,u){e.split(",").forEach(function(i){let n=i;D[n]!==void 0&&D[n].classes.push(u),$1[n]!==void 0&&$1[n].classes.push(u)})},Pe=function(e,u){e.split(",").forEach(function(i){u!==void 0&&(tt[st==="gen-1"?P1(i):i]=it(u))})},Ue=function(e,u,i){let n=P1(e);if(et().securityLevel!=="loose"||u===void 0)return;let c=[];if(typeof i=="string"){c=i.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let l=0;l")),c.classed("hover",!0)}).on("mouseout",function(){u.transition().duration(500).style("opacity",0),w1(this).classed("hover",!1)})};ut.push(ce);const oe=function(e="gen-1"){D={},V1={},H=[],ut=[ce],c1=[],$1={},Z1=0,tt={},At=!0,st=e,L1=et(),Ne()},le=e=>{st=e||"gen-2"},he=function(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"},fe=function(e,u,i){let n=e.text.trim(),c=i.text;e===i&&i.text.match(/\s/)&&(n=void 0);function l(X){const o1={boolean:{},number:{},string:{}},p1=[];let A1;return{nodeList:X.filter(function(k){const l1=typeof k;return k.stmt&&k.stmt==="dir"?(A1=k.value,!1):k.trim()===""?!1:l1 in o1?o1[l1].hasOwnProperty(k)?!1:o1[l1][k]=!0:p1.includes(k)?!1:p1.push(k)}),dir:A1}}let h=[];const{nodeList:U,dir:F}=l(h.concat.apply(h,u));if(h=U,st==="gen-1")for(let X=0;X2e3)return;if(pe[O1]=u,c1[u].id===e)return{result:!0,count:0};let n=0,c=1;for(;n=0){const h=Ae(e,l);if(h.result)return{result:!0,count:c+h.count};c=c+h.count}n=n+1}return{result:!1,count:c}},de=function(e){return pe[e]},Ee=function(){O1=-1,c1.length>0&&Ae("none",c1.length-1)},ke=function(){return c1},be=()=>At?(At=!1,!0):!1,Me=e=>{let u=e.trim(),i="arrow_open";switch(u[0]){case"<":i="arrow_point",u=u.slice(1);break;case"x":i="arrow_cross",u=u.slice(1);break;case"o":i="arrow_circle",u=u.slice(1);break}let n="normal";return u.includes("=")&&(n="thick"),u.includes(".")&&(n="dotted"),{type:i,stroke:n}},Ke=(e,u)=>{const i=u.length;let n=0;for(let c=0;c{const u=e.trim();let i=u.slice(0,-1),n="arrow_open";switch(u.slice(-1)){case"x":n="arrow_cross",u[0]==="x"&&(n="double_"+n,i=i.slice(1));break;case">":n="arrow_point",u[0]==="<"&&(n="double_"+n,i=i.slice(1));break;case"o":n="arrow_circle",u[0]==="o"&&(n="double_"+n,i=i.slice(1));break}let c="normal",l=i.length-1;i[0]==="="&&(c="thick"),i[0]==="~"&&(c="invisible");let h=Ke(".",i);return h&&(c="dotted",l=h),{type:n,stroke:c,length:l}},ge=(e,u)=>{const i=Ye(e);let n;if(u){if(n=Me(u),n.stroke!==i.stroke)return{type:"INVALID",stroke:"INVALID"};if(n.type==="arrow_open")n.type=i.type;else{if(n.type!==i.type)return{type:"INVALID",stroke:"INVALID"};n.type="double_"+n.type}return n.type==="double_arrow"&&(n.type="double_arrow_point"),n.length=i.length,n}return i},De=(e,u)=>{let i=!1;return e.forEach(n=>{n.nodes.indexOf(u)>=0&&(i=!0)}),i},Fe=(e,u)=>{const i=[];return e.nodes.forEach((n,c)=>{De(u,n)||i.push(e.nodes[c])}),{nodes:i}},Te={firstGraph:be},je={defaultConfig:()=>Re.flowchart,setAccTitle:Ie,getAccTitle:Le,getAccDescription:Ve,setAccDescription:ve,addVertex:Ht,lookUpDomId:P1,addLink:Wt,updateLinkInterpolate:Qt,updateLink:Zt,addClass:Jt,setDirection:$t,setClass:rt,setTooltip:Pe,getTooltip:ee,setClickEvent:se,setLink:te,bindFunctions:ue,getDirection:ie,getVertices:re,getEdges:ne,getClasses:ae,clear:oe,setGen:le,defaultStyle:he,addSubGraph:fe,getDepthFirstPos:de,indexNodes:Ee,getSubGraphs:ke,destructLink:ge,lex:Te,exists:De,makeUniq:Fe,setDiagramTitle:ye,getDiagramTitle:me},He=Object.freeze(Object.defineProperty({__proto__:null,addClass:Jt,addLink:Wt,addSingleLink:qt,addSubGraph:fe,addVertex:Ht,bindFunctions:ue,clear:oe,default:je,defaultStyle:he,destructLink:ge,firstGraph:be,getClasses:ae,getDepthFirstPos:de,getDirection:ie,getEdges:ne,getSubGraphs:ke,getTooltip:ee,getVertices:re,indexNodes:Ee,lex:Te,lookUpDomId:P1,setClass:rt,setClickEvent:se,setDirection:$t,setGen:le,setLink:te,updateLink:Zt,updateLinkInterpolate:Qt},Symbol.toStringTag,{value:"Module"}));export{He as d,je as f,Xe as p};
diff --git a/frontend-dist/assets/flowDiagram-50d868cf-CulGHM8M.js b/frontend-dist/assets/flowDiagram-50d868cf-CulGHM8M.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ab83ddc0bebdd09d74d8e215c65c77e74e918b6
--- /dev/null
+++ b/frontend-dist/assets/flowDiagram-50d868cf-CulGHM8M.js
@@ -0,0 +1,4 @@
+import{f as $,p as kt}from"./flowDb-c6c81e3f-DSM4lr0v.js";import{d as x,n as G,l as M,c as D,o as St,p as z,q as W,r as Z,t as tt,k as et}from"./index-BCNM9-Ly.js";import{G as Lt}from"./graph-CY8eBbAS.js";import{u as Et,r as _t,p as Tt,l as Nt,d as I}from"./layout-CUwpW5wl.js";import{a as T,b as rt,i as at,c as E,e as nt,d as st,f as At,g as Pt,s as Ct}from"./styles-d45a18b0-1nSSoCDY.js";import{l as It}from"./line-DdWeXrJe.js";import"./index-5325376f-CbH2QcFV.js";import"./clone-C4pHamD7.js";import"./edges-96097737-CqpaF4BI.js";import"./createText-1719965b-BZ0xZVnk.js";import"./channel-DsKT-zfZ.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";function Bt(r){if(!r.ok)throw new Error(r.status+" "+r.statusText);return r.text()}function Mt(r,e){return fetch(r,e).then(Bt)}function Dt(r){return(e,t)=>Mt(e,t).then(n=>new DOMParser().parseFromString(n,r))}var Rt=Dt("image/svg+xml"),V={normal:Ot,vee:Ut,undirected:$t};function Gt(r){V=r}function Ot(r,e,t,n){var a=r.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto"),s=a.append("path").attr("d","M 0 0 L 10 5 L 0 10 z").style("stroke-width",1).style("stroke-dasharray","1,0");T(s,t[n+"Style"]),t[n+"Class"]&&s.attr("class",t[n+"Class"])}function Ut(r,e,t,n){var a=r.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto"),s=a.append("path").attr("d","M 0 0 L 10 5 L 0 10 L 4 5 z").style("stroke-width",1).style("stroke-dasharray","1,0");T(s,t[n+"Style"]),t[n+"Class"]&&s.attr("class",t[n+"Class"])}function $t(r,e,t,n){var a=r.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto"),s=a.append("path").attr("d","M 0 5 L 10 5").style("stroke-width",1).style("stroke-dasharray","1,0");T(s,t[n+"Style"]),t[n+"Class"]&&s.attr("class",t[n+"Class"])}function Wt(r,e){var t=r;return t.node().appendChild(e.label),T(t,e.labelStyle),t}function zt(r,e){for(var t=r.append("text"),n=Vt(e.label).split(`
+`),a=0;a0}function _(r,e,t){var n=r.x,a=r.y,s=[],i=Number.POSITIVE_INFINITY,o=Number.POSITIVE_INFINITY;e.forEach(function(f){i=Math.min(i,f.x),o=Math.min(o,f.y)});for(var c=n-r.width/2-i,d=a-r.height/2-o,l=0;l1&&s.sort(function(f,g){var p=f.x-t.x,v=f.y-t.y,k=Math.sqrt(p*p+v*v),P=g.x-t.x,L=g.y-t.y,O=Math.sqrt(P*P+L*L);return kMath.abs(a)*o?(s<0&&(o=-o),c=s===0?0:o*a/s,d=o):(a<0&&(i=-i),c=i,d=a===0?0:i*s/a),{x:t+c,y:n+d}}var j={rect:se,ellipse:ie,circle:oe,diamond:le};function ne(r){j=r}function se(r,e,t){var n=r.insert("rect",":first-child").attr("rx",t.rx).attr("ry",t.ry).attr("x",-e.width/2).attr("y",-e.height/2).attr("width",e.width).attr("height",e.height);return t.intersect=function(a){return Q(t,a)},n}function ie(r,e,t){var n=e.width/2,a=e.height/2,s=r.insert("ellipse",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("rx",n).attr("ry",a);return t.intersect=function(i){return ot(t,n,a,i)},s}function oe(r,e,t){var n=Math.max(e.width,e.height)/2,a=r.insert("circle",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("r",n);return t.intersect=function(s){return re(t,n,s)},a}function le(r,e,t){var n=e.width*Math.SQRT2/2,a=e.height*Math.SQRT2/2,s=[{x:0,y:-a},{x:-n,y:0},{x:0,y:a},{x:n,y:0}],i=r.insert("polygon",":first-child").attr("points",s.map(function(o){return o.x+","+o.y}).join(" "));return t.intersect=function(o){return _(t,s,o)},i}function ce(){var r=function(e,t){ue(t);var n=B(e,"output"),a=B(n,"clusters"),s=B(n,"edgePaths"),i=H(B(n,"edgeLabels"),t),o=F(B(n,"nodes"),t,j);Nt(t),ee(o,t),te(i,t),X(s,t,V);var c=Y(a,t);Zt(c,t),pe(t)};return r.createNodes=function(e){return arguments.length?(Jt(e),r):F},r.createClusters=function(e){return arguments.length?(Yt(e),r):Y},r.createEdgeLabels=function(e){return arguments.length?(Ht(e),r):H},r.createEdgePaths=function(e){return arguments.length?(Xt(e),r):X},r.shapes=function(e){return arguments.length?(ne(e),r):j},r.arrows=function(e){return arguments.length?(Gt(e),r):V},r}var de={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},he={arrowhead:"normal",curve:G};function ue(r){r.nodes().forEach(e=>{const t=r.node(e);!Object.prototype.hasOwnProperty.call(t,"label")&&!r.children(e).length&&(t.label=e),Object.prototype.hasOwnProperty.call(t,"paddingX")&&I(t,{paddingLeft:t.paddingX,paddingRight:t.paddingX}),Object.prototype.hasOwnProperty.call(t,"paddingY")&&I(t,{paddingTop:t.paddingY,paddingBottom:t.paddingY}),Object.prototype.hasOwnProperty.call(t,"padding")&&I(t,{paddingLeft:t.padding,paddingRight:t.padding,paddingTop:t.padding,paddingBottom:t.padding}),I(t,de),["paddingLeft","paddingRight","paddingTop","paddingBottom"].forEach(n=>{t[n]=Number(t[n])}),Object.prototype.hasOwnProperty.call(t,"width")&&(t._prevWidth=t.width),Object.prototype.hasOwnProperty.call(t,"height")&&(t._prevHeight=t.height)}),r.edges().forEach(function(e){var t=r.edge(e);Object.prototype.hasOwnProperty.call(t,"label")||(t.label=""),I(t,he)})}function pe(r){r.nodes().forEach(e=>{var t=r.node(e);Object.prototype.hasOwnProperty.call(t,"_prevWidth")?t.width=t._prevWidth:delete t.width,Object.prototype.hasOwnProperty.call(t,"_prevHeight")?t.height=t._prevHeight:delete t.height,delete t._prevWidth,delete t._prevHeight})}function B(r,e){var t=r.select("g."+e);return t.empty()&&(t=r.append("g").attr("class",e)),t}function lt(r,e,t){const n=e.width,a=e.height,s=(n+a)*.9,i=[{x:s/2,y:0},{x:s,y:-s/2},{x:s/2,y:-s},{x:0,y:-s/2}],o=N(r,s,s,i);return t.intersect=function(c){return _(t,i,c)},o}function ct(r,e,t){const a=e.height,s=a/4,i=e.width+2*s,o=[{x:s,y:0},{x:i-s,y:0},{x:i,y:-a/2},{x:i-s,y:-a},{x:s,y:-a},{x:0,y:-a/2}],c=N(r,i,a,o);return t.intersect=function(d){return _(t,o,d)},c}function dt(r,e,t){const n=e.width,a=e.height,s=[{x:-a/2,y:0},{x:n,y:0},{x:n,y:-a},{x:-a/2,y:-a},{x:0,y:-a/2}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function ht(r,e,t){const n=e.width,a=e.height,s=[{x:-2*a/6,y:0},{x:n-a/6,y:0},{x:n+2*a/6,y:-a},{x:a/6,y:-a}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function ut(r,e,t){const n=e.width,a=e.height,s=[{x:2*a/6,y:0},{x:n+a/6,y:0},{x:n-2*a/6,y:-a},{x:-a/6,y:-a}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function pt(r,e,t){const n=e.width,a=e.height,s=[{x:-2*a/6,y:0},{x:n+2*a/6,y:0},{x:n-a/6,y:-a},{x:a/6,y:-a}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function ft(r,e,t){const n=e.width,a=e.height,s=[{x:a/6,y:0},{x:n-a/6,y:0},{x:n+2*a/6,y:-a},{x:-2*a/6,y:-a}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function yt(r,e,t){const n=e.width,a=e.height,s=[{x:0,y:0},{x:n+a/2,y:0},{x:n,y:-a/2},{x:n+a/2,y:-a},{x:0,y:-a}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function vt(r,e,t){const n=e.height,a=e.width+n/4,s=r.insert("rect",":first-child").attr("rx",n/2).attr("ry",n/2).attr("x",-a/2).attr("y",-n/2).attr("width",a).attr("height",n);return t.intersect=function(i){return Q(t,i)},s}function gt(r,e,t){const n=e.width,a=e.height,s=[{x:0,y:0},{x:n,y:0},{x:n,y:-a},{x:0,y:-a},{x:0,y:0},{x:-8,y:0},{x:n+8,y:0},{x:n+8,y:-a},{x:-8,y:-a},{x:-8,y:0}],i=N(r,n,a,s);return t.intersect=function(o){return _(t,s,o)},i}function wt(r,e,t){const n=e.width,a=n/2,s=a/(2.5+n/50),i=e.height+s,o="M 0,"+s+" a "+a+","+s+" 0,0,0 "+n+" 0 a "+a+","+s+" 0,0,0 "+-n+" 0 l 0,"+i+" a "+a+","+s+" 0,0,0 "+n+" 0 l 0,"+-i,c=r.attr("label-offset-y",s).insert("path",":first-child").attr("d",o).attr("transform","translate("+-n/2+","+-(i/2+s)+")");return t.intersect=function(d){const l=Q(t,d),y=l.x-t.x;if(a!=0&&(Math.abs(y)t.height/2-s)){let h=s*s*(1-y*y/(a*a));h!=0&&(h=Math.sqrt(h)),h=s-h,d.y-t.y>0&&(h=-h),l.y+=h}return l},c}function fe(r){r.shapes().question=lt,r.shapes().hexagon=ct,r.shapes().stadium=vt,r.shapes().subroutine=gt,r.shapes().cylinder=wt,r.shapes().rect_left_inv_arrow=dt,r.shapes().lean_right=ht,r.shapes().lean_left=ut,r.shapes().trapezoid=pt,r.shapes().inv_trapezoid=ft,r.shapes().rect_right_inv_arrow=yt}function ye(r){r({question:lt}),r({hexagon:ct}),r({stadium:vt}),r({subroutine:gt}),r({cylinder:wt}),r({rect_left_inv_arrow:dt}),r({lean_right:ht}),r({lean_left:ut}),r({trapezoid:pt}),r({inv_trapezoid:ft}),r({rect_right_inv_arrow:yt})}function N(r,e,t,n){return r.insert("polygon",":first-child").attr("points",n.map(function(a){return a.x+","+a.y}).join(" ")).attr("transform","translate("+-e/2+","+t/2+")")}const ve={addToRender:fe,addToRenderV2:ye},mt={},ge=function(r){const e=Object.keys(r);for(const t of e)mt[t]=r[t]},xt=async function(r,e,t,n,a,s){const i=n?n.select(`[id="${t}"]`):x(`[id="${t}"]`),o=a||document,c=Object.keys(r);for(const d of c){const l=r[d];let y="default";l.classes.length>0&&(y=l.classes.join(" "));const h=z(l.styles);let u=l.text!==void 0?l.text:l.id,f;if(Z(D().flowchart.htmlLabels)){const v={label:await tt(u.replace(/fa[blrs]?:fa-[\w-]+/g,k=>``),D())};f=rt(i,v).node(),f.parentNode.removeChild(f)}else{const v=o.createElementNS("http://www.w3.org/2000/svg","text");v.setAttribute("style",h.labelStyle.replace("color:","fill:"));const k=u.split(et.lineBreakRegex);for(const P of k){const L=o.createElementNS("http://www.w3.org/2000/svg","tspan");L.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),L.setAttribute("dy","1em"),L.setAttribute("x","1"),L.textContent=P,v.appendChild(L)}f=v}let g=0,p="";switch(l.type){case"round":g=5,p="rect";break;case"square":p="rect";break;case"diamond":p="question";break;case"hexagon":p="hexagon";break;case"odd":p="rect_left_inv_arrow";break;case"lean_right":p="lean_right";break;case"lean_left":p="lean_left";break;case"trapezoid":p="trapezoid";break;case"inv_trapezoid":p="inv_trapezoid";break;case"odd_right":p="rect_left_inv_arrow";break;case"circle":p="circle";break;case"ellipse":p="ellipse";break;case"stadium":p="stadium";break;case"subroutine":p="subroutine";break;case"cylinder":p="cylinder";break;case"group":p="rect";break;default:p="rect"}M.warn("Adding node",l.id,l.domId),e.setNode(s.db.lookUpDomId(l.id),{labelType:"svg",labelStyle:h.labelStyle,shape:p,label:f,rx:g,ry:g,class:y,style:h.style,id:s.db.lookUpDomId(l.id)})}},bt=async function(r,e,t){let n=0,a,s;if(r.defaultStyle!==void 0){const i=z(r.defaultStyle);a=i.style,s=i.labelStyle}for(const i of r){n++;const o="L-"+i.start+"-"+i.end,c="LS-"+i.start,d="LE-"+i.end,l={};i.type==="arrow_open"?l.arrowhead="none":l.arrowhead="normal";let y="",h="";if(i.style!==void 0){const u=z(i.style);y=u.style,h=u.labelStyle}else switch(i.stroke){case"normal":y="fill:none",a!==void 0&&(y=a),s!==void 0&&(h=s);break;case"dotted":y="fill:none;stroke-width:2px;stroke-dasharray:3;";break;case"thick":y=" stroke-width: 3.5px;fill:none";break}l.style=y,l.labelStyle=h,i.interpolate!==void 0?l.curve=W(i.interpolate,G):r.defaultInterpolate!==void 0?l.curve=W(r.defaultInterpolate,G):l.curve=W(mt.curve,G),i.text===void 0?i.style!==void 0&&(l.arrowheadStyle="fill: #333"):(l.arrowheadStyle="fill: #333",l.labelpos="c",Z(D().flowchart.htmlLabels)?(l.labelType="html",l.label=`${await tt(i.text.replace(/fa[blrs]?:fa-[\w-]+/g,u=>``),D())}`):(l.labelType="text",l.label=i.text.replace(et.lineBreakRegex,`
+`),i.style===void 0&&(l.style=l.style||"stroke: #333; stroke-width: 1.5px;fill:none"),l.labelStyle=l.labelStyle.replace("color:","fill:"))),l.id=o,l.class=c+" "+d,l.minlen=i.length||1,e.setEdge(t.db.lookUpDomId(i.start),t.db.lookUpDomId(i.end),l,n)}},we=function(r,e){return M.info("Extracting classes"),e.db.getClasses()},me=async function(r,e,t,n){M.info("Drawing flowchart");const{securityLevel:a,flowchart:s}=D();let i;a==="sandbox"&&(i=x("#i"+e));const o=a==="sandbox"?x(i.nodes()[0].contentDocument.body):x("body"),c=a==="sandbox"?i.nodes()[0].contentDocument:document;let d=n.db.getDirection();d===void 0&&(d="TD");const l=s.nodeSpacing||50,y=s.rankSpacing||50,h=new Lt({multigraph:!0,compound:!0}).setGraph({rankdir:d,nodesep:l,ranksep:y,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}});let u;const f=n.db.getSubGraphs();for(let w=f.length-1;w>=0;w--)u=f[w],n.db.addVertex(u.id,u.title,"group",void 0,u.classes);const g=n.db.getVertices();M.warn("Get vertices",g);const p=n.db.getEdges();let v=0;for(v=f.length-1;v>=0;v--){u=f[v],Ct("cluster").append("text");for(let w=0;w{r.flowchart||(r.flowchart={}),r.flowchart.arrowMarkerAbsolute=r.arrowMarkerAbsolute,xe.setConf(r.flowchart),$.clear(),$.setGen("gen-1")}};export{Me as diagram};
diff --git a/frontend-dist/assets/flowDiagram-v2-4f6560a1-CjX8NA5h.js b/frontend-dist/assets/flowDiagram-v2-4f6560a1-CjX8NA5h.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d71c024a254c6b33bffdf77b8ed7ff2b27c1d02
--- /dev/null
+++ b/frontend-dist/assets/flowDiagram-v2-4f6560a1-CjX8NA5h.js
@@ -0,0 +1 @@
+import{f as o,p as e}from"./flowDb-c6c81e3f-DSM4lr0v.js";import{f as a,g as t}from"./styles-d45a18b0-1nSSoCDY.js";import{u as i}from"./index-BCNM9-Ly.js";import"./graph-CY8eBbAS.js";import"./layout-CUwpW5wl.js";import"./index-5325376f-CbH2QcFV.js";import"./clone-C4pHamD7.js";import"./edges-96097737-CqpaF4BI.js";import"./createText-1719965b-BZ0xZVnk.js";import"./line-DdWeXrJe.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";import"./channel-DsKT-zfZ.js";const M={parser:e,db:o,renderer:t,styles:a,init:r=>{r.flowchart||(r.flowchart={}),r.flowchart.arrowMarkerAbsolute=r.arrowMarkerAbsolute,i({flowchart:{arrowMarkerAbsolute:r.arrowMarkerAbsolute}}),t.setConf(r.flowchart),o.clear(),o.setGen("gen-2")}};export{M as diagram};
diff --git a/frontend-dist/assets/flowchart-elk-definition-6af322e1-Bo-P5b8z.js b/frontend-dist/assets/flowchart-elk-definition-6af322e1-Bo-P5b8z.js
new file mode 100644
index 0000000000000000000000000000000000000000..8acc8e2e8d9f78101bcf0a118b477d5d3ba1b795
--- /dev/null
+++ b/frontend-dist/assets/flowchart-elk-definition-6af322e1-Bo-P5b8z.js
@@ -0,0 +1,139 @@
+import{p as xNe,d as FNe}from"./flowDb-c6c81e3f-DSM4lr0v.js";import{Q as BNe,P as Nse,d as IO,l as Ba,_ as xU,o as RNe,p as E0n,q as j0n,n as $U,k as KNe}from"./index-BCNM9-Ly.js";import{i as _Ne,a as HNe,l as qNe,b as UNe,k as GNe,m as zNe}from"./edges-96097737-CqpaF4BI.js";import{l as XNe}from"./line-DdWeXrJe.js";import"./createText-1719965b-BZ0xZVnk.js";import"./array-BKyUJesY.js";import"./path-CbwjOpE9.js";function NU(ct){throw new Error('Could not dynamically require "'+ct+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var Bse={exports:{}};(function(ct,_t){(function(Xt){ct.exports=Xt()})(function(){return function(){function Xt(gt,Sr,Di){function y(Ht,Jt){if(!Sr[Ht]){if(!gt[Ht]){var ze=typeof NU=="function"&&NU;if(!Jt&&ze)return ze(Ht,!0);if(Wt)return Wt(Ht,!0);var Yi=new Error("Cannot find module '"+Ht+"'");throw Yi.code="MODULE_NOT_FOUND",Yi}var Ri=Sr[Ht]={exports:{}};gt[Ht][0].call(Ri.exports,function(En){var hu=gt[Ht][1][En];return y(hu||En)},Ri,Ri.exports,Xt,gt,Sr,Di)}return Sr[Ht].exports}for(var Wt=typeof NU=="function"&&NU,Bu=0;Bu0&&arguments[0]!==void 0?arguments[0]:{},Yi=ze.defaultLayoutOptions,Ri=Yi===void 0?{}:Yi,En=ze.algorithms,hu=En===void 0?["layered","stress","mrtree","radial","force","disco","sporeOverlap","sporeCompaction","rectpacking"]:En,Qc=ze.workerFactory,Ru=ze.workerUrl;if(y(this,Ht),this.defaultLayoutOptions=Ri,this.initialized=!1,typeof Ru>"u"&&typeof Qc>"u")throw new Error("Cannot construct an ELK without both 'workerUrl' and 'workerFactory'.");var Pr=Qc;typeof Ru<"u"&&typeof Qc>"u"&&(Pr=function(N1){return new Worker(N1)});var Cf=Pr(Ru);if(typeof Cf.postMessage!="function")throw new TypeError("Created worker does not provide the required 'postMessage' function.");this.worker=new Bu(Cf),this.worker.postMessage({cmd:"register",algorithms:hu}).then(function(L1){return Jt.initialized=!0}).catch(console.err)}return Di(Ht,[{key:"layout",value:function(ze){var Yi=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},Ri=Yi.layoutOptions,En=Ri===void 0?this.defaultLayoutOptions:Ri,hu=Yi.logging,Qc=hu===void 0?!1:hu,Ru=Yi.measureExecutionTime,Pr=Ru===void 0?!1:Ru;return ze?this.worker.postMessage({cmd:"layout",graph:ze,layoutOptions:En,options:{logging:Qc,measureExecutionTime:Pr}}):Promise.reject(new Error("Missing mandatory parameter 'graph'."))}},{key:"knownLayoutAlgorithms",value:function(){return this.worker.postMessage({cmd:"algorithms"})}},{key:"knownLayoutOptions",value:function(){return this.worker.postMessage({cmd:"options"})}},{key:"knownLayoutCategories",value:function(){return this.worker.postMessage({cmd:"categories"})}},{key:"terminateWorker",value:function(){this.worker&&this.worker.terminate()}}]),Ht}();Sr.default=Wt;var Bu=function(){function Ht(Jt){var ze=this;if(y(this,Ht),Jt===void 0)throw new Error("Missing mandatory parameter 'worker'.");this.resolvers={},this.worker=Jt,this.worker.onmessage=function(Yi){setTimeout(function(){ze.receive(ze,Yi)},0)}}return Di(Ht,[{key:"postMessage",value:function(ze){var Yi=this.id||0;this.id=Yi+1,ze.id=Yi;var Ri=this;return new Promise(function(En,hu){Ri.resolvers[Yi]=function(Qc,Ru){Qc?(Ri.convertGwtStyleError(Qc),hu(Qc)):En(Ru)},Ri.worker.postMessage(ze)})}},{key:"receive",value:function(ze,Yi){var Ri=Yi.data,En=ze.resolvers[Ri.id];En&&(delete ze.resolvers[Ri.id],Ri.error?En(Ri.error):En(null,Ri.data))}},{key:"terminate",value:function(){this.worker&&this.worker.terminate()}},{key:"convertGwtStyleError",value:function(ze){if(ze){var Yi=ze.__java$exception;Yi&&(Yi.cause&&Yi.cause.backingJsObject&&(ze.cause=Yi.cause.backingJsObject,this.convertGwtStyleError(ze.cause)),delete ze.__java$exception)}}}]),Ht}()},{}],2:[function(Xt,gt,Sr){(function(Di){(function(){var y;typeof window<"u"?y=window:typeof Di<"u"?y=Di:typeof self<"u"&&(y=self);var Wt;function Bu(){}function Ht(){}function Jt(){}function ze(){}function Yi(){}function Ri(){}function En(){}function hu(){}function Qc(){}function Ru(){}function Pr(){}function Cf(){}function L1(){}function N1(){}function og(){}function V3(){}function $1(){}function ul(){}function C0n(){}function M0n(){}function J2(){}function F(){}function T0n(){}function mE(){}function A0n(){}function S0n(){}function P0n(){}function I0n(){}function O0n(){}function FU(){}function D0n(){}function L0n(){}function N0n(){}function OO(){}function $0n(){}function x0n(){}function F0n(){}function DO(){}function B0n(){}function R0n(){}function BU(){}function K0n(){}function _0n(){}function yu(){}function ju(){}function Q2(){}function Y2(){}function H0n(){}function q0n(){}function U0n(){}function G0n(){}function RU(){}function Eu(){}function Z2(){}function np(){}function z0n(){}function X0n(){}function LO(){}function V0n(){}function W0n(){}function J0n(){}function Q0n(){}function Y0n(){}function Z0n(){}function nbn(){}function ebn(){}function tbn(){}function ibn(){}function rbn(){}function cbn(){}function ubn(){}function obn(){}function sbn(){}function fbn(){}function hbn(){}function lbn(){}function abn(){}function dbn(){}function bbn(){}function wbn(){}function gbn(){}function pbn(){}function mbn(){}function vbn(){}function kbn(){}function ybn(){}function jbn(){}function Ebn(){}function Cbn(){}function Mbn(){}function Tbn(){}function KU(){}function Abn(){}function Sbn(){}function Pbn(){}function Ibn(){}function NO(){}function $O(){}function vE(){}function Obn(){}function Dbn(){}function xO(){}function Lbn(){}function Nbn(){}function $bn(){}function kE(){}function xbn(){}function Fbn(){}function Bbn(){}function Rbn(){}function Kbn(){}function _bn(){}function Hbn(){}function qbn(){}function Ubn(){}function _U(){}function Gbn(){}function zbn(){}function HU(){}function Xbn(){}function Vbn(){}function Wbn(){}function Jbn(){}function Qbn(){}function Ybn(){}function Zbn(){}function nwn(){}function ewn(){}function twn(){}function iwn(){}function rwn(){}function cwn(){}function FO(){}function uwn(){}function own(){}function swn(){}function fwn(){}function hwn(){}function lwn(){}function awn(){}function dwn(){}function bwn(){}function qU(){}function UU(){}function wwn(){}function gwn(){}function pwn(){}function mwn(){}function vwn(){}function kwn(){}function ywn(){}function jwn(){}function Ewn(){}function Cwn(){}function Mwn(){}function Twn(){}function Awn(){}function Swn(){}function Pwn(){}function Iwn(){}function Own(){}function Dwn(){}function Lwn(){}function Nwn(){}function $wn(){}function xwn(){}function Fwn(){}function Bwn(){}function Rwn(){}function Kwn(){}function _wn(){}function Hwn(){}function qwn(){}function Uwn(){}function Gwn(){}function zwn(){}function Xwn(){}function Vwn(){}function Wwn(){}function Jwn(){}function Qwn(){}function Ywn(){}function Zwn(){}function ngn(){}function egn(){}function tgn(){}function ign(){}function rgn(){}function cgn(){}function ugn(){}function ogn(){}function sgn(){}function fgn(){}function hgn(){}function lgn(){}function agn(){}function dgn(){}function bgn(){}function wgn(){}function ggn(){}function pgn(){}function mgn(){}function vgn(){}function kgn(){}function ygn(){}function jgn(){}function Egn(){}function Cgn(){}function Mgn(){}function Tgn(){}function Agn(){}function Sgn(){}function Pgn(){}function Ign(){}function Ogn(){}function Dgn(){}function Lgn(){}function Ngn(){}function $gn(){}function xgn(){}function Fgn(){}function Bgn(){}function Rgn(){}function Kgn(){}function _gn(){}function Hgn(){}function qgn(){}function Ugn(){}function Ggn(){}function zgn(){}function Xgn(){}function Vgn(){}function Wgn(){}function Jgn(){}function Qgn(){}function Ygn(){}function Zgn(){}function n2n(){}function e2n(){}function t2n(){}function i2n(){}function r2n(){}function c2n(){}function u2n(){}function GU(){}function o2n(){}function s2n(){}function f2n(){}function h2n(){}function l2n(){}function a2n(){}function d2n(){}function b2n(){}function w2n(){}function g2n(){}function p2n(){}function m2n(){}function v2n(){}function k2n(){}function y2n(){}function j2n(){}function E2n(){}function C2n(){}function M2n(){}function T2n(){}function A2n(){}function S2n(){}function P2n(){}function I2n(){}function O2n(){}function D2n(){}function L2n(){}function N2n(){}function $2n(){}function x2n(){}function F2n(){}function B2n(){}function R2n(){}function K2n(){}function _2n(){}function H2n(){}function q2n(){}function U2n(){}function G2n(){}function z2n(){}function X2n(){}function V2n(){}function W2n(){}function J2n(){}function Q2n(){}function Y2n(){}function Z2n(){}function npn(){}function epn(){}function tpn(){}function ipn(){}function rpn(){}function cpn(){}function upn(){}function opn(){}function spn(){}function fpn(){}function hpn(){}function lpn(){}function apn(){}function dpn(){}function bpn(){}function wpn(){}function gpn(){}function ppn(){}function mpn(){}function vpn(){}function kpn(){}function ypn(){}function jpn(){}function Epn(){}function Cpn(){}function Mpn(){}function zU(){}function Tpn(){}function Apn(){}function Spn(){}function Ppn(){}function Ipn(){}function Opn(){}function Dpn(){}function Lpn(){}function Npn(){}function $pn(){}function XU(){}function xpn(){}function Fpn(){}function Bpn(){}function Rpn(){}function Kpn(){}function _pn(){}function VU(){}function WU(){}function Hpn(){}function JU(){}function QU(){}function qpn(){}function Upn(){}function Gpn(){}function zpn(){}function Xpn(){}function Vpn(){}function Wpn(){}function Jpn(){}function Qpn(){}function Ypn(){}function Zpn(){}function YU(){}function n3n(){}function e3n(){}function t3n(){}function i3n(){}function r3n(){}function c3n(){}function u3n(){}function o3n(){}function s3n(){}function f3n(){}function h3n(){}function l3n(){}function a3n(){}function d3n(){}function b3n(){}function w3n(){}function g3n(){}function p3n(){}function m3n(){}function v3n(){}function k3n(){}function y3n(){}function j3n(){}function E3n(){}function C3n(){}function M3n(){}function T3n(){}function A3n(){}function S3n(){}function P3n(){}function I3n(){}function O3n(){}function D3n(){}function L3n(){}function N3n(){}function $3n(){}function x3n(){}function F3n(){}function B3n(){}function R3n(){}function K3n(){}function _3n(){}function H3n(){}function q3n(){}function U3n(){}function G3n(){}function z3n(){}function X3n(){}function V3n(){}function W3n(){}function J3n(){}function Q3n(){}function Y3n(){}function Z3n(){}function n4n(){}function e4n(){}function t4n(){}function i4n(){}function r4n(){}function c4n(){}function u4n(){}function o4n(){}function s4n(){}function f4n(){}function h4n(){}function l4n(){}function a4n(){}function d4n(){}function b4n(){}function w4n(){}function g4n(){}function p4n(){}function m4n(){}function v4n(){}function k4n(){}function y4n(){}function j4n(){}function E4n(){}function C4n(){}function M4n(){}function T4n(){}function A4n(){}function S4n(){}function P4n(){}function I4n(){}function O4n(){}function _se(){}function D4n(){}function L4n(){}function N4n(){}function $4n(){}function x4n(){}function F4n(){}function B4n(){}function R4n(){}function K4n(){}function _4n(){}function H4n(){}function q4n(){}function U4n(){}function G4n(){}function z4n(){}function X4n(){}function V4n(){}function W4n(){}function J4n(){}function Q4n(){}function Y4n(){}function Z4n(){}function nmn(){}function emn(){}function tmn(){}function imn(){}function rmn(){}function BO(){}function RO(){}function cmn(){}function KO(){}function umn(){}function omn(){}function smn(){}function fmn(){}function hmn(){}function lmn(){}function amn(){}function dmn(){}function bmn(){}function wmn(){}function ZU(){}function gmn(){}function pmn(){}function mmn(){}function Hse(){}function vmn(){}function kmn(){}function ymn(){}function jmn(){}function Emn(){}function Cmn(){}function Mmn(){}function Ra(){}function Tmn(){}function ep(){}function nG(){}function Amn(){}function Smn(){}function Pmn(){}function Imn(){}function Omn(){}function Dmn(){}function Lmn(){}function Nmn(){}function $mn(){}function xmn(){}function Fmn(){}function Bmn(){}function Rmn(){}function Kmn(){}function _mn(){}function Hmn(){}function qmn(){}function Umn(){}function Gmn(){}function hn(){}function zmn(){}function Xmn(){}function Vmn(){}function Wmn(){}function Jmn(){}function Qmn(){}function Ymn(){}function Zmn(){}function nvn(){}function evn(){}function tvn(){}function ivn(){}function rvn(){}function _O(){}function cvn(){}function uvn(){}function ovn(){}function yE(){}function svn(){}function HO(){}function jE(){}function fvn(){}function eG(){}function hvn(){}function lvn(){}function avn(){}function dvn(){}function bvn(){}function wvn(){}function EE(){}function gvn(){}function pvn(){}function CE(){}function mvn(){}function ME(){}function vvn(){}function tG(){}function kvn(){}function qO(){}function iG(){}function yvn(){}function jvn(){}function Evn(){}function Cvn(){}function qse(){}function Mvn(){}function Tvn(){}function Avn(){}function Svn(){}function Pvn(){}function Ivn(){}function Ovn(){}function Dvn(){}function Lvn(){}function Nvn(){}function W3(){}function UO(){}function $vn(){}function xvn(){}function Fvn(){}function Bvn(){}function Rvn(){}function Kvn(){}function _vn(){}function Hvn(){}function qvn(){}function Uvn(){}function Gvn(){}function zvn(){}function Xvn(){}function Vvn(){}function Wvn(){}function Jvn(){}function Qvn(){}function Yvn(){}function Zvn(){}function n6n(){}function e6n(){}function t6n(){}function i6n(){}function r6n(){}function c6n(){}function u6n(){}function o6n(){}function s6n(){}function f6n(){}function h6n(){}function l6n(){}function a6n(){}function d6n(){}function b6n(){}function w6n(){}function g6n(){}function p6n(){}function m6n(){}function v6n(){}function k6n(){}function y6n(){}function j6n(){}function E6n(){}function C6n(){}function M6n(){}function T6n(){}function A6n(){}function S6n(){}function P6n(){}function I6n(){}function O6n(){}function D6n(){}function L6n(){}function N6n(){}function $6n(){}function x6n(){}function F6n(){}function B6n(){}function R6n(){}function K6n(){}function _6n(){}function H6n(){}function q6n(){}function U6n(){}function G6n(){}function z6n(){}function X6n(){}function V6n(){}function W6n(){}function J6n(){}function Q6n(){}function Y6n(){}function Z6n(){}function n5n(){}function e5n(){}function t5n(){}function i5n(){}function r5n(){}function c5n(){}function u5n(){}function o5n(){}function s5n(){}function f5n(){}function h5n(){}function l5n(){}function a5n(){}function d5n(){}function b5n(){}function w5n(){}function g5n(){}function p5n(){}function m5n(){}function v5n(){}function k5n(){}function y5n(){}function j5n(){}function E5n(){}function C5n(){}function M5n(){}function T5n(){}function A5n(){}function rG(){}function S5n(){}function P5n(){}function GO(){n6()}function I5n(){u7()}function O5n(){aA()}function D5n(){Q$()}function L5n(){M5()}function N5n(){ann()}function $5n(){qs()}function x5n(){jZ()}function F5n(){zk()}function B5n(){o7()}function R5n(){$7()}function K5n(){aCn()}function _5n(){Hp()}function H5n(){KLn()}function q5n(){yQ()}function U5n(){SOn()}function G5n(){jQ()}function z5n(){pNn()}function X5n(){AOn()}function V5n(){cm()}function W5n(){nxn()}function J5n(){Z$n()}function Q5n(){EDn()}function Y5n(){exn()}function Z5n(){ca()}function n8n(){ZE()}function e8n(){ltn()}function t8n(){cn()}function i8n(){txn()}function r8n(){Pxn()}function c8n(){POn()}function u8n(){nKn()}function o8n(){IOn()}function s8n(){bUn()}function f8n(){qnn()}function h8n(){kl()}function l8n(){wBn()}function a8n(){lc()}function d8n(){ROn()}function b8n(){_p()}function w8n(){Men()}function g8n(){ua()}function p8n(){Ten()}function m8n(){Bf()}function v8n(){Qk()}function k8n(){EF()}function y8n(){Dx()}function cf(){wSn()}function j8n(){YM()}function E8n(){mA()}function cG(){qe()}function C8n(){NT()}function M8n(){YY()}function uG(){D$()}function oG(){KA()}function T8n(){Fen()}function sG(n){Jn(n)}function A8n(n){this.a=n}function TE(n){this.a=n}function S8n(n){this.a=n}function P8n(n){this.a=n}function I8n(n){this.a=n}function O8n(n){this.a=n}function D8n(n){this.a=n}function L8n(n){this.a=n}function fG(n){this.a=n}function hG(n){this.a=n}function N8n(n){this.a=n}function $8n(n){this.a=n}function zO(n){this.a=n}function x8n(n){this.a=n}function F8n(n){this.a=n}function XO(n){this.a=n}function VO(n){this.a=n}function B8n(n){this.a=n}function WO(n){this.a=n}function R8n(n){this.a=n}function K8n(n){this.a=n}function _8n(n){this.a=n}function lG(n){this.b=n}function H8n(n){this.c=n}function q8n(n){this.a=n}function U8n(n){this.a=n}function G8n(n){this.a=n}function z8n(n){this.a=n}function X8n(n){this.a=n}function V8n(n){this.a=n}function W8n(n){this.a=n}function J8n(n){this.a=n}function Q8n(n){this.a=n}function Y8n(n){this.a=n}function Z8n(n){this.a=n}function n9n(n){this.a=n}function e9n(n){this.a=n}function aG(n){this.a=n}function dG(n){this.a=n}function AE(n){this.a=n}function z9(n){this.a=n}function Ka(){this.a=[]}function t9n(n,e){n.a=e}function Use(n,e){n.a=e}function Gse(n,e){n.b=e}function zse(n,e){n.b=e}function Xse(n,e){n.b=e}function bG(n,e){n.j=e}function Vse(n,e){n.g=e}function Wse(n,e){n.i=e}function Jse(n,e){n.c=e}function Qse(n,e){n.c=e}function Yse(n,e){n.d=e}function Zse(n,e){n.d=e}function _a(n,e){n.k=e}function nfe(n,e){n.c=e}function wG(n,e){n.c=e}function gG(n,e){n.a=e}function efe(n,e){n.a=e}function tfe(n,e){n.f=e}function ife(n,e){n.a=e}function rfe(n,e){n.b=e}function JO(n,e){n.d=e}function SE(n,e){n.i=e}function pG(n,e){n.o=e}function cfe(n,e){n.r=e}function ufe(n,e){n.a=e}function ofe(n,e){n.b=e}function i9n(n,e){n.e=e}function sfe(n,e){n.f=e}function mG(n,e){n.g=e}function ffe(n,e){n.e=e}function hfe(n,e){n.f=e}function lfe(n,e){n.f=e}function QO(n,e){n.a=e}function YO(n,e){n.b=e}function afe(n,e){n.n=e}function dfe(n,e){n.a=e}function bfe(n,e){n.c=e}function wfe(n,e){n.c=e}function gfe(n,e){n.c=e}function pfe(n,e){n.a=e}function mfe(n,e){n.a=e}function vfe(n,e){n.d=e}function kfe(n,e){n.d=e}function yfe(n,e){n.e=e}function jfe(n,e){n.e=e}function Efe(n,e){n.g=e}function Cfe(n,e){n.f=e}function Mfe(n,e){n.j=e}function Tfe(n,e){n.a=e}function Afe(n,e){n.a=e}function Sfe(n,e){n.b=e}function r9n(n){n.b=n.a}function c9n(n){n.c=n.d.d}function vG(n){this.a=n}function kG(n){this.a=n}function yG(n){this.a=n}function Ha(n){this.a=n}function qa(n){this.a=n}function X9(n){this.a=n}function u9n(n){this.a=n}function jG(n){this.a=n}function V9(n){this.a=n}function PE(n){this.a=n}function ol(n){this.a=n}function Sb(n){this.a=n}function o9n(n){this.a=n}function s9n(n){this.a=n}function ZO(n){this.b=n}function J3(n){this.b=n}function Q3(n){this.b=n}function nD(n){this.a=n}function f9n(n){this.a=n}function eD(n){this.c=n}function C(n){this.c=n}function h9n(n){this.c=n}function Xv(n){this.d=n}function EG(n){this.a=n}function Te(n){this.a=n}function l9n(n){this.a=n}function CG(n){this.a=n}function MG(n){this.a=n}function TG(n){this.a=n}function AG(n){this.a=n}function SG(n){this.a=n}function PG(n){this.a=n}function Y3(n){this.a=n}function a9n(n){this.a=n}function d9n(n){this.a=n}function Z3(n){this.a=n}function b9n(n){this.a=n}function w9n(n){this.a=n}function g9n(n){this.a=n}function p9n(n){this.a=n}function m9n(n){this.a=n}function v9n(n){this.a=n}function k9n(n){this.a=n}function y9n(n){this.a=n}function j9n(n){this.a=n}function E9n(n){this.a=n}function C9n(n){this.a=n}function M9n(n){this.a=n}function T9n(n){this.a=n}function A9n(n){this.a=n}function S9n(n){this.a=n}function Vv(n){this.a=n}function P9n(n){this.a=n}function I9n(n){this.a=n}function O9n(n){this.a=n}function D9n(n){this.a=n}function IE(n){this.a=n}function L9n(n){this.a=n}function N9n(n){this.a=n}function n4(n){this.a=n}function IG(n){this.a=n}function $9n(n){this.a=n}function x9n(n){this.a=n}function F9n(n){this.a=n}function B9n(n){this.a=n}function R9n(n){this.a=n}function K9n(n){this.a=n}function OG(n){this.a=n}function DG(n){this.a=n}function LG(n){this.a=n}function Wv(n){this.a=n}function OE(n){this.e=n}function e4(n){this.a=n}function _9n(n){this.a=n}function tp(n){this.a=n}function NG(n){this.a=n}function H9n(n){this.a=n}function q9n(n){this.a=n}function U9n(n){this.a=n}function G9n(n){this.a=n}function z9n(n){this.a=n}function X9n(n){this.a=n}function V9n(n){this.a=n}function W9n(n){this.a=n}function J9n(n){this.a=n}function Q9n(n){this.a=n}function Y9n(n){this.a=n}function $G(n){this.a=n}function Z9n(n){this.a=n}function n7n(n){this.a=n}function e7n(n){this.a=n}function t7n(n){this.a=n}function i7n(n){this.a=n}function r7n(n){this.a=n}function c7n(n){this.a=n}function u7n(n){this.a=n}function o7n(n){this.a=n}function s7n(n){this.a=n}function f7n(n){this.a=n}function h7n(n){this.a=n}function l7n(n){this.a=n}function a7n(n){this.a=n}function d7n(n){this.a=n}function b7n(n){this.a=n}function w7n(n){this.a=n}function g7n(n){this.a=n}function p7n(n){this.a=n}function m7n(n){this.a=n}function v7n(n){this.a=n}function k7n(n){this.a=n}function y7n(n){this.a=n}function j7n(n){this.a=n}function E7n(n){this.a=n}function C7n(n){this.a=n}function M7n(n){this.a=n}function T7n(n){this.a=n}function A7n(n){this.a=n}function S7n(n){this.a=n}function P7n(n){this.a=n}function I7n(n){this.a=n}function O7n(n){this.a=n}function D7n(n){this.a=n}function L7n(n){this.a=n}function N7n(n){this.a=n}function $7n(n){this.a=n}function x7n(n){this.a=n}function F7n(n){this.c=n}function B7n(n){this.b=n}function R7n(n){this.a=n}function K7n(n){this.a=n}function _7n(n){this.a=n}function H7n(n){this.a=n}function q7n(n){this.a=n}function U7n(n){this.a=n}function G7n(n){this.a=n}function z7n(n){this.a=n}function X7n(n){this.a=n}function V7n(n){this.a=n}function W7n(n){this.a=n}function J7n(n){this.a=n}function Q7n(n){this.a=n}function Y7n(n){this.a=n}function Z7n(n){this.a=n}function nkn(n){this.a=n}function ekn(n){this.a=n}function tkn(n){this.a=n}function ikn(n){this.a=n}function rkn(n){this.a=n}function ckn(n){this.a=n}function ukn(n){this.a=n}function okn(n){this.a=n}function skn(n){this.a=n}function fkn(n){this.a=n}function hkn(n){this.a=n}function lkn(n){this.a=n}function sl(n){this.a=n}function sg(n){this.a=n}function akn(n){this.a=n}function dkn(n){this.a=n}function bkn(n){this.a=n}function wkn(n){this.a=n}function gkn(n){this.a=n}function pkn(n){this.a=n}function mkn(n){this.a=n}function vkn(n){this.a=n}function kkn(n){this.a=n}function ykn(n){this.a=n}function jkn(n){this.a=n}function Ekn(n){this.a=n}function Ckn(n){this.a=n}function Mkn(n){this.a=n}function Tkn(n){this.a=n}function Akn(n){this.a=n}function Skn(n){this.a=n}function Pkn(n){this.a=n}function Ikn(n){this.a=n}function Okn(n){this.a=n}function Dkn(n){this.a=n}function Lkn(n){this.a=n}function Nkn(n){this.a=n}function $kn(n){this.a=n}function xkn(n){this.a=n}function Fkn(n){this.a=n}function DE(n){this.a=n}function Bkn(n){this.f=n}function Rkn(n){this.a=n}function Kkn(n){this.a=n}function _kn(n){this.a=n}function Hkn(n){this.a=n}function qkn(n){this.a=n}function Ukn(n){this.a=n}function Gkn(n){this.a=n}function zkn(n){this.a=n}function Xkn(n){this.a=n}function Vkn(n){this.a=n}function Wkn(n){this.a=n}function Jkn(n){this.a=n}function Qkn(n){this.a=n}function Ykn(n){this.a=n}function Zkn(n){this.a=n}function nyn(n){this.a=n}function eyn(n){this.a=n}function tyn(n){this.a=n}function iyn(n){this.a=n}function ryn(n){this.a=n}function cyn(n){this.a=n}function uyn(n){this.a=n}function oyn(n){this.a=n}function syn(n){this.a=n}function fyn(n){this.a=n}function hyn(n){this.a=n}function lyn(n){this.a=n}function ayn(n){this.a=n}function tD(n){this.a=n}function xG(n){this.a=n}function lt(n){this.b=n}function dyn(n){this.a=n}function byn(n){this.a=n}function wyn(n){this.a=n}function gyn(n){this.a=n}function pyn(n){this.a=n}function myn(n){this.a=n}function vyn(n){this.a=n}function kyn(n){this.b=n}function yyn(n){this.a=n}function W9(n){this.a=n}function jyn(n){this.a=n}function Eyn(n){this.a=n}function FG(n){this.c=n}function LE(n){this.e=n}function NE(n){this.a=n}function $E(n){this.a=n}function iD(n){this.a=n}function Cyn(n){this.d=n}function Myn(n){this.a=n}function BG(n){this.a=n}function RG(n){this.a=n}function Wd(n){this.e=n}function Pfe(){this.a=0}function de(){Hu(this)}function Z(){pL(this)}function rD(){sIn(this)}function Tyn(){}function Jd(){this.c=Gdn}function Ayn(n,e){n.b+=e}function Ife(n,e){e.Wb(n)}function Ofe(n){return n.a}function Dfe(n){return n.a}function Lfe(n){return n.a}function Nfe(n){return n.a}function $fe(n){return n.a}function M(n){return n.e}function xfe(){return null}function Ffe(){return null}function Bfe(){Cz(),pLe()}function Rfe(n){n.b.Of(n.e)}function Syn(n){n.b=new CD}function Jv(n,e){n.b=e-n.b}function Qv(n,e){n.a=e-n.a}function Bn(n,e){n.push(e)}function Pyn(n,e){n.sort(e)}function Iyn(n,e){e.jd(n.a)}function Kfe(n,e){gi(e,n)}function _fe(n,e,t){n.Yd(t,e)}function J9(n,e){n.e=e,e.b=n}function KG(n){uh(),this.a=n}function Oyn(n){uh(),this.a=n}function Dyn(n){uh(),this.a=n}function cD(n){m0(),this.a=n}function Lyn(n){O4(),VK.le(n)}function _G(){_G=F,new de}function Ua(){YTn.call(this)}function HG(){YTn.call(this)}function qG(){Ua.call(this)}function uD(){Ua.call(this)}function Nyn(){Ua.call(this)}function Q9(){Ua.call(this)}function Cu(){Ua.call(this)}function ip(){Ua.call(this)}function Pe(){Ua.call(this)}function Bo(){Ua.call(this)}function $yn(){Ua.call(this)}function nc(){Ua.call(this)}function xyn(){Ua.call(this)}function Fyn(){this.a=this}function xE(){this.Bb|=256}function Byn(){this.b=new GMn}function Pb(n,e){n.length=e}function FE(n,e){nn(n.a,e)}function Hfe(n,e){bnn(n.c,e)}function qfe(n,e){fi(n.b,e)}function Ufe(n,e){uA(n.a,e)}function Gfe(n,e){cx(n.a,e)}function t4(n,e){it(n.e,e)}function rp(n){jA(n.c,n.b)}function zfe(n,e){n.kc().Nb(e)}function UG(n){this.a=B5e(n)}function ni(){this.a=new de}function Ryn(){this.a=new de}function GG(){this.a=new rCn}function BE(){this.a=new Z}function oD(){this.a=new Z}function zG(){this.a=new Z}function hs(){this.a=new cbn}function Ga(){this.a=new NLn}function XG(){this.a=new _U}function VG(){this.a=new TOn}function WG(){this.a=new BAn}function Kyn(){this.a=new Z}function _yn(){this.a=new Z}function Hyn(){this.a=new Z}function JG(){this.a=new Z}function qyn(){this.d=new Z}function Uyn(){this.a=new zOn}function Gyn(){this.a=new ni}function zyn(){this.a=new de}function Xyn(){this.b=new de}function Vyn(){this.b=new Z}function QG(){this.e=new Z}function Wyn(){this.a=new Z5n}function Jyn(){this.d=new Z}function Qyn(){QIn.call(this)}function Yyn(){QIn.call(this)}function Zyn(){Z.call(this)}function YG(){qG.call(this)}function ZG(){BE.call(this)}function njn(){qC.call(this)}function ejn(){JG.call(this)}function Yv(){Tyn.call(this)}function sD(){Yv.call(this)}function cp(){Tyn.call(this)}function nz(){cp.call(this)}function tjn(){rz.call(this)}function ijn(){rz.call(this)}function rjn(){rz.call(this)}function cjn(){cz.call(this)}function Zv(){svn.call(this)}function ez(){svn.call(this)}function Mu(){Ct.call(this)}function ujn(){yjn.call(this)}function ojn(){yjn.call(this)}function sjn(){de.call(this)}function fjn(){de.call(this)}function hjn(){de.call(this)}function fD(){cxn.call(this)}function ljn(){ni.call(this)}function ajn(){xE.call(this)}function hD(){BX.call(this)}function tz(){de.call(this)}function lD(){BX.call(this)}function aD(){de.call(this)}function djn(){de.call(this)}function iz(){ME.call(this)}function bjn(){iz.call(this)}function wjn(){ME.call(this)}function gjn(){rG.call(this)}function rz(){this.a=new ni}function pjn(){this.a=new de}function mjn(){this.a=new Z}function cz(){this.a=new de}function up(){this.a=new Ct}function vjn(){this.j=new Z}function kjn(){this.a=new mEn}function yjn(){this.a=new mvn}function uz(){this.a=new Z4n}function n6(){n6=F,KK=new Ht}function dD(){dD=F,_K=new Ejn}function bD(){bD=F,HK=new jjn}function jjn(){XO.call(this,"")}function Ejn(){XO.call(this,"")}function Cjn(n){S$n.call(this,n)}function Mjn(n){S$n.call(this,n)}function oz(n){fG.call(this,n)}function sz(n){XEn.call(this,n)}function Xfe(n){XEn.call(this,n)}function Vfe(n){sz.call(this,n)}function Wfe(n){sz.call(this,n)}function Jfe(n){sz.call(this,n)}function Tjn(n){zN.call(this,n)}function Ajn(n){zN.call(this,n)}function Sjn(n){uSn.call(this,n)}function Pjn(n){Oz.call(this,n)}function e6(n){WE.call(this,n)}function fz(n){WE.call(this,n)}function Ijn(n){WE.call(this,n)}function hz(n){mje.call(this,n)}function lz(n){hz.call(this,n)}function ec(n){APn.call(this,n)}function Ojn(n){ec.call(this,n)}function op(){z9.call(this,{})}function Djn(){Djn=F,dQn=new M0n}function RE(){RE=F,GK=new STn}function Ljn(){Ljn=F,oun=new Bu}function az(){az=F,sun=new N1}function KE(){KE=F,P8=new $1}function wD(n){b4(),this.a=n}function gD(n){RQ(),this.a=n}function Qd(n){nN(),this.f=n}function pD(n){nN(),this.f=n}function Njn(n){bSn(),this.a=n}function $jn(n){n.b=null,n.c=0}function Qfe(n,e){n.e=e,bqn(n,e)}function Yfe(n,e){n.a=e,cEe(n)}function mD(n,e,t){n.a[e.g]=t}function Zfe(n,e,t){kke(t,n,e)}function nhe(n,e){Wae(e.i,n.n)}function xjn(n,e){v6e(n).Cd(e)}function ehe(n,e){n.a.ec().Mc(e)}function Fjn(n,e){return n.g-e.g}function the(n,e){return n*n/e}function on(n){return Jn(n),n}function $(n){return Jn(n),n}function Y9(n){return Jn(n),n}function ihe(n){return new AE(n)}function rhe(n){return new qb(n)}function dz(n){return Jn(n),n}function che(n){return Jn(n),n}function _E(n){ec.call(this,n)}function Ir(n){ec.call(this,n)}function Bjn(n){ec.call(this,n)}function vD(n){APn.call(this,n)}function i4(n){ec.call(this,n)}function Gn(n){ec.call(this,n)}function Or(n){ec.call(this,n)}function Rjn(n){ec.call(this,n)}function sp(n){ec.call(this,n)}function Kl(n){ec.call(this,n)}function _l(n){ec.call(this,n)}function fp(n){ec.call(this,n)}function nh(n){ec.call(this,n)}function kD(n){ec.call(this,n)}function Le(n){ec.call(this,n)}function Ku(n){Jn(n),this.a=n}function bz(n){return ld(n),n}function t6(n){TW(n,n.length)}function i6(n){return n.b==n.c}function Ib(n){return!!n&&n.b}function uhe(n){return!!n&&n.k}function ohe(n){return!!n&&n.j}function she(n,e,t){n.c.Ef(e,t)}function Kjn(n,e){n.be(e),e.ae(n)}function hp(n){uh(),this.a=Se(n)}function yD(){this.a=Oe(Se(ur))}function _jn(){throw M(new Pe)}function fhe(){throw M(new Pe)}function wz(){throw M(new Pe)}function Hjn(){throw M(new Pe)}function hhe(){throw M(new Pe)}function lhe(){throw M(new Pe)}function HE(){HE=F,O4()}function Hl(){X9.call(this,"")}function r6(){X9.call(this,"")}function x1(){X9.call(this,"")}function lp(){X9.call(this,"")}function gz(n){Ir.call(this,n)}function pz(n){Ir.call(this,n)}function eh(n){Gn.call(this,n)}function r4(n){Q3.call(this,n)}function qjn(n){r4.call(this,n)}function jD(n){BC.call(this,n)}function ED(n){JX.call(this,n,0)}function CD(){sJ.call(this,12,3)}function T(n,e){return kOn(n,e)}function qE(n,e){return o$(n,e)}function ahe(n,e){return n.a-e.a}function dhe(n,e){return n.a-e.a}function bhe(n,e){return n.a-e.a}function whe(n,e){return e in n.a}function Ujn(n){return n.a?n.b:0}function ghe(n){return n.a?n.b:0}function phe(n,e,t){e.Cd(n.a[t])}function mhe(n,e,t){e.Pe(n.a[t])}function vhe(n,e){n.b=new rr(e)}function khe(n,e){return n.b=e,n}function Gjn(n,e){return n.c=e,n}function zjn(n,e){return n.f=e,n}function yhe(n,e){return n.g=e,n}function mz(n,e){return n.a=e,n}function vz(n,e){return n.f=e,n}function jhe(n,e){return n.k=e,n}function kz(n,e){return n.a=e,n}function Ehe(n,e){return n.e=e,n}function yz(n,e){return n.e=e,n}function Che(n,e){return n.f=e,n}function Mhe(n,e){n.b=!0,n.d=e}function The(n,e){return n.b-e.b}function Ahe(n,e){return n.g-e.g}function She(n,e){return n?0:e-1}function Xjn(n,e){return n?0:e-1}function Phe(n,e){return n?e-1:0}function Ihe(n,e){return n.s-e.s}function Ohe(n,e){return e.rg(n)}function Yd(n,e){return n.b=e,n}function UE(n,e){return n.a=e,n}function Zd(n,e){return n.c=e,n}function n0(n,e){return n.d=e,n}function e0(n,e){return n.e=e,n}function jz(n,e){return n.f=e,n}function c6(n,e){return n.a=e,n}function c4(n,e){return n.b=e,n}function u4(n,e){return n.c=e,n}function an(n,e){return n.c=e,n}function Sn(n,e){return n.b=e,n}function dn(n,e){return n.d=e,n}function bn(n,e){return n.e=e,n}function Dhe(n,e){return n.f=e,n}function wn(n,e){return n.g=e,n}function gn(n,e){return n.a=e,n}function pn(n,e){return n.i=e,n}function mn(n,e){return n.j=e,n}function Lhe(n,e){ca(),ic(e,n)}function Nhe(n,e,t){Jbe(n.a,e,t)}function GE(n){$L.call(this,n)}function Vjn(n){Z5e.call(this,n)}function Wjn(n){SIn.call(this,n)}function Ez(n){SIn.call(this,n)}function F1(n){S0.call(this,n)}function Jjn(n){CN.call(this,n)}function Qjn(n){CN.call(this,n)}function Yjn(){DX.call(this,"")}function Li(){this.a=0,this.b=0}function Zjn(){this.b=0,this.a=0}function nEn(n,e){n.b=0,Zb(n,e)}function eEn(n,e){return n.k=e,n}function $he(n,e){return n.j=e,n}function xhe(n,e){n.c=e,n.b=!0}function tEn(){tEn=F,TQn=Xke()}function B1(){B1=F,voe=rke()}function iEn(){iEn=F,Ti=gye()}function Cz(){Cz=F,Oa=z4()}function o4(){o4=F,Udn=cke()}function rEn(){rEn=F,ise=uke()}function Mz(){Mz=F,yc=tEe()}function uf(n){return n.e&&n.e()}function cEn(n){return n.l|n.m<<22}function uEn(n,e){return n.c._b(e)}function oEn(n,e){return rBn(n.b,e)}function MD(n){return n?n.d:null}function Fhe(n){return n?n.g:null}function Bhe(n){return n?n.i:null}function za(n){return ll(n),n.o}function fg(n,e){return n.a+=e,n}function TD(n,e){return n.a+=e,n}function ql(n,e){return n.a+=e,n}function t0(n,e){return n.a+=e,n}function Tz(n,e){for(;n.Bd(e););}function zE(n){this.a=new ap(n)}function sEn(){throw M(new Pe)}function fEn(){throw M(new Pe)}function hEn(){throw M(new Pe)}function lEn(){throw M(new Pe)}function aEn(){throw M(new Pe)}function dEn(){throw M(new Pe)}function Ul(n){this.a=new iN(n)}function bEn(){this.a=new K5(Rln)}function wEn(){this.b=new K5(rln)}function gEn(){this.a=new K5(f1n)}function pEn(){this.b=new K5(Fq)}function mEn(){this.b=new K5(Fq)}function XE(n){this.a=0,this.b=n}function Az(n){zGn(),ILe(this,n)}function s4(n){return z1(n),n.a}function Z9(n){return n.b!=n.d.c}function Sz(n,e){return n.d[e.p]}function vEn(n,e){return XTe(n,e)}function Pz(n,e,t){n.splice(e,t)}function hg(n,e){for(;n.Re(e););}function kEn(n){n.c?Dqn(n):Lqn(n)}function yEn(){throw M(new Pe)}function jEn(){throw M(new Pe)}function EEn(){throw M(new Pe)}function CEn(){throw M(new Pe)}function MEn(){throw M(new Pe)}function TEn(){throw M(new Pe)}function AEn(){throw M(new Pe)}function SEn(){throw M(new Pe)}function PEn(){throw M(new Pe)}function IEn(){throw M(new Pe)}function Rhe(){throw M(new nc)}function Khe(){throw M(new nc)}function n7(n){this.a=new OEn(n)}function OEn(n){Ume(this,n,jje())}function e7(n){return!n||oIn(n)}function t7(n){return Zf[n]!=-1}function _he(){cP!=0&&(cP=0),uP=-1}function DEn(){RK==null&&(RK=[])}function i7(n,e){Cg.call(this,n,e)}function f4(n,e){i7.call(this,n,e)}function LEn(n,e){this.a=n,this.b=e}function NEn(n,e){this.a=n,this.b=e}function $En(n,e){this.a=n,this.b=e}function xEn(n,e){this.a=n,this.b=e}function FEn(n,e){this.a=n,this.b=e}function BEn(n,e){this.a=n,this.b=e}function REn(n,e){this.a=n,this.b=e}function h4(n,e){this.e=n,this.d=e}function Iz(n,e){this.b=n,this.c=e}function KEn(n,e){this.b=n,this.a=e}function _En(n,e){this.b=n,this.a=e}function HEn(n,e){this.b=n,this.a=e}function qEn(n,e){this.b=n,this.a=e}function UEn(n,e){this.a=n,this.b=e}function AD(n,e){this.a=n,this.b=e}function GEn(n,e){this.a=n,this.f=e}function i0(n,e){this.g=n,this.i=e}function je(n,e){this.f=n,this.g=e}function zEn(n,e){this.b=n,this.c=e}function XEn(n){KX(n.dc()),this.c=n}function Hhe(n,e){this.a=n,this.b=e}function VEn(n,e){this.a=n,this.b=e}function WEn(n){this.a=u(Se(n),15)}function Oz(n){this.a=u(Se(n),15)}function JEn(n){this.a=u(Se(n),85)}function VE(n){this.b=u(Se(n),85)}function WE(n){this.b=u(Se(n),51)}function JE(){this.q=new y.Date}function SD(n,e){this.a=n,this.b=e}function QEn(n,e){return Zc(n.b,e)}function r7(n,e){return n.b.Hc(e)}function YEn(n,e){return n.b.Ic(e)}function ZEn(n,e){return n.b.Qc(e)}function nCn(n,e){return n.b.Hc(e)}function eCn(n,e){return n.c.uc(e)}function tCn(n,e){return rt(n.c,e)}function of(n,e){return n.a._b(e)}function iCn(n,e){return n>e&&e0}function ND(n,e){return Ec(n,e)<0}function vCn(n,e){return JL(n.a,e)}function ole(n,e){yOn.call(this,n,e)}function Bz(n){wN(),uSn.call(this,n)}function Rz(n,e){bPn(n,n.length,e)}function s7(n,e){HPn(n,n.length,e)}function d6(n,e){return n.a.get(e)}function kCn(n,e){return Zc(n.e,e)}function Kz(n){return Jn(n),!1}function _z(n){this.a=u(Se(n),229)}function cC(n){In.call(this,n,21)}function uC(n,e){je.call(this,n,e)}function $D(n,e){je.call(this,n,e)}function yCn(n,e){this.b=n,this.a=e}function oC(n,e){this.d=n,this.e=e}function jCn(n,e){this.a=n,this.b=e}function ECn(n,e){this.a=n,this.b=e}function CCn(n,e){this.a=n,this.b=e}function MCn(n,e){this.a=n,this.b=e}function bp(n,e){this.a=n,this.b=e}function TCn(n,e){this.b=n,this.a=e}function Hz(n,e){this.b=n,this.a=e}function qz(n,e){je.call(this,n,e)}function Uz(n,e){je.call(this,n,e)}function lg(n,e){je.call(this,n,e)}function xD(n,e){je.call(this,n,e)}function FD(n,e){je.call(this,n,e)}function BD(n,e){je.call(this,n,e)}function sC(n,e){je.call(this,n,e)}function Gz(n,e){this.b=n,this.a=e}function fC(n,e){je.call(this,n,e)}function zz(n,e){this.b=n,this.a=e}function hC(n,e){je.call(this,n,e)}function ACn(n,e){this.b=n,this.a=e}function Xz(n,e){je.call(this,n,e)}function RD(n,e){je.call(this,n,e)}function f7(n,e){je.call(this,n,e)}function b6(n,e,t){n.splice(e,0,t)}function sle(n,e,t){n.Mb(t)&&e.Cd(t)}function fle(n,e,t){e.Pe(n.a.Ye(t))}function hle(n,e,t){e.Dd(n.a.Ze(t))}function lle(n,e,t){e.Cd(n.a.Kb(t))}function ale(n,e){return Au(n.c,e)}function dle(n,e){return Au(n.e,e)}function lC(n,e){je.call(this,n,e)}function aC(n,e){je.call(this,n,e)}function w6(n,e){je.call(this,n,e)}function Vz(n,e){je.call(this,n,e)}function ei(n,e){je.call(this,n,e)}function dC(n,e){je.call(this,n,e)}function SCn(n,e){this.a=n,this.b=e}function PCn(n,e){this.a=n,this.b=e}function ICn(n,e){this.a=n,this.b=e}function OCn(n,e){this.a=n,this.b=e}function DCn(n,e){this.a=n,this.b=e}function LCn(n,e){this.a=n,this.b=e}function NCn(n,e){this.b=n,this.a=e}function $Cn(n,e){this.b=n,this.a=e}function Wz(n,e){this.b=n,this.a=e}function d4(n,e){this.c=n,this.d=e}function xCn(n,e){this.e=n,this.d=e}function FCn(n,e){this.a=n,this.b=e}function BCn(n,e){this.a=n,this.b=e}function RCn(n,e){this.a=n,this.b=e}function KCn(n,e){this.b=n,this.a=e}function _Cn(n,e){this.b=e,this.c=n}function bC(n,e){je.call(this,n,e)}function h7(n,e){je.call(this,n,e)}function KD(n,e){je.call(this,n,e)}function Jz(n,e){je.call(this,n,e)}function g6(n,e){je.call(this,n,e)}function _D(n,e){je.call(this,n,e)}function HD(n,e){je.call(this,n,e)}function l7(n,e){je.call(this,n,e)}function Qz(n,e){je.call(this,n,e)}function qD(n,e){je.call(this,n,e)}function p6(n,e){je.call(this,n,e)}function Yz(n,e){je.call(this,n,e)}function m6(n,e){je.call(this,n,e)}function v6(n,e){je.call(this,n,e)}function Db(n,e){je.call(this,n,e)}function UD(n,e){je.call(this,n,e)}function GD(n,e){je.call(this,n,e)}function Zz(n,e){je.call(this,n,e)}function a7(n,e){je.call(this,n,e)}function ag(n,e){je.call(this,n,e)}function zD(n,e){je.call(this,n,e)}function wC(n,e){je.call(this,n,e)}function d7(n,e){je.call(this,n,e)}function Lb(n,e){je.call(this,n,e)}function gC(n,e){je.call(this,n,e)}function nX(n,e){je.call(this,n,e)}function XD(n,e){je.call(this,n,e)}function VD(n,e){je.call(this,n,e)}function WD(n,e){je.call(this,n,e)}function JD(n,e){je.call(this,n,e)}function QD(n,e){je.call(this,n,e)}function YD(n,e){je.call(this,n,e)}function ZD(n,e){je.call(this,n,e)}function HCn(n,e){this.b=n,this.a=e}function eX(n,e){je.call(this,n,e)}function qCn(n,e){this.a=n,this.b=e}function UCn(n,e){this.a=n,this.b=e}function GCn(n,e){this.a=n,this.b=e}function tX(n,e){je.call(this,n,e)}function iX(n,e){je.call(this,n,e)}function zCn(n,e){this.a=n,this.b=e}function ble(n,e){return k4(),e!=n}function b7(n){return oe(n.a),n.b}function nL(n){return yCe(n,n.c),n}function XCn(){return tEn(),new TQn}function VCn(){VC(),this.a=new kV}function WCn(){OA(),this.a=new ni}function JCn(){NN(),this.b=new ni}function QCn(n,e){this.b=n,this.d=e}function YCn(n,e){this.a=n,this.b=e}function ZCn(n,e){this.a=n,this.b=e}function nMn(n,e){this.a=n,this.b=e}function eMn(n,e){this.b=n,this.a=e}function rX(n,e){je.call(this,n,e)}function cX(n,e){je.call(this,n,e)}function pC(n,e){je.call(this,n,e)}function u0(n,e){je.call(this,n,e)}function eL(n,e){je.call(this,n,e)}function mC(n,e){je.call(this,n,e)}function uX(n,e){je.call(this,n,e)}function oX(n,e){je.call(this,n,e)}function w7(n,e){je.call(this,n,e)}function sX(n,e){je.call(this,n,e)}function tL(n,e){je.call(this,n,e)}function vC(n,e){je.call(this,n,e)}function iL(n,e){je.call(this,n,e)}function rL(n,e){je.call(this,n,e)}function cL(n,e){je.call(this,n,e)}function uL(n,e){je.call(this,n,e)}function fX(n,e){je.call(this,n,e)}function oL(n,e){je.call(this,n,e)}function hX(n,e){je.call(this,n,e)}function g7(n,e){je.call(this,n,e)}function sL(n,e){je.call(this,n,e)}function lX(n,e){je.call(this,n,e)}function p7(n,e){je.call(this,n,e)}function aX(n,e){je.call(this,n,e)}function tMn(n,e){this.b=n,this.a=e}function iMn(n,e){this.b=n,this.a=e}function rMn(n,e){this.b=n,this.a=e}function cMn(n,e){this.b=n,this.a=e}function dX(n,e){this.a=n,this.b=e}function uMn(n,e){this.a=n,this.b=e}function oMn(n,e){this.a=n,this.b=e}function V(n,e){this.a=n,this.b=e}function k6(n,e){je.call(this,n,e)}function m7(n,e){je.call(this,n,e)}function wp(n,e){je.call(this,n,e)}function y6(n,e){je.call(this,n,e)}function v7(n,e){je.call(this,n,e)}function fL(n,e){je.call(this,n,e)}function kC(n,e){je.call(this,n,e)}function j6(n,e){je.call(this,n,e)}function hL(n,e){je.call(this,n,e)}function yC(n,e){je.call(this,n,e)}function dg(n,e){je.call(this,n,e)}function k7(n,e){je.call(this,n,e)}function E6(n,e){je.call(this,n,e)}function C6(n,e){je.call(this,n,e)}function y7(n,e){je.call(this,n,e)}function jC(n,e){je.call(this,n,e)}function bg(n,e){je.call(this,n,e)}function lL(n,e){je.call(this,n,e)}function sMn(n,e){je.call(this,n,e)}function EC(n,e){je.call(this,n,e)}function fMn(n,e){this.a=n,this.b=e}function hMn(n,e){this.a=n,this.b=e}function lMn(n,e){this.a=n,this.b=e}function aMn(n,e){this.a=n,this.b=e}function dMn(n,e){this.a=n,this.b=e}function bMn(n,e){this.a=n,this.b=e}function bi(n,e){this.a=n,this.b=e}function wMn(n,e){this.a=n,this.b=e}function gMn(n,e){this.a=n,this.b=e}function pMn(n,e){this.a=n,this.b=e}function mMn(n,e){this.a=n,this.b=e}function vMn(n,e){this.a=n,this.b=e}function kMn(n,e){this.a=n,this.b=e}function yMn(n,e){this.b=n,this.a=e}function jMn(n,e){this.b=n,this.a=e}function EMn(n,e){this.b=n,this.a=e}function CMn(n,e){this.b=n,this.a=e}function MMn(n,e){this.a=n,this.b=e}function TMn(n,e){this.a=n,this.b=e}function CC(n,e){je.call(this,n,e)}function AMn(n,e){this.a=n,this.b=e}function SMn(n,e){this.a=n,this.b=e}function gp(n,e){je.call(this,n,e)}function PMn(n,e){this.f=n,this.c=e}function bX(n,e){return Au(n.g,e)}function wle(n,e){return Au(e.b,n)}function IMn(n,e){return wx(n.a,e)}function gle(n,e){return-n.b.af(e)}function ple(n,e){n&&Xe(hE,n,e)}function wX(n,e){n.i=null,kT(n,e)}function mle(n,e,t){yKn(e,oF(n,t))}function vle(n,e,t){yKn(e,oF(n,t))}function kle(n,e){VMe(n.a,u(e,58))}function OMn(n,e){U4e(n.a,u(e,12))}function MC(n,e){this.a=n,this.b=e}function DMn(n,e){this.a=n,this.b=e}function LMn(n,e){this.a=n,this.b=e}function NMn(n,e){this.a=n,this.b=e}function $Mn(n,e){this.a=n,this.b=e}function xMn(n,e){this.d=n,this.b=e}function FMn(n,e){this.e=n,this.a=e}function j7(n,e){this.b=n,this.c=e}function gX(n,e){this.i=n,this.g=e}function pX(n,e){this.d=n,this.e=e}function yle(n,e){cme(new ne(n),e)}function TC(n){return Rk(n.c,n.b)}function Kr(n){return n?n.md():null}function x(n){return n??null}function Ai(n){return typeof n===nB}function Nb(n){return typeof n===i3}function $b(n){return typeof n===dtn}function o0(n,e){return Ec(n,e)==0}function AC(n,e){return Ec(n,e)>=0}function M6(n,e){return Ec(n,e)!=0}function SC(n,e){return jve(n.Kc(),e)}function _1(n,e){return n.Rd().Xb(e)}function BMn(n){return eo(n),n.d.gc()}function PC(n){return F6(n==null),n}function T6(n,e){return n.a+=""+e,n}function Er(n,e){return n.a+=""+e,n}function A6(n,e){return n.a+=""+e,n}function Dc(n,e){return n.a+=""+e,n}function Be(n,e){return n.a+=""+e,n}function mX(n,e){return n.a+=""+e,n}function jle(n){return""+(Jn(n),n)}function RMn(n){Hu(this),f5(this,n)}function KMn(){oJ(),dW.call(this)}function _Mn(n,e){mW.call(this,n,e)}function HMn(n,e){mW.call(this,n,e)}function IC(n,e){mW.call(this,n,e)}function ir(n,e){xt(n,e,n.c.b,n.c)}function wg(n,e){xt(n,e,n.a,n.a.a)}function vX(n){return Ln(n,0),null}function qMn(){this.b=0,this.a=!1}function UMn(){this.b=0,this.a=!1}function GMn(){this.b=new ap(Qb(12))}function zMn(){zMn=F,kYn=Ce(jx())}function XMn(){XMn=F,HZn=Ce(iqn())}function VMn(){VMn=F,lre=Ce(xxn())}function kX(){kX=F,_G(),fun=new de}function sf(n){return n.a=0,n.b=0,n}function WMn(n,e){return n.a=e.g+1,n}function aL(n,e){Kb.call(this,n,e)}function Mn(n,e){Dt.call(this,n,e)}function gg(n,e){gX.call(this,n,e)}function JMn(n,e){T7.call(this,n,e)}function dL(n,e){Y4.call(this,n,e)}function Ue(n,e){iC(),Xe(yO,n,e)}function QMn(n,e){n.q.setTime(id(e))}function Ele(n){y.clearTimeout(n)}function Cle(n){return Se(n),new S6(n)}function YMn(n,e){return x(n)===x(e)}function ZMn(n,e){return n.a.a.a.cc(e)}function bL(n,e){return qo(n.a,0,e)}function yX(n){return Awe(u(n,74))}function pp(n){return wi((Jn(n),n))}function Mle(n){return wi((Jn(n),n))}function nTn(n){return Yc(n.l,n.m,n.h)}function jX(n,e){return jc(n.a,e.a)}function Tle(n,e){return KPn(n.a,e.a)}function Ale(n,e){return bt(n.a,e.a)}function th(n,e){return n.indexOf(e)}function Sle(n,e){return n.j[e.p]==2}function s0(n,e){return n==e?0:n?1:-1}function OC(n){return n<10?"0"+n:""+n}function Vr(n){return typeof n===dtn}function Ple(n){return n==rb||n==Iw}function Ile(n){return n==rb||n==Pw}function eTn(n,e){return jc(n.g,e.g)}function EX(n){return qr(n.b.b,n,0)}function tTn(){rM.call(this,0,0,0,0)}function ih(){CG.call(this,new Ql)}function CX(n,e){F4(n,0,n.length,e)}function Ole(n,e){return nn(n.a,e),e}function Dle(n,e){return xs(),e.a+=n}function Lle(n,e){return xs(),e.a+=n}function Nle(n,e){return xs(),e.c+=n}function $le(n,e){return nn(n.c,e),n}function MX(n,e){return Mo(n.a,e),n}function iTn(n){this.a=XCn(),this.b=n}function rTn(n){this.a=XCn(),this.b=n}function rr(n){this.a=n.a,this.b=n.b}function S6(n){this.a=n,GO.call(this)}function cTn(n){this.a=n,GO.call(this)}function mp(){Ho.call(this,0,0,0,0)}function DC(n){return Mo(new ii,n)}function uTn(n){return jM(u(n,123))}function fo(n){return n.vh()&&n.wh()}function pg(n){return n!=Jf&&n!=Sa}function hl(n){return n==Br||n==Xr}function mg(n){return n==us||n==Vf}function oTn(n){return n==S2||n==A2}function xle(n,e){return jc(n.g,e.g)}function sTn(n,e){return new Y4(e,n)}function Fle(n,e){return new Y4(e,n)}function TX(n){return rbe(n.b.Kc(),n.a)}function wL(n,e){um(n,e),G4(n,n.D)}function gL(n,e,t){aT(n,e),lT(n,t)}function vg(n,e,t){I0(n,e),P0(n,t)}function Ro(n,e,t){eu(n,e),tu(n,t)}function E7(n,e,t){_4(n,e),q4(n,t)}function C7(n,e,t){H4(n,e),U4(n,t)}function fTn(n,e,t){sV.call(this,n,e,t)}function AX(n){PMn.call(this,n,!0)}function hTn(){uC.call(this,"Tail",3)}function lTn(){uC.call(this,"Head",1)}function H1(n){dh(),mve.call(this,n)}function f0(n){rM.call(this,n,n,n,n)}function pL(n){n.c=K(ki,Fn,1,0,5,1)}function SX(n){return n.b&&xF(n),n.a}function PX(n){return n.b&&xF(n),n.c}function Ble(n,e){qf||(n.b=e)}function Rle(n,e){return n[n.length]=e}function Kle(n,e){return n[n.length]=e}function _le(n,e){return Yb(e,Af(n))}function Hle(n,e){return Yb(e,Af(n))}function qle(n,e){return pT(dN(n.d),e)}function Ule(n,e){return pT(dN(n.g),e)}function Gle(n,e){return pT(dN(n.j),e)}function Ni(n,e){Dt.call(this,n.b,e)}function zle(n,e){ve(Sc(n.a),DOn(e))}function Xle(n,e){ve(no(n.a),LOn(e))}function Vle(n,e,t){Ro(t,t.i+n,t.j+e)}function aTn(n,e,t){$t(n.c[e.g],e.g,t)}function Wle(n,e,t){u(n.c,71).Gi(e,t)}function mL(n,e,t){return $t(n,e,t),t}function dTn(n){nu(n.Sf(),new D9n(n))}function kg(n){return n!=null?mt(n):0}function Jle(n){return n==null?0:mt(n)}function P6(n){nt(),Wd.call(this,n)}function bTn(n){this.a=n,qV.call(this,n)}function Mf(){Mf=F,y.Math.log(2)}function Ko(){Ko=F,rl=(pCn(),Moe)}function wTn(){wTn=F,YH=new j5(aU)}function Ie(){Ie=F,new gTn,new Z}function gTn(){new de,new de,new de}function Qle(){throw M(new Kl(QJn))}function Yle(){throw M(new Kl(QJn))}function Zle(){throw M(new Kl(YJn))}function n1e(){throw M(new Kl(YJn))}function vL(n){this.a=n,VE.call(this,n)}function kL(n){this.a=n,VE.call(this,n)}function pTn(n,e){m0(),this.a=n,this.b=e}function e1e(n,e){Se(e),Tg(n).Jc(new Ru)}function Yt(n,e){QL(n.c,n.c.length,e)}function tc(n){return n.ae?1:0}function OX(n,e){return Ec(n,e)>0?n:e}function Yc(n,e,t){return{l:n,m:e,h:t}}function t1e(n,e){n.a!=null&&OMn(e,n.a)}function i1e(n){Zi(n,null),Ii(n,null)}function r1e(n,e,t){return Xe(n.g,t,e)}function yg(n,e,t){return nZ(e,t,n.c)}function c1e(n,e,t){return Xe(n.k,t,e)}function u1e(n,e,t){return GOe(n,e,t),t}function o1e(n,e){return ko(),e.n.b+=n}function vTn(n){nJ.call(this),this.b=n}function DX(n){vV.call(this),this.a=n}function kTn(){uC.call(this,"Range",2)}function LC(n){this.b=n,this.a=new Z}function yTn(n){this.b=new $bn,this.a=n}function jTn(n){n.a=new OO,n.c=new OO}function ETn(n){n.a=new de,n.d=new de}function CTn(n){$N(n,null),xN(n,null)}function MTn(n,e){return XOe(n.a,e,null)}function s1e(n,e){return Xe(n.a,e.a,e)}function Ki(n){return new V(n.a,n.b)}function LX(n){return new V(n.c,n.d)}function f1e(n){return new V(n.c,n.d)}function I6(n,e){return cOe(n.c,n.b,e)}function O(n,e){return n!=null&&Tx(n,e)}function yL(n,e){return Yve(n.Kc(),e)!=-1}function NC(n){return n.Ob()?n.Pb():null}function h1e(n){this.b=(Dn(),new eD(n))}function NX(n){this.a=n,de.call(this)}function TTn(){T7.call(this,null,null)}function ATn(){_C.call(this,null,null)}function STn(){je.call(this,"INSTANCE",0)}function PTn(){LZ(),this.a=new K5(Ion)}function ITn(n){return hh(n,0,n.length)}function l1e(n,e){return new VTn(n.Kc(),e)}function $X(n,e){return n.a.Bc(e)!=null}function OTn(n,e){me(n),n.Gc(u(e,15))}function a1e(n,e,t){n.c.bd(e,u(t,136))}function d1e(n,e,t){n.c.Ui(e,u(t,136))}function DTn(n,e){n.c&&(tW(e),rOn(e))}function b1e(n,e){n.q.setHours(e),G5(n,e)}function w1e(n,e){a0(e,n.a.a.a,n.a.a.b)}function g1e(n,e,t,i){$t(n.a[e.g],t.g,i)}function jL(n,e,t){return n.a[e.g][t.g]}function p1e(n,e){return n.e[e.c.p][e.p]}function m1e(n,e){return n.c[e.c.p][e.p]}function Tf(n,e){return n.a[e.c.p][e.p]}function v1e(n,e){return n.j[e.p]=IMe(e)}function EL(n,e){return n.a.Bc(e)!=null}function k1e(n,e){return $(R(e.a))<=n}function y1e(n,e){return $(R(e.a))>=n}function j1e(n,e){return RJ(n.f,e.Pg())}function vp(n,e){return n.a*e.a+n.b*e.b}function E1e(n,e){return n.a0?e/(n*n):e*100}function V1e(n,e){return n>0?e*e/n:e*e*100}function xb(n,e){return u(Lf(n.a,e),34)}function W1e(n,e){return ca(),Pn(n,e.e,e)}function J1e(n,e,t){return nC(),t.Mg(n,e)}function Q1e(n){return kl(),n.e.a+n.f.a/2}function Y1e(n,e,t){return kl(),t.e.a-n*e}function Z1e(n){return kl(),n.e.b+n.f.b/2}function nae(n,e,t){return kl(),t.e.b-n*e}function sAn(n){n.d=new cAn(n),n.e=new de}function fAn(){this.a=new C0,this.b=new C0}function hAn(n){this.c=n,this.a=1,this.b=1}function lAn(n){YF(),Syn(this),this.Ff(n)}function eae(n,e,t){YM(),n.pf(e)&&t.Cd(n)}function tae(n,e,t){return nn(e,jBn(n,t))}function a0(n,e,t){return n.a+=e,n.b+=t,n}function iae(n,e,t){return n.a*=e,n.b*=t,n}function ZX(n,e){return n.a=e.a,n.b=e.b,n}function HC(n){return n.a=-n.a,n.b=-n.b,n}function N6(n,e,t){return n.a-=e,n.b-=t,n}function aAn(n){Ct.call(this),c5(this,n)}function dAn(){je.call(this,"GROW_TREE",0)}function bAn(){je.call(this,"POLYOMINO",0)}function lo(n,e,t){Iu.call(this,n,e,t,2)}function rae(n,e,t){k5(Sc(n.a),e,DOn(t))}function wAn(n,e){a6(),T7.call(this,n,e)}function nV(n,e){Gl(),_C.call(this,n,e)}function gAn(n,e){Gl(),nV.call(this,n,e)}function pAn(n,e){Gl(),_C.call(this,n,e)}function cae(n,e){return n.c.Fc(u(e,136))}function uae(n,e,t){k5(no(n.a),e,LOn(t))}function mAn(n){this.c=n,eu(n,0),tu(n,0)}function PL(n,e){Ko(),oM.call(this,n,e)}function vAn(n,e){Ko(),PL.call(this,n,e)}function eV(n,e){Ko(),PL.call(this,n,e)}function tV(n,e){Ko(),oM.call(this,n,e)}function kAn(n,e){Ko(),eV.call(this,n,e)}function yAn(n,e){Ko(),tV.call(this,n,e)}function jAn(n,e){Ko(),oM.call(this,n,e)}function oae(n,e,t){return e.zl(n.e,n.c,t)}function sae(n,e,t){return e.Al(n.e,n.c,t)}function iV(n,e,t){return qA(ak(n,e),t)}function IL(n,e){return na(n.e,u(e,54))}function fae(n){return n==null?null:NDe(n)}function hae(n){return n==null?null:Aje(n)}function lae(n){return n==null?null:Jr(n)}function aae(n){return n==null?null:Jr(n)}function un(n){return F6(n==null||Nb(n)),n}function R(n){return F6(n==null||$b(n)),n}function Oe(n){return F6(n==null||Ai(n)),n}function ll(n){n.o==null&&cMe(n)}function rV(n){if(!n)throw M(new Q9)}function dae(n){if(!n)throw M(new uD)}function oe(n){if(!n)throw M(new nc)}function Fb(n){if(!n)throw M(new Cu)}function EAn(n){if(!n)throw M(new Bo)}function m4(){m4=F,aE=new ujn,new ojn}function Mg(){Mg=F,O2=new lt("root")}function cV(){cxn.call(this),this.Bb|=hr}function bae(n,e){this.d=n,c9n(this),this.b=e}function uV(n,e){i$.call(this,n),this.a=e}function oV(n,e){i$.call(this,n),this.a=e}function sV(n,e,t){VM.call(this,n,e,t,null)}function CAn(n,e,t){VM.call(this,n,e,t,null)}function P7(n,e){this.c=n,h4.call(this,n,e)}function $6(n,e){this.a=n,P7.call(this,n,e)}function fV(n){this.q=new y.Date(id(n))}function MAn(n){return n>8?0:n+1}function TAn(n,e){qf||nn(n.a,e)}function wae(n,e){return o7(),Q4(e.d.i,n)}function gae(n,e){return Hp(),new tUn(e,n)}function pae(n,e,t){return n.Ne(e,t)<=0?t:e}function mae(n,e,t){return n.Ne(e,t)<=0?e:t}function vae(n,e){return u(Lf(n.b,e),143)}function kae(n,e){return u(Lf(n.c,e),233)}function OL(n){return u(sn(n.a,n.b),294)}function AAn(n){return new V(n.c,n.d+n.a)}function SAn(n){return Jn(n),n?1231:1237}function PAn(n){return ko(),oTn(u(n,203))}function Bb(){Bb=F,ron=yn((go(),Gd))}function yae(n,e){e.a?MCe(n,e):EL(n.a,e.b)}function I7(n,e,t){++n.j,n.tj(),t$(n,e,t)}function IAn(n,e,t){++n.j,n.qj(e,n.Zi(e,t))}function OAn(n,e,t){var i;i=n.fd(e),i.Rb(t)}function hV(n,e,t){return t=So(n,e,6,t),t}function lV(n,e,t){return t=So(n,e,3,t),t}function aV(n,e,t){return t=So(n,e,9,t),t}function ch(n,e){return X7(e,xtn),n.f=e,n}function dV(n,e){return(e&et)%n.d.length}function DAn(n,e,t){return zen(n.c,n.b,e,t)}function LAn(n,e){this.c=n,S0.call(this,e)}function NAn(n,e){this.a=n,kyn.call(this,e)}function O7(n,e){this.a=n,kyn.call(this,e)}function Dt(n,e){lt.call(this,n),this.a=e}function bV(n,e){FG.call(this,n),this.a=e}function DL(n,e){FG.call(this,n),this.a=e}function jae(n){VY.call(this,0,0),this.f=n}function $An(n,e,t){return n.a+=hh(e,0,t),n}function D7(n){return!n.a&&(n.a=new C0n),n.a}function wV(n,e){var t;return t=n.e,n.e=e,t}function gV(n,e){var t;return t=e,!!n.Fe(t)}function Eae(n,e){return _n(),n==e?0:n?1:-1}function Rb(n,e){n.a.bd(n.b,e),++n.b,n.c=-1}function L7(n){n.b?L7(n.b):n.f.c.zc(n.e,n.d)}function xAn(n){Hu(n.e),n.d.b=n.d,n.d.a=n.d}function Cae(n,e,t){Xa(),t9n(n,e.Ve(n.a,t))}function pV(n,e,t){return Pp(n,u(e,22),t)}function $s(n,e){return qE(new Array(e),n)}function Mae(n){return Ae(U1(n,32))^Ae(n)}function LL(n){return String.fromCharCode(n)}function Tae(n){return n==null?null:n.message}function Aae(n,e,t){return n.apply(e,t)}function Sae(n,e){var t;t=n[DB],t.call(n,e)}function Pae(n,e){var t;t=n[DB],t.call(n,e)}function Iae(n,e){return o7(),!Q4(e.d.i,n)}function mV(n,e,t,i){rM.call(this,n,e,t,i)}function FAn(){qC.call(this),this.a=new Li}function vV(){this.n=new Li,this.o=new Li}function BAn(){this.b=new Li,this.c=new Z}function RAn(){this.a=new Z,this.b=new Z}function KAn(){this.a=new _U,this.b=new Byn}function kV(){this.b=new Ql,this.a=new Ql}function _An(){this.b=new ni,this.a=new ni}function HAn(){this.b=new de,this.a=new de}function qAn(){this.b=new wEn,this.a=new H3n}function UAn(){this.a=new n8n,this.b=new Lpn}function GAn(){this.a=new Z,this.d=new Z}function qC(){this.n=new cp,this.i=new mp}function zAn(n){this.a=(Co(n,mw),new Gc(n))}function XAn(n){this.a=(Co(n,mw),new Gc(n))}function Oae(n){return n<100?null:new F1(n)}function Dae(n,e){return n.n.a=(Jn(e),e+10)}function Lae(n,e){return n.n.a=(Jn(e),e+10)}function Nae(n,e){return e==n||km(TA(e),n)}function VAn(n,e){return Xe(n.a,e,"")==null}function $ae(n,e){var t;return t=e.qi(n.a),t}function tt(n,e){return n.a+=e.a,n.b+=e.b,n}function mi(n,e){return n.a-=e.a,n.b-=e.b,n}function xae(n){return Pb(n.j.c,0),n.a=-1,n}function yV(n,e,t){return t=So(n,e,11,t),t}function Fae(n,e,t){t!=null&&mT(e,Fx(n,t))}function Bae(n,e,t){t!=null&&vT(e,Fx(n,t))}function jp(n,e,t,i){q.call(this,n,e,t,i)}function jV(n,e,t,i){q.call(this,n,e,t,i)}function WAn(n,e,t,i){jV.call(this,n,e,t,i)}function JAn(n,e,t,i){bM.call(this,n,e,t,i)}function NL(n,e,t,i){bM.call(this,n,e,t,i)}function EV(n,e,t,i){bM.call(this,n,e,t,i)}function QAn(n,e,t,i){NL.call(this,n,e,t,i)}function CV(n,e,t,i){NL.call(this,n,e,t,i)}function Nn(n,e,t,i){EV.call(this,n,e,t,i)}function YAn(n,e,t,i){CV.call(this,n,e,t,i)}function ZAn(n,e,t,i){jW.call(this,n,e,t,i)}function Kb(n,e){Ir.call(this,k8+n+Td+e)}function MV(n,e){return n.jk().wi().ri(n,e)}function TV(n,e){return n.jk().wi().ti(n,e)}function nSn(n,e){return Jn(n),x(n)===x(e)}function An(n,e){return Jn(n),x(n)===x(e)}function Rae(n,e){return n.b.Bd(new ECn(n,e))}function Kae(n,e){return n.b.Bd(new CCn(n,e))}function eSn(n,e){return n.b.Bd(new MCn(n,e))}function _ae(n,e){return n.e=u(n.d.Kb(e),159)}function AV(n,e,t){return n.lastIndexOf(e,t)}function Hae(n,e,t){return bt(n[e.a],n[t.a])}function qae(n,e){return U(e,(cn(),Cj),n)}function Uae(n,e){return jc(e.a.d.p,n.a.d.p)}function Gae(n,e){return jc(n.a.d.p,e.a.d.p)}function zae(n,e){return bt(n.c-n.s,e.c-e.s)}function Xae(n,e){return bt(n.b.e.a,e.b.e.a)}function Vae(n,e){return bt(n.c.e.a,e.c.e.a)}function tSn(n){return n.c?qr(n.c.a,n,0):-1}function Ep(n){return n==Ud||n==tl||n==qc}function SV(n,e){this.c=n,oN.call(this,n,e)}function iSn(n,e,t){this.a=n,JX.call(this,e,t)}function rSn(n){this.c=n,IC.call(this,Ey,0)}function cSn(n,e,t){this.c=e,this.b=t,this.a=n}function N7(n){k4(),this.d=n,this.a=new Eg}function uSn(n){uh(),this.a=(Dn(),new r4(n))}function Wae(n,e){hl(n.f)?QCe(n,e):Sye(n,e)}function oSn(n,e){sbe.call(this,n,n.length,e)}function Jae(n,e){qf||e&&(n.d=e)}function sSn(n,e){return O(e,15)&&xqn(n.c,e)}function Qae(n,e,t){return u(n.c,71).Wk(e,t)}function UC(n,e,t){return u(n.c,71).Xk(e,t)}function Yae(n,e,t){return oae(n,u(e,343),t)}function PV(n,e,t){return sae(n,u(e,343),t)}function Zae(n,e,t){return PKn(n,u(e,343),t)}function fSn(n,e,t){return _ye(n,u(e,343),t)}function x6(n,e){return e==null?null:tw(n.b,e)}function IV(n){return $b(n)?(Jn(n),n):n.ue()}function GC(n){return!isNaN(n)&&!isFinite(n)}function $L(n){jTn(this),vo(this),Bi(this,n)}function _u(n){pL(this),zV(this.c,0,n.Pc())}function _o(n,e,t){this.a=n,this.b=e,this.c=t}function hSn(n,e,t){this.a=n,this.b=e,this.c=t}function lSn(n,e,t){this.d=n,this.b=t,this.a=e}function aSn(n){this.a=n,fl(),vc(Date.now())}function dSn(n){bo(n.a),GJ(n.c,n.b),n.b=null}function xL(){xL=F,Oun=new $0n,AQn=new x0n}function bSn(){bSn=F,Ioe=K(ki,Fn,1,0,5,1)}function wSn(){wSn=F,Voe=K(ki,Fn,1,0,5,1)}function OV(){OV=F,Woe=K(ki,Fn,1,0,5,1)}function uh(){uh=F,new KG((Dn(),Dn(),sr))}function nde(n){return B4(),Ee((yNn(),IQn),n)}function ede(n){return Gu(),Ee((lNn(),xQn),n)}function tde(n){return YT(),Ee((JDn(),HQn),n)}function ide(n){return cT(),Ee((QDn(),qQn),n)}function rde(n){return NA(),Ee((Jxn(),UQn),n)}function cde(n){return bf(),Ee((fNn(),XQn),n)}function ude(n){return Uu(),Ee((sNn(),WQn),n)}function ode(n){return bu(),Ee((hNn(),QQn),n)}function sde(n){return VA(),Ee((zMn(),kYn),n)}function fde(n){return N0(),Ee((ENn(),jYn),n)}function hde(n){return Vp(),Ee((MNn(),CYn),n)}function lde(n){return A5(),Ee((CNn(),AYn),n)}function ade(n){return YE(),Ee((jDn(),SYn),n)}function dde(n){return uT(),Ee((YDn(),GYn),n)}function bde(n){return i5(),Ee((aNn(),pZn),n)}function wde(n){return Vi(),Ee((u$n(),yZn),n)}function gde(n){return nm(),Ee((ANn(),TZn),n)}function pde(n){return dd(),Ee((TNn(),DZn),n)}function DV(n,e){if(!n)throw M(new Gn(e))}function v4(n){if(!n)throw M(new Or(btn))}function FL(n,e){if(n!=e)throw M(new Bo)}function gSn(n,e,t){this.a=n,this.b=e,this.c=t}function LV(n,e,t){this.a=n,this.b=e,this.c=t}function pSn(n,e,t){this.a=n,this.b=e,this.c=t}function zC(n,e,t){this.b=n,this.a=e,this.c=t}function NV(n,e,t){this.b=n,this.c=e,this.a=t}function $V(n,e,t){this.a=n,this.b=e,this.c=t}function XC(n,e,t){this.e=e,this.b=n,this.d=t}function mSn(n,e,t){this.b=n,this.a=e,this.c=t}function mde(n,e,t){return Xa(),n.a.Yd(e,t),e}function BL(n){var e;return e=new ubn,e.e=n,e}function xV(n){var e;return e=new qyn,e.b=n,e}function $7(){$7=F,CP=new sgn,MP=new fgn}function VC(){VC=F,XZn=new xgn,zZn=new Fgn}function xs(){xs=F,YZn=new G2n,ZZn=new z2n}function vde(n){return D0(),Ee((qLn(),fne),n)}function kde(n){return tr(),Ee((XMn(),HZn),n)}function yde(n){return OT(),Ee((PNn(),GZn),n)}function jde(n){return $f(),Ee((SNn(),tne),n)}function Ede(n){return ow(),Ee((o$n(),rne),n)}function Cde(n){return DA(),Ee(($xn(),hne),n)}function Mde(n){return Yp(),Ee((D$n(),lne),n)}function Tde(n){return QM(),Ee((cLn(),ane),n)}function Ade(n){return u5(),Ee((_Ln(),dne),n)}function Sde(n){return bT(),Ee((HLn(),bne),n)}function Pde(n){return o1(),Ee((s$n(),wne),n)}function Ide(n){return pk(),Ee((eLn(),gne),n)}function Ode(n){return jm(),Ee(($$n(),jne),n)}function Dde(n){return pr(),Ee((aFn(),Ene),n)}function Lde(n){return Z4(),Ee((GLn(),Cne),n)}function Nde(n){return vl(),Ee((zLn(),Tne),n)}function $de(n){return KM(),Ee((nLn(),Ane),n)}function xde(n){return Jk(),Ee((N$n(),yne),n)}function Fde(n){return hd(),Ee((ULn(),mne),n)}function Bde(n){return vA(),Ee((L$n(),vne),n)}function Rde(n){return hk(),Ee((tLn(),kne),n)}function Kde(n){return Yo(),Ee((h$n(),Sne),n)}function _de(n){return a1(),Ee((Xxn(),Yte),n)}function Hde(n){return g5(),Ee((XLn(),Zte),n)}function qde(n){return cw(),Ee((INn(),nie),n)}function Ude(n){return T5(),Ee((f$n(),eie),n)}function Gde(n){return gs(),Ee((dFn(),tie),n)}function zde(n){return lh(),Ee((ONn(),iie),n)}function Xde(n){return wk(),Ee((iLn(),rie),n)}function Vde(n){return gr(),Ee((JLn(),uie),n)}function Wde(n){return ST(),Ee((VLn(),oie),n)}function Jde(n){return d5(),Ee((WLn(),sie),n)}function Qde(n){return om(),Ee((QLn(),fie),n)}function Yde(n){return dT(),Ee((YLn(),hie),n)}function Zde(n){return DT(),Ee((ZLn(),lie),n)}function n0e(n){return O0(),Ee((oNn(),Aie),n)}function e0e(n){return n5(),Ee((rLn(),Die),n)}function t0e(n){return sh(),Ee((sLn(),Rie),n)}function i0e(n){return Sf(),Ee((fLn(),_ie),n)}function r0e(n){return lf(),Ee((hLn(),tre),n)}function c0e(n){return M0(),Ee((lLn(),fre),n)}function u0e(n){return Qp(),Ee((BNn(),hre),n)}function o0e(n){return q5(),Ee((VMn(),lre),n)}function s0e(n){return b5(),Ee((nNn(),are),n)}function f0e(n){return w5(),Ee((FNn(),$re),n)}function h0e(n){return FM(),Ee((uLn(),xre),n)}function l0e(n){return yT(),Ee((oLn(),_re),n)}function a0e(n){return wA(),Ee((l$n(),qre),n)}function d0e(n){return Ok(),Ee((eNn(),Gre),n)}function b0e(n){return ZM(),Ee((aLn(),Ure),n)}function w0e(n){return sA(),Ee((xNn(),lce),n)}function g0e(n){return AT(),Ee((tNn(),ace),n)}function p0e(n){return XT(),Ee((iNn(),dce),n)}function m0e(n){return rA(),Ee((rNn(),wce),n)}function v0e(n){return _T(),Ee((cNn(),mce),n)}function k0e(n){return GM(),Ee((dLn(),Rce),n)}function y0e(n){return V4(),Ee((ZDn(),_Zn),n)}function j0e(n){return Vn(),Ee((x$n(),xZn),n)}function E0e(n){return nT(),Ee((uNn(),Kce),n)}function C0e(n){return N$(),Ee((bLn(),_ce),n)}function M0e(n){return R5(),Ee((a$n(),qce),n)}function T0e(n){return eC(),Ee((IDn(),Gce),n)}function A0e(n){return Fk(),Ee((bNn(),Uce),n)}function S0e(n){return tC(),Ee((ODn(),Xce),n)}function P0e(n){return ck(),Ee((wLn(),Vce),n)}function I0e(n){return Yk(),Ee((d$n(),Wce),n)}function O0e(n){return f6(),Ee((DDn(),lue),n)}function D0e(n){return Ak(),Ee((gLn(),aue),n)}function L0e(n){return gf(),Ee((w$n(),mue),n)}function N0e(n){return l1(),Ee((Lxn(),kue),n)}function $0e(n){return Rh(),Ee((F$n(),yue),n)}function x0e(n){return wd(),Ee((B$n(),Aue),n)}function F0e(n){return ci(),Ee((b$n(),zue),n)}function B0e(n){return Nf(),Ee((wNn(),Xue),n)}function R0e(n){return El(),Ee((RNn(),Vue),n)}function K0e(n){return pA(),Ee((R$n(),Wue),n)}function _0e(n){return jl(),Ee((dNn(),Que),n)}function H0e(n){return To(),Ee((KNn(),Zue),n)}function q0e(n){return lw(),Ee((Wxn(),noe),n)}function U0e(n){return Fg(),Ee((g$n(),eoe),n)}function G0e(n){return Oi(),Ee((K$n(),toe),n)}function z0e(n){return zu(),Ee((_$n(),ioe),n)}function X0e(n){return tn(),Ee((p$n(),roe),n)}function V0e(n){return go(),Ee((_Nn(),foe),n)}function W0e(n){return io(),Ee((Vxn(),hoe),n)}function J0e(n){return Gp(),Ee((gNn(),loe),n)}function Q0e(n,e){return Jn(n),n+(Jn(e),e)}function Y0e(n){return RL(),Ee((pLn(),aoe),n)}function Z0e(n){return qT(),Ee((HNn(),doe),n)}function nbe(n){return LT(),Ee((qNn(),goe),n)}function k4(){k4=F,tln=(tn(),Wn),II=Zn}function RL(){RL=F,vdn=new VSn,kdn=new LPn}function ebe(n){return!n.e&&(n.e=new Z),n.e}function KL(n,e){this.c=n,this.a=e,this.b=e-n}function vSn(n,e,t){this.a=n,this.b=e,this.c=t}function _L(n,e,t){this.a=n,this.b=e,this.c=t}function FV(n,e,t){this.a=n,this.b=e,this.c=t}function BV(n,e,t){this.a=n,this.b=e,this.c=t}function kSn(n,e,t){this.a=n,this.b=e,this.c=t}function ySn(n,e,t){this.a=n,this.b=e,this.c=t}function Xl(n,e,t){this.e=n,this.a=e,this.c=t}function jSn(n,e,t){Ko(),tJ.call(this,n,e,t)}function HL(n,e,t){Ko(),RW.call(this,n,e,t)}function RV(n,e,t){Ko(),RW.call(this,n,e,t)}function KV(n,e,t){Ko(),RW.call(this,n,e,t)}function ESn(n,e,t){Ko(),HL.call(this,n,e,t)}function _V(n,e,t){Ko(),HL.call(this,n,e,t)}function CSn(n,e,t){Ko(),_V.call(this,n,e,t)}function MSn(n,e,t){Ko(),RV.call(this,n,e,t)}function TSn(n,e,t){Ko(),KV.call(this,n,e,t)}function qL(n){rM.call(this,n.d,n.c,n.a,n.b)}function HV(n){rM.call(this,n.d,n.c,n.a,n.b)}function qV(n){this.d=n,c9n(this),this.b=nwe(n.d)}function tbe(n){return Cm(),Ee((Nxn(),Poe),n)}function x7(n,e){return Se(n),Se(e),new NEn(n,e)}function Cp(n,e){return Se(n),Se(e),new RSn(n,e)}function ibe(n,e){return Se(n),Se(e),new KSn(n,e)}function rbe(n,e){return Se(n),Se(e),new qEn(n,e)}function UL(n){return oe(n.b!=0),Xo(n,n.a.a)}function cbe(n){return oe(n.b!=0),Xo(n,n.c.b)}function ube(n){return!n.c&&(n.c=new W3),n.c}function y4(n){var e;return e=new Z,b$(e,n),e}function obe(n){var e;return e=new ni,b$(e,n),e}function ASn(n){var e;return e=new GG,A$(e,n),e}function F7(n){var e;return e=new Ct,A$(e,n),e}function u(n,e){return F6(n==null||Tx(n,e)),n}function sbe(n,e,t){TPn.call(this,e,t),this.a=n}function SSn(n,e){this.c=n,this.b=e,this.a=!1}function PSn(){this.a=";,;",this.b="",this.c=""}function ISn(n,e,t){this.b=n,_Mn.call(this,e,t)}function UV(n,e,t){this.c=n,oC.call(this,e,t)}function GV(n,e,t){d4.call(this,n,e),this.b=t}function zV(n,e,t){Bnn(t,0,n,e,t.length,!1)}function Lh(n,e,t,i,r){n.b=e,n.c=t,n.d=i,n.a=r}function XV(n,e,t,i,r){n.d=e,n.c=t,n.a=i,n.b=r}function fbe(n,e){e&&(n.b=e,n.a=(z1(e),e.a))}function B7(n,e){if(!n)throw M(new Gn(e))}function Mp(n,e){if(!n)throw M(new Or(e))}function VV(n,e){if(!n)throw M(new Bjn(e))}function hbe(n,e){return ZE(),jc(n.d.p,e.d.p)}function lbe(n,e){return kl(),bt(n.e.b,e.e.b)}function abe(n,e){return kl(),bt(n.e.a,e.e.a)}function dbe(n,e){return jc(GSn(n.d),GSn(e.d))}function WC(n,e){return e&&vM(n,e.d)?e:null}function bbe(n,e){return e==(tn(),Wn)?n.c:n.d}function WV(n){return Q1(dwe(Vr(n)?ds(n):n))}function wbe(n){return new V(n.c+n.b,n.d+n.a)}function OSn(n){return n!=null&&!lx(n,N9,$9)}function gbe(n,e){return(fBn(n)<<4|fBn(e))&ui}function DSn(n,e,t,i,r){n.c=e,n.d=t,n.b=i,n.a=r}function JV(n){var e,t;e=n.b,t=n.c,n.b=t,n.c=e}function QV(n){var e,t;t=n.d,e=n.a,n.d=e,n.a=t}function pbe(n,e){var t;return t=n.c,PQ(n,e),t}function YV(n,e){return e<0?n.g=-1:n.g=e,n}function JC(n,e){return Mme(n),n.a*=e,n.b*=e,n}function LSn(n,e,t){A$n.call(this,e,t),this.d=n}function R7(n,e,t){pX.call(this,n,e),this.c=t}function QC(n,e,t){pX.call(this,n,e),this.c=t}function ZV(n){OV(),ME.call(this),this.ci(n)}function NSn(){$4(),Bwe.call(this,(R1(),Ss))}function $Sn(n){return nt(),new Nh(0,n)}function xSn(){xSn=F,AU=(Dn(),new nD(IK))}function YC(){YC=F,new hZ((bD(),HK),(dD(),_K))}function FSn(){FSn=F,pun=K(Gi,J,17,256,0,1)}function BSn(){this.b=$(R(rn((qs(),y_))))}function GL(n){this.b=n,this.a=Wa(this.b.a).Od()}function RSn(n,e){this.b=n,this.a=e,GO.call(this)}function KSn(n,e){this.a=n,this.b=e,GO.call(this)}function _Sn(n,e,t){this.a=n,gg.call(this,e,t)}function HSn(n,e,t){this.a=n,gg.call(this,e,t)}function j4(n,e,t){var i;i=new qb(t),df(n,e,i)}function nW(n,e,t){var i;return i=n[e],n[e]=t,i}function ZC(n){var e;return e=n.slice(),o$(e,n)}function nM(n){var e;return e=n.n,n.a.b+e.d+e.a}function qSn(n){var e;return e=n.n,n.e.b+e.d+e.a}function eW(n){var e;return e=n.n,n.e.a+e.b+e.c}function tW(n){n.a.b=n.b,n.b.a=n.a,n.a=n.b=null}function xe(n,e){return xt(n,e,n.c.b,n.c),!0}function mbe(n){return n.a?n.a:vN(n)}function vbe(n){return Lp(),Kh(n)==At(ia(n))}function kbe(n){return Lp(),ia(n)==At(Kh(n))}function d0(n,e){return O5(n,new d4(e.a,e.b))}function ybe(n,e){return yM(),Nx(n,e),new lIn(n,e)}function jbe(n,e){return n.c=e)throw M(new YG)}function _b(n,e){return $k(n,(Jn(e),new a9n(e)))}function Ap(n,e){return $k(n,(Jn(e),new d9n(e)))}function SPn(n,e,t){return VLe(n,u(e,12),u(t,12))}function PPn(n){return Ou(),u(n,12).g.c.length!=0}function IPn(n){return Ou(),u(n,12).e.c.length!=0}function uwe(n,e){return Hp(),bt(e.a.o.a,n.a.o.a)}function owe(n,e){e.Bb&kc&&!n.a.o&&(n.a.o=e)}function swe(n,e){e.Ug("General 'Rotator",1),jDe(n)}function fwe(n,e,t){e.qf(t,$(R(ee(n.b,t)))*n.a)}function OPn(n,e,t){return Xg(),W4(n,e)&&W4(n,t)}function _6(n){return zu(),!n.Hc(Fl)&&!n.Hc(Pa)}function hwe(n){return n.e?qJ(n.e):null}function H6(n){return Vr(n)?""+n:$qn(n)}function yW(n){var e;for(e=n;e.f;)e=e.f;return e}function lwe(n,e,t){return $t(e,0,oW(e[0],t[0])),e}function Vl(n,e,t,i){var r;r=n.i,r.i=e,r.a=t,r.b=i}function q(n,e,t,i){ti.call(this,n,e,t),this.b=i}function Ci(n,e,t,i,r){c$.call(this,n,e,t,i,r,-1)}function q6(n,e,t,i,r){ok.call(this,n,e,t,i,r,-1)}function bM(n,e,t,i){R7.call(this,n,e,t),this.b=i}function DPn(n){PMn.call(this,n,!1),this.a=!1}function LPn(){sMn.call(this,"LOOKAHEAD_LAYOUT",1)}function NPn(n){this.b=n,kp.call(this,n),RTn(this)}function $Pn(n){this.b=n,A7.call(this,n),KTn(this)}function Hb(n,e,t){this.a=n,jp.call(this,e,t,5,6)}function jW(n,e,t,i){this.b=n,ti.call(this,e,t,i)}function xPn(n,e){this.b=n,H8n.call(this,n.b),this.a=e}function FPn(n){this.a=kRn(n.a),this.b=new _u(n.b)}function EW(n,e){m0(),Hhe.call(this,n,FT(new Ku(e)))}function wM(n,e){return nt(),new BW(n,e,0)}function rN(n,e){return nt(),new BW(6,n,e)}function _i(n,e){for(Jn(e);n.Ob();)e.Cd(n.Pb())}function Zc(n,e){return Ai(e)?AN(n,e):!!wr(n.f,e)}function cN(n,e){return e.Vh()?na(n.b,u(e,54)):e}function awe(n,e){return An(n.substr(0,e.length),e)}function $h(n){return new te(new UX(n.a.length,n.a))}function gM(n){return new V(n.c+n.b/2,n.d+n.a/2)}function dwe(n){return Yc(~n.l&ro,~n.m&ro,~n.h&Il)}function uN(n){return typeof n===vy||typeof n===eB}function Hu(n){n.f=new iTn(n),n.i=new rTn(n),++n.g}function BPn(n){if(!n)throw M(new nc);return n.d}function Sp(n){var e;return e=a5(n),oe(e!=null),e}function bwe(n){var e;return e=I5e(n),oe(e!=null),e}function C4(n,e){var t;return t=n.a.gc(),BJ(e,t),t-e}function fi(n,e){var t;return t=n.a.zc(e,n),t==null}function _7(n,e){return n.a.zc(e,(_n(),wa))==null}function CW(n){return new Tn(null,vwe(n,n.length))}function MW(n,e,t){return cGn(n,u(e,42),u(t,176))}function Pp(n,e,t){return Ks(n.a,e),nW(n.b,e.g,t)}function wwe(n,e,t){E4(t,n.a.c.length),Go(n.a,t,e)}function B(n,e,t,i){xFn(e,t,n.length),gwe(n,e,t,i)}function gwe(n,e,t,i){var r;for(r=e;r0?y.Math.log(n/e):-100}function KPn(n,e){return Ec(n,e)<0?-1:Ec(n,e)>0?1:0}function H7(n,e){OTn(n,O(e,160)?e:u(e,2036).Rl())}function PW(n,e){if(n==null)throw M(new sp(e))}function vwe(n,e){return yme(e,n.length),new XSn(n,e)}function IW(n,e){return e?Bi(n,e):!1}function kwe(){return RE(),S(T(uQn,1),G,549,0,[GK])}function G6(n){return n.e==0?n:new Qa(-n.e,n.d,n.a)}function ywe(n,e){return bt(n.c.c+n.c.b,e.c.c+e.c.b)}function q7(n,e){xt(n.d,e,n.b.b,n.b),++n.a,n.c=null}function _Pn(n,e){return n.c?_Pn(n.c,e):nn(n.b,e),n}function jwe(n,e,t){var i;return i=Jb(n,e),qN(n,e,t),i}function HPn(n,e,t){var i;for(i=0;i=n.g}function $t(n,e,t){return dae(t==null||oPe(n,t)),n[e]=t}function $W(n,e){return zn(e,n.length+1),n.substr(e)}function gN(n,e){for(Jn(e);n.c=n?new Dz:Gme(n-1)}function Hi(n){return!n.a&&n.c?n.c.b:n.a}function KW(n){return O(n,616)?n:new oOn(n)}function z1(n){n.c?z1(n.c):(ea(n),n.d=!0)}function V6(n){n.c?n.c.$e():(n.d=!0,fTe(n))}function sIn(n){n.b=!1,n.c=!1,n.d=!1,n.a=!1}function fIn(n){var e,t;return e=n.c.i.c,t=n.d.i.c,e==t}function _we(n,e){var t;t=n.Ih(e),t>=0?n.ki(t):Pnn(n,e)}function hIn(n,e){n.c<0||n.b.b0;)n=n<<1|(n<0?1:0);return n}function mIn(n,e){var t;return t=new Lc(n),Bn(e.c,t),t}function vIn(n,e){n.u.Hc((zu(),Fl))&&zEe(n,e),h4e(n,e)}function mc(n,e){return x(n)===x(e)||n!=null&&rt(n,e)}function Cr(n,e){return JL(n.a,e)?n.b[u(e,22).g]:null}function nge(){return YE(),S(T(oon,1),G,489,0,[b_])}function ege(){return eC(),S(T($1n,1),G,490,0,[Bq])}function tge(){return tC(),S(T(zce,1),G,558,0,[Rq])}function ige(){return f6(),S(T(tan,1),G,539,0,[Hj])}function jM(n){return!n.n&&(n.n=new q(Ar,n,1,7)),n.n}function mN(n){return!n.c&&(n.c=new q(Qu,n,9,9)),n.c}function UW(n){return!n.c&&(n.c=new Nn(he,n,5,8)),n.c}function rge(n){return!n.b&&(n.b=new Nn(he,n,4,7)),n.b}function U7(n){return n.j.c.length=0,zW(n.c),xae(n.a),n}function P4(n){return n.e==rv&&jfe(n,Y8e(n.g,n.b)),n.e}function G7(n){return n.f==rv&&Cfe(n,q7e(n.g,n.b)),n.f}function Ve(n,e,t,i){return Hxn(n,e,t,!1),BT(n,i),n}function kIn(n,e){this.b=n,oN.call(this,n,e),RTn(this)}function yIn(n,e){this.b=n,SV.call(this,n,e),KTn(this)}function W6(n){this.d=n,this.a=this.d.b,this.b=this.d.c}function GW(n,e){this.b=n,this.c=e,this.a=new dp(this.b)}function Xi(n,e){return zn(e,n.length),n.charCodeAt(e)}function cge(n,e){DY(n,$(yl(e,"x")),$(yl(e,"y")))}function uge(n,e){DY(n,$(yl(e,"x")),$(yl(e,"y")))}function ut(n,e){return ea(n),new Tn(n,new tQ(e,n.a))}function _r(n,e){return ea(n),new Tn(n,new _J(e,n.a))}function Ub(n,e){return ea(n),new uV(n,new ILn(e,n.a))}function EM(n,e){return ea(n),new oV(n,new OLn(e,n.a))}function oge(n,e){return new GIn(u(Se(n),50),u(Se(e),50))}function sge(n,e){return bt(n.d.c+n.d.b/2,e.d.c+e.d.b/2)}function jIn(n,e,t){t.a?tu(n,e.b-n.f/2):eu(n,e.a-n.g/2)}function fge(n,e){return bt(n.g.c+n.g.b/2,e.g.c+e.g.b/2)}function hge(n,e){return $z(),bt((Jn(n),n),(Jn(e),e))}function lge(n){return n!=null&&r7(jO,n.toLowerCase())}function zW(n){var e;for(e=n.Kc();e.Ob();)e.Pb(),e.Qb()}function Tg(n){var e;return e=n.b,!e&&(n.b=e=new N8n(n)),e}function vN(n){var e;return e=Wme(n),e||null}function EIn(n,e){var t,i;return t=n/e,i=wi(t),t>i&&++i,i}function age(n,e,t){var i;i=u(n.d.Kb(t),159),i&&i.Nb(e)}function dge(n,e,t){wIe(n.a,t),zve(t),xCe(n.b,t),$Ie(e,t)}function CM(n,e,t,i){this.a=n,this.c=e,this.b=t,this.d=i}function XW(n,e,t,i){this.c=n,this.b=e,this.a=t,this.d=i}function CIn(n,e,t,i){this.c=n,this.b=e,this.d=t,this.a=i}function Ho(n,e,t,i){this.c=n,this.d=e,this.b=t,this.a=i}function MIn(n,e,t,i){this.a=n,this.d=e,this.c=t,this.b=i}function kN(n,e,t,i){this.a=n,this.e=e,this.d=t,this.c=i}function TIn(n,e,t,i){this.a=n,this.c=e,this.d=t,this.b=i}function yN(n,e,t){this.a=ktn,this.d=n,this.b=e,this.c=t}function Op(n,e,t,i){je.call(this,n,e),this.a=t,this.b=i}function AIn(n,e){this.d=(Jn(n),n),this.a=16449,this.c=e}function SIn(n){this.a=new Z,this.e=K(ye,J,53,n,0,2)}function bge(n){n.Ug("No crossing minimization",1),n.Vg()}function PIn(){ec.call(this,"There is no more element.")}function IIn(n,e,t,i){this.a=n,this.b=e,this.c=t,this.d=i}function OIn(n,e,t,i){this.a=n,this.b=e,this.c=t,this.d=i}function Za(n,e,t,i){this.e=n,this.a=e,this.c=t,this.d=i}function DIn(n,e,t,i){this.a=n,this.c=e,this.d=t,this.b=i}function LIn(n,e,t,i){Ko(),DLn.call(this,e,t,i),this.a=n}function NIn(n,e,t,i){Ko(),DLn.call(this,e,t,i),this.a=n}function jN(n,e,t){var i,r;return i=utn(n),r=e.ti(t,i),r}function al(n){var e,t;return t=(e=new Jd,e),K4(t,n),t}function EN(n){var e,t;return t=(e=new Jd,e),fnn(t,n),t}function wge(n,e){var t;return t=ee(n.f,e),HQ(e,t),null}function $In(n){return!n.b&&(n.b=new q(Vt,n,12,3)),n.b}function xIn(n){return F6(n==null||uN(n)&&n.Tm!==J2),n}function MM(n){return n.n&&(n.e!==Fzn&&n.je(),n.j=null),n}function I4(n){if(eo(n.d),n.d.d!=n.c)throw M(new Bo)}function VW(n){return oe(n.b0&&bKn(this)}function FIn(n,e){this.a=n,bae.call(this,n,u(n.d,15).fd(e))}function gge(n,e){return bt(Su(n)*ao(n),Su(e)*ao(e))}function pge(n,e){return bt(Su(n)*ao(n),Su(e)*ao(e))}function mge(n){return _0(n)&&on(un(z(n,(cn(),Nd))))}function vge(n,e){return Pn(n,u(v(e,(cn(),Cv)),17),e)}function kge(n,e){return u(v(n,(W(),T3)),15).Fc(e),e}function WW(n,e){return n.b=e.b,n.c=e.c,n.d=e.d,n.a=e.a,n}function BIn(n,e,t,i){this.b=n,this.c=i,IC.call(this,e,t)}function yge(n,e,t){n.i=0,n.e=0,e!=t&&yFn(n,e,t)}function jge(n,e,t){n.i=0,n.e=0,e!=t&&jFn(n,e,t)}function Ege(n,e,t){return s6(),J5e(u(ee(n.e,e),529),t)}function Dp(n){var e;return e=n.f,e||(n.f=new h4(n,n.c))}function RIn(n,e){return xg(n.j,e.s,e.c)+xg(e.e,n.s,n.c)}function KIn(n,e){n.e&&!n.e.a&&(Ayn(n.e,e),KIn(n.e,e))}function _In(n,e){n.d&&!n.d.a&&(Ayn(n.d,e),_In(n.d,e))}function Cge(n,e){return-bt(Su(n)*ao(n),Su(e)*ao(e))}function Mge(n){return u(n.ld(),149).Pg()+":"+Jr(n.md())}function HIn(){tF(this,new oG),this.wb=(G1(),Hn),o4()}function qIn(n){this.b=new Z,hi(this.b,this.b),this.a=n}function JW(n,e){new Ct,this.a=new Mu,this.b=n,this.c=e}function j0(){j0=F,Pun=new FU,ZK=new FU,Iun=new D0n}function Dn(){Dn=F,sr=new A0n,Wh=new P0n,hP=new I0n}function QW(){QW=F,RQn=new nbn,_Qn=new aW,KQn=new ebn}function Lp(){Lp=F,mP=new Z,m_=new de,p_=new Z}function TM(n,e){if(n==null)throw M(new sp(e));return n}function AM(n){return!n.a&&(n.a=new q(Qe,n,10,11)),n.a}function ft(n){return!n.q&&(n.q=new q(As,n,11,10)),n.q}function _(n){return!n.s&&(n.s=new q(ku,n,21,17)),n.s}function Tge(n){return Se(n),IRn(new te(re(n.a.Kc(),new En)))}function Age(n,e){return wo(n),wo(e),Fjn(u(n,22),u(e,22))}function nd(n,e,t){var i,r;i=IV(t),r=new AE(i),df(n,e,r)}function MN(n,e,t,i,r,c){ok.call(this,n,e,t,i,r,c?-2:-1)}function UIn(n,e,t,i){pX.call(this,e,t),this.b=n,this.a=i}function GIn(n,e){Vfe.call(this,new iN(n)),this.a=n,this.b=e}function YW(n){this.b=n,this.c=n,n.e=null,n.c=null,this.a=1}function Sge(n){xs();var e;e=u(n.g,10),e.n.a=n.d.c+e.d.b}function O4(){O4=F;var n,e;e=!$8e(),n=new V3,VK=e?new og:n}function TN(n){return Dn(),O(n,59)?new jD(n):new BC(n)}function SM(n){return O(n,16)?new B6(u(n,16)):obe(n.Kc())}function Pge(n){return new HTn(n,n.e.Rd().gc()*n.c.Rd().gc())}function Ige(n){return new qTn(n,n.e.Rd().gc()*n.c.Rd().gc())}function ZW(n){return n&&n.hashCode?n.hashCode():l0(n)}function AN(n,e){return e==null?!!wr(n.f,null):zbe(n.i,e)}function Oge(n,e){var t;return t=$X(n.a,e),t&&(e.d=null),t}function zIn(n,e,t){return n.f?n.f.ef(e,t):!1}function z7(n,e,t,i){$t(n.c[e.g],t.g,i),$t(n.c[t.g],e.g,i)}function SN(n,e,t,i){$t(n.c[e.g],e.g,t),$t(n.b[e.g],e.g,i)}function Dge(n,e,t){return $(R(t.a))<=n&&$(R(t.b))>=e}function XIn(n,e){this.g=n,this.d=S(T(Qh,1),b1,10,0,[e])}function VIn(n){this.c=n,this.b=new Ul(u(Se(new tbn),50))}function WIn(n){this.c=n,this.b=new Ul(u(Se(new ewn),50))}function JIn(n){this.b=n,this.a=new Ul(u(Se(new Nbn),50))}function QIn(){this.b=new ni,this.d=new Ct,this.e=new ZG}function nJ(){this.c=new Li,this.d=new Li,this.e=new Li}function E0(){this.a=new Mu,this.b=(Co(3,mw),new Gc(3))}function Wl(n,e){this.e=n,this.a=ki,this.b=Qqn(e),this.c=e}function PM(n){this.c=n.c,this.d=n.d,this.b=n.b,this.a=n.a}function YIn(n,e,t,i,r,c){this.a=n,k$.call(this,e,t,i,r,c)}function ZIn(n,e,t,i,r,c){this.a=n,k$.call(this,e,t,i,r,c)}function X1(n,e,t,i,r,c,s){return new GN(n.e,e,t,i,r,c,s)}function Lge(n,e,t){return t>=0&&An(n.substr(t,e.length),e)}function nOn(n,e){return O(e,149)&&An(n.b,u(e,149).Pg())}function Nge(n,e){return n.a?e.Gh().Kc():u(e.Gh(),71).Ii()}function eOn(n,e){var t;return t=n.b.Qc(e),WDn(t,n.b.gc()),t}function X7(n,e){if(n==null)throw M(new sp(e));return n}function Hr(n){return n.u||(Zu(n),n.u=new NAn(n,n)),n.u}function PN(n){this.a=(Dn(),O(n,59)?new jD(n):new BC(n))}function au(n){var e;return e=u(Un(n,16),29),e||n.ii()}function IM(n,e){var t;return t=za(n.Rm),e==null?t:t+": "+e}function qo(n,e,t){return Fi(e,t,n.length),n.substr(e,t-e)}function tOn(n,e){qC.call(this),lQ(this),this.a=n,this.c=e}function $ge(n){n&&IM(n,n.ie())}function xge(n){HE(),y.setTimeout(function(){throw n},0)}function Fge(){return YT(),S(T(Bun,1),G,436,0,[o_,Fun])}function Bge(){return cT(),S(T(Kun,1),G,435,0,[Run,s_])}function Rge(){return uT(),S(T(bon,1),G,432,0,[v_,vP])}function Kge(){return V4(),S(T(KZn,1),G,517,0,[dj,L_])}function _ge(){return KM(),S(T(Qsn,1),G,429,0,[fH,Jsn])}function Hge(){return pk(),S(T($sn,1),G,428,0,[WP,Nsn])}function qge(){return QM(),S(T(Asn,1),G,431,0,[Tsn,V_])}function Uge(){return wk(),S(T(qhn,1),G,430,0,[UH,GH])}function Gge(){return n5(),S(T(Oie,1),G,531,0,[r9,i9])}function zge(){return yT(),S(T(Rln,1),G,501,0,[RI,D2])}function Xge(){return sh(),S(T(Bie,1),G,523,0,[mb,y1])}function Vge(){return Sf(),S(T(Kie,1),G,522,0,[Rd,zf])}function Wge(){return lf(),S(T(ere,1),G,528,0,[zw,ja])}function Jge(){return hk(),S(T(Bsn,1),G,488,0,[Fsn,QP])}function Qge(){return GM(),S(T(S1n,1),G,491,0,[$q,A1n])}function Yge(){return N$(),S(T(N1n,1),G,492,0,[D1n,L1n])}function Zge(){return FM(),S(T(Bln,1),G,433,0,[dq,Fln])}function n2e(){return ZM(),S(T(_ln,1),G,434,0,[Kln,vq])}function e2e(){return M0(),S(T(sre,1),G,465,0,[Ea,P2])}function t2e(){return ck(),S(T(x1n,1),G,438,0,[Kq,JI])}function i2e(){return Ak(),S(T(ran,1),G,437,0,[YI,ian])}function r2e(){return RL(),S(T(dO,1),G,347,0,[vdn,kdn])}function OM(n,e,t,i){return t>=0?n.Uh(e,t,i):n.Ch(null,t,i)}function V7(n){return n.b.b==0?n.a.sf():UL(n.b)}function c2e(n){if(n.p!=5)throw M(new Cu);return Ae(n.f)}function u2e(n){if(n.p!=5)throw M(new Cu);return Ae(n.k)}function eJ(n){return x(n.a)===x((D$(),CU))&&rOe(n),n.a}function o2e(n,e){n.b=e,n.c>0&&n.b>0&&(n.g=cM(n.c,n.b,n.a))}function s2e(n,e){n.c=e,n.c>0&&n.b>0&&(n.g=cM(n.c,n.b,n.a))}function iOn(n,e){ufe(this,new V(n.a,n.b)),ofe(this,F7(e))}function C0(){Wfe.call(this,new ap(Qb(12))),KX(!0),this.a=2}function IN(n,e,t){nt(),Wd.call(this,n),this.b=e,this.a=t}function tJ(n,e,t){Ko(),LE.call(this,e),this.a=n,this.b=t}function rOn(n){var e;e=n.c.d.b,n.b=e,n.a=n.c.d,e.a=n.c.d.b=n}function f2e(n){return n.b==0?null:(oe(n.b!=0),Xo(n,n.a.a))}function Nc(n,e){return e==null?Kr(wr(n.f,null)):d6(n.i,e)}function cOn(n,e,t,i,r){return new rF(n,(B4(),i_),e,t,i,r)}function DM(n,e){return zDn(e),Lme(n,K(ye,Ke,28,e,15,1),e)}function LM(n,e){return TM(n,"set1"),TM(e,"set2"),new VEn(n,e)}function h2e(n,e){var t=XK[n.charCodeAt(0)];return t??n}function uOn(n,e){var t,i;return t=e,i=new DO,LGn(n,t,i),i.d}function ON(n,e,t,i){var r;r=new FAn,e.a[t.g]=r,Pp(n.b,i,r)}function l2e(n,e){var t;return t=Ime(n.f,e),tt(HC(t),n.f.d)}function W7(n){var e;_me(n.a),dTn(n.a),e=new IE(n.a),HY(e)}function a2e(n,e){_qn(n,!0),nu(n.e.Rf(),new NV(n,!0,e))}function d2e(n,e){return Lp(),n==At(Kh(e))||n==At(ia(e))}function b2e(n,e){return kl(),u(v(e,(lc(),Sh)),17).a==n}function wi(n){return Math.max(Math.min(n,et),-2147483648)|0}function oOn(n){this.a=u(Se(n),277),this.b=(Dn(),new XX(n))}function sOn(n,e,t){this.i=new Z,this.b=n,this.g=e,this.a=t}function iJ(n,e,t){this.a=new Z,this.e=n,this.f=e,this.c=t}function NM(n,e,t){this.c=new Z,this.e=n,this.f=e,this.b=t}function fOn(n){qC.call(this),lQ(this),this.a=n,this.c=!0}function w2e(n){function e(){}return e.prototype=n||{},new e}function g2e(n){if(n.Ae())return null;var e=n.n;return rP[e]}function J7(n){return n.Db>>16!=3?null:u(n.Cb,27)}function Af(n){return n.Db>>16!=9?null:u(n.Cb,27)}function hOn(n){return n.Db>>16!=6?null:u(n.Cb,74)}function M0(){M0=F,Ea=new cX(s3,0),P2=new cX(f3,1)}function sh(){sh=F,mb=new tX(f3,0),y1=new tX(s3,1)}function Sf(){Sf=F,Rd=new iX(_B,0),zf=new iX("UP",1)}function lOn(){lOn=F,oQn=Ce((RE(),S(T(uQn,1),G,549,0,[GK])))}function aOn(n){var e;return e=new zE(Qb(n.length)),eY(e,n),e}function dOn(n,e){return n.b+=e.b,n.c+=e.c,n.d+=e.d,n.a+=e.a,n}function p2e(n,e){return Zxn(n,e)?(W$n(n),!0):!1}function dl(n,e){if(e==null)throw M(new ip);return F8e(n,e)}function Q7(n,e){var t;t=n.q.getHours(),n.q.setDate(e),G5(n,t)}function rJ(n,e,t){var i;i=n.Ih(e),i>=0?n.bi(i,t):ten(n,e,t)}function bOn(n,e){var t;return t=n.Ih(e),t>=0?n.Wh(t):hF(n,e)}function wOn(n,e){var t;for(Se(e),t=n.a;t;t=t.c)e.Yd(t.g,t.i)}function DN(n,e,t){var i;i=vFn(n,e,t),n.b=new ET(i.c.length)}function Ag(n,e,t){$M(),n&&Xe(yU,n,e),n&&Xe(hE,n,t)}function m2e(n,e){return VC(),_n(),u(e.a,17).a0}function cJ(n){var e;return e=n.d,e=n.bj(n.f),ve(n,e),e.Ob()}function gOn(n,e){var t;return t=new fW(e),_Kn(t,n),new _u(t)}function y2e(n){if(n.p!=0)throw M(new Cu);return M6(n.f,0)}function j2e(n){if(n.p!=0)throw M(new Cu);return M6(n.k,0)}function pOn(n){return n.Db>>16!=7?null:u(n.Cb,241)}function D4(n){return n.Db>>16!=6?null:u(n.Cb,241)}function mOn(n){return n.Db>>16!=7?null:u(n.Cb,167)}function At(n){return n.Db>>16!=11?null:u(n.Cb,27)}function Gb(n){return n.Db>>16!=17?null:u(n.Cb,29)}function vOn(n){return n.Db>>16!=3?null:u(n.Cb,155)}function uJ(n){var e;return ea(n),e=new ni,ut(n,new M9n(e))}function kOn(n,e){var t=n.a=n.a||[];return t[e]||(t[e]=n.ve(e))}function E2e(n,e){var t;t=n.q.getHours(),n.q.setMonth(e),G5(n,t)}function yOn(n,e){xC(this),this.f=e,this.g=n,MM(this),this.je()}function jOn(n,e){this.a=n,this.c=Ki(this.a),this.b=new PM(e)}function EOn(n,e,t){this.a=e,this.c=n,this.b=(Se(t),new _u(t))}function COn(n,e,t){this.a=e,this.c=n,this.b=(Se(t),new _u(t))}function MOn(n){this.a=n,this.b=K(Sie,J,2043,n.e.length,0,2)}function TOn(){this.a=new ih,this.e=new ni,this.g=0,this.i=0}function $M(){$M=F,yU=new de,hE=new de,ple(MQn,new wvn)}function AOn(){AOn=F,aie=Pu(new ii,(Vi(),zr),(tr(),bj))}function oJ(){oJ=F,die=Pu(new ii,(Vi(),zr),(tr(),bj))}function SOn(){SOn=F,wie=Pu(new ii,(Vi(),zr),(tr(),bj))}function POn(){POn=F,Lie=Re(new ii,(Vi(),zr),(tr(),x8))}function ko(){ko=F,xie=Re(new ii,(Vi(),zr),(tr(),x8))}function IOn(){IOn=F,Fie=Re(new ii,(Vi(),zr),(tr(),x8))}function NN(){NN=F,Hie=Re(new ii,(Vi(),zr),(tr(),x8))}function J6(n,e,t,i,r,c){return new ml(n.e,e,n.Lj(),t,i,r,c)}function Dr(n,e,t){return e==null?Vc(n.f,null,t):$0(n.i,e,t)}function Zi(n,e){n.c&&du(n.c.g,n),n.c=e,n.c&&nn(n.c.g,n)}function $i(n,e){n.c&&du(n.c.a,n),n.c=e,n.c&&nn(n.c.a,n)}function ic(n,e){n.i&&du(n.i.j,n),n.i=e,n.i&&nn(n.i.j,n)}function Ii(n,e){n.d&&du(n.d.e,n),n.d=e,n.d&&nn(n.d.e,n)}function $N(n,e){n.a&&du(n.a.k,n),n.a=e,n.a&&nn(n.a.k,n)}function xN(n,e){n.b&&du(n.b.f,n),n.b=e,n.b&&nn(n.b.f,n)}function OOn(n,e){$we(n,n.b,n.c),u(n.b.b,68),e&&u(e.b,68).b}function C2e(n,e){return bt(u(n.c,65).c.e.b,u(e.c,65).c.e.b)}function M2e(n,e){return bt(u(n.c,65).c.e.a,u(e.c,65).c.e.a)}function T2e(n){return Y$(),_n(),u(n.a,86).d.e!=0}function xM(n,e){O(n.Cb,184)&&(u(n.Cb,184).tb=null),zc(n,e)}function FN(n,e){O(n.Cb,90)&&hw(Zu(u(n.Cb,90)),4),zc(n,e)}function A2e(n,e){LY(n,e),O(n.Cb,90)&&hw(Zu(u(n.Cb,90)),2)}function S2e(n,e){var t,i;t=e.c,i=t!=null,i&&Ip(n,new qb(e.c))}function DOn(n){var e,t;return t=(o4(),e=new Jd,e),K4(t,n),t}function LOn(n){var e,t;return t=(o4(),e=new Jd,e),K4(t,n),t}function NOn(n){for(var e;;)if(e=n.Pb(),!n.Ob())return e}function P2e(n,e,t){return nn(n.a,(yM(),Nx(e,t),new i0(e,t))),n}function $c(n,e){return dr(),a$(e)?new eM(e,n):new j7(e,n)}function Y7(n){return dh(),Ec(n,0)>=0?ta(n):G6(ta(n1(n)))}function I2e(n){var e;return e=u(ZC(n.b),9),new _o(n.a,e,n.c)}function $On(n,e){var t;return t=u(tw(Dp(n.a),e),16),t?t.gc():0}function xOn(n,e,t){var i;oBn(e,t,n.c.length),i=t-e,Pz(n.c,e,i)}function Jl(n,e,t){oBn(e,t,n.gc()),this.c=n,this.a=e,this.b=t-e}function Np(n){this.c=new Ct,this.b=n.b,this.d=n.c,this.a=n.a}function BN(n){this.a=y.Math.cos(n),this.b=y.Math.sin(n)}function ed(n,e,t,i){this.c=n,this.d=i,$N(this,e),xN(this,t)}function sJ(n,e){Xfe.call(this,new ap(Qb(n))),Co(e,Ozn),this.a=e}function FOn(n,e,t){return new rF(n,(B4(),t_),null,!1,e,t)}function BOn(n,e,t){return new rF(n,(B4(),r_),e,t,null,!1)}function O2e(){return Gu(),S(T(xr,1),G,108,0,[xun,Yr,Aw])}function D2e(){return bu(),S(T(JQn,1),G,472,0,[vf,pa,zs])}function L2e(){return Uu(),S(T(VQn,1),G,471,0,[Mh,ga,Gs])}function N2e(){return bf(),S(T(Sw,1),G,237,0,[bc,Wc,wc])}function $2e(){return i5(),S(T(Pon,1),G,391,0,[E_,j_,C_])}function x2e(){return D0(),S(T(R_,1),G,372,0,[ub,ma,cb])}function F2e(){return u5(),S(T(Psn,1),G,322,0,[B8,pj,Ssn])}function B2e(){return bT(),S(T(Osn,1),G,351,0,[Isn,VP,W_])}function R2e(){return hd(),S(T(pne,1),G,460,0,[Y_,mv,p2])}function K2e(){return Z4(),S(T(sH,1),G,299,0,[uH,oH,mj])}function _2e(){return vl(),S(T(Mne,1),G,311,0,[vj,v2,E3])}function H2e(){return g5(),S(T(Lhn,1),G,390,0,[FH,Dhn,MI])}function q2e(){return gr(),S(T(cie,1),G,463,0,[n9,Vu,Jc])}function U2e(){return ST(),S(T(zhn,1),G,387,0,[Uhn,zH,Ghn])}function G2e(){return d5(),S(T(Xhn,1),G,349,0,[VH,XH,Ij])}function z2e(){return om(),S(T(Whn,1),G,350,0,[WH,Vhn,e9])}function X2e(){return dT(),S(T(Yhn,1),G,352,0,[Qhn,JH,Jhn])}function V2e(){return DT(),S(T(Zhn,1),G,388,0,[QH,Ov,Gw])}function W2e(){return O0(),S(T(Tie,1),G,464,0,[Oj,t9,PI])}function Pf(n){return cc(S(T(Ei,1),J,8,0,[n.i.n,n.n,n.a]))}function J2e(){return b5(),S(T(gln,1),G,392,0,[wln,nq,Lj])}function ROn(){ROn=F,Fre=Pu(new ii,(Qp(),u9),(q5(),uln))}function FM(){FM=F,dq=new uX("DFS",0),Fln=new uX("BFS",1)}function KOn(n,e,t){var i;i=new E3n,i.b=e,i.a=t,++e.b,nn(n.d,i)}function Q2e(n,e,t){var i;i=new rr(t.d),tt(i,n),DY(e,i.a,i.b)}function Y2e(n,e){LTn(n,Ae(vi(w0(e,24),YA)),Ae(vi(e,YA)))}function zb(n,e){if(n<0||n>e)throw M(new Ir(Ptn+n+Itn+e))}function Ln(n,e){if(n<0||n>=e)throw M(new Ir(Ptn+n+Itn+e))}function zn(n,e){if(n<0||n>=e)throw M(new gz(Ptn+n+Itn+e))}function In(n,e){this.b=(Jn(n),n),this.a=e&vw?e:e|64|wh}function fJ(n){var e;return ea(n),e=(j0(),j0(),ZK),fT(n,e)}function Z2e(n,e,t){var i;return i=V5(n,e,!1),i.b<=e&&i.a<=t}function npe(){return nT(),S(T(O1n,1),G,439,0,[xq,I1n,P1n])}function epe(){return _T(),S(T(a1n,1),G,394,0,[l1n,Oq,h1n])}function tpe(){return XT(),S(T(f1n,1),G,445,0,[Bj,qI,Mq])}function ipe(){return rA(),S(T(bce,1),G,456,0,[Tq,Sq,Aq])}function rpe(){return Ok(),S(T(Uln,1),G,393,0,[KI,Hln,qln])}function cpe(){return AT(),S(T(s1n,1),G,300,0,[Cq,o1n,u1n])}function upe(){return jl(),S(T(ldn,1),G,346,0,[uO,M1,M9])}function ope(){return Fk(),S(T(Fq,1),G,444,0,[XI,VI,WI])}function spe(){return Nf(),S(T(Zan,1),G,278,0,[Bv,Jw,Rv])}function fpe(){return Gp(),S(T(mdn,1),G,280,0,[pdn,Yw,aO])}function T0(n){return Se(n),O(n,16)?new _u(u(n,16)):y4(n.Kc())}function hJ(n,e){return n&&n.equals?n.equals(e):x(n)===x(e)}function vi(n,e){return Q1(ewe(Vr(n)?ds(n):n,Vr(e)?ds(e):e))}function hf(n,e){return Q1(twe(Vr(n)?ds(n):n,Vr(e)?ds(e):e))}function RN(n,e){return Q1(iwe(Vr(n)?ds(n):n,Vr(e)?ds(e):e))}function hpe(n,e){var t;return t=(Jn(n),n).g,rV(!!t),Jn(e),t(e)}function _On(n,e){var t,i;return i=C4(n,e),t=n.a.fd(i),new zEn(n,t)}function lpe(n){return n.Db>>16!=6?null:u(dF(n),241)}function ape(n){if(n.p!=2)throw M(new Cu);return Ae(n.f)&ui}function dpe(n){if(n.p!=2)throw M(new Cu);return Ae(n.k)&ui}function E(n){return oe(n.ai?1:0}function GOn(n,e){var t,i;return t=s$(e),i=t,u(ee(n.c,i),17).a}function KN(n,e,t){var i;i=n.d[e.p],n.d[e.p]=n.d[t.p],n.d[t.p]=i}function Cpe(n,e,t){var i;n.n&&e&&t&&(i=new uvn,nn(n.e,i))}function _N(n,e){if(fi(n.a,e),e.d)throw M(new ec(nXn));e.d=n}function dJ(n,e){this.a=new Z,this.d=new Z,this.f=n,this.c=e}function zOn(){this.c=new PTn,this.a=new $Ln,this.b=new Xyn,lCn()}function XOn(){qp(),this.b=new de,this.a=new de,this.c=new Z}function VOn(n,e,t){this.d=n,this.j=e,this.e=t,this.o=-1,this.p=3}function WOn(n,e,t){this.d=n,this.k=e,this.f=t,this.o=-1,this.p=5}function JOn(n,e,t,i,r,c){dQ.call(this,n,e,t,i,r),c&&(this.o=-2)}function QOn(n,e,t,i,r,c){bQ.call(this,n,e,t,i,r),c&&(this.o=-2)}function YOn(n,e,t,i,r,c){OJ.call(this,n,e,t,i,r),c&&(this.o=-2)}function ZOn(n,e,t,i,r,c){pQ.call(this,n,e,t,i,r),c&&(this.o=-2)}function nDn(n,e,t,i,r,c){DJ.call(this,n,e,t,i,r),c&&(this.o=-2)}function eDn(n,e,t,i,r,c){wQ.call(this,n,e,t,i,r),c&&(this.o=-2)}function tDn(n,e,t,i,r,c){gQ.call(this,n,e,t,i,r),c&&(this.o=-2)}function iDn(n,e,t,i,r,c){LJ.call(this,n,e,t,i,r),c&&(this.o=-2)}function rDn(n,e,t,i){LE.call(this,t),this.b=n,this.c=e,this.d=i}function cDn(n,e){this.f=n,this.a=($4(),MO),this.c=MO,this.b=e}function uDn(n,e){this.g=n,this.d=($4(),TO),this.a=TO,this.b=e}function bJ(n,e){!n.c&&(n.c=new Rt(n,0)),HA(n.c,(at(),F9),e)}function Mpe(n,e){return oMe(n,e,O(e,102)&&(u(e,19).Bb&hr)!=0)}function Tpe(n,e){return KPn(vc(n.q.getTime()),vc(e.q.getTime()))}function oDn(n){return XL(n.e.Rd().gc()*n.c.Rd().gc(),16,new O8n(n))}function Ape(n){return!!n.u&&Sc(n.u.a).i!=0&&!(n.n&&Ix(n.n))}function Spe(n){return!!n.a&&no(n.a.a).i!=0&&!(n.b&&Ox(n.b))}function wJ(n,e){return e==0?!!n.o&&n.o.f!=0:Cx(n,e)}function Ppe(n,e,t){var i;return i=u(n.Zb().xc(e),16),!!i&&i.Hc(t)}function sDn(n,e,t){var i;return i=u(n.Zb().xc(e),16),!!i&&i.Mc(t)}function fDn(n,e){var t;return t=1-e,n.a[t]=jT(n.a[t],t),jT(n,e)}function hDn(n,e){var t,i;return i=vi(n,mr),t=Fs(e,32),hf(t,i)}function lDn(n,e,t){var i;i=(Se(n),new _u(n)),O7e(new EOn(i,e,t))}function Z7(n,e,t){var i;i=(Se(n),new _u(n)),D7e(new COn(i,e,t))}function fc(n,e,t,i,r,c){return Hxn(n,e,t,c),CY(n,i),MY(n,r),n}function aDn(n,e,t,i){return n.a+=""+qo(e==null?gu:Jr(e),t,i),n}function xi(n,e){this.a=n,Xv.call(this,n),zb(e,n.gc()),this.b=e}function dDn(n){this.a=K(ki,Fn,1,QQ(y.Math.max(8,n))<<1,5,1)}function nk(n){return u(xf(n,K(Qh,b1,10,n.c.length,0,1)),199)}function fh(n){return u(xf(n,K(O_,rR,18,n.c.length,0,1)),483)}function bDn(n){return n.a?n.e.length==0?n.a.a:n.a.a+(""+n.e):n.c}function Q6(n){for(;n.d>0&&n.a[--n.d]==0;);n.a[n.d++]==0&&(n.e=0)}function wDn(n){return oe(n.b.b!=n.d.a),n.c=n.b=n.b.b,--n.a,n.c.c}function Ipe(n,e,t){n.a=e,n.c=t,n.b.a.$b(),vo(n.d),Pb(n.e.a.c,0)}function gDn(n,e){var t;n.e=new uz,t=aw(e),Yt(t,n.c),Iqn(n,t,0)}function ri(n,e,t,i){var r;r=new nG,r.a=e,r.b=t,r.c=i,xe(n.a,r)}function Q(n,e,t,i){var r;r=new nG,r.a=e,r.b=t,r.c=i,xe(n.b,r)}function pDn(n,e,t){if(n<0||et)throw M(new Ir(qje(n,e,t)))}function ek(n,e){if(n<0||n>=e)throw M(new Ir(kEe(n,e)));return n}function Ope(n){if(!("stack"in n))try{throw n}catch{}return n}function Sg(n){return s6(),O(n.g,10)?u(n.g,10):null}function Dpe(n){return Tg(n).dc()?!1:(e1e(n,new Pr),!0)}function id(n){var e;return Vr(n)?(e=n,e==-0?0:e):X4e(n)}function mDn(n,e){return O(e,44)?xx(n.a,u(e,44)):!1}function vDn(n,e){return O(e,44)?xx(n.a,u(e,44)):!1}function kDn(n,e){return O(e,44)?xx(n.a,u(e,44)):!1}function gJ(n){var e;return z1(n),e=new L0n,hg(n.a,new j9n(e)),e}function pJ(){var n,e,t;return e=(t=(n=new Jd,n),t),nn(n0n,e),e}function BM(n){var e;return z1(n),e=new N0n,hg(n.a,new E9n(e)),e}function Lpe(n,e){return n.a<=n.b?(e.Dd(n.a++),!0):!1}function yDn(n){P$.call(this,n,(B4(),e_),null,!1,null,!1)}function jDn(){jDn=F,SYn=Ce((YE(),S(T(oon,1),G,489,0,[b_])))}function EDn(){EDn=F,eln=wIn(Y(1),Y(4)),nln=wIn(Y(1),Y(2))}function Npe(n,e){return new _L(e,N6(Ki(e.e),n,n),(_n(),!0))}function RM(n){return new Gc((Co(n,cB),oT(nr(nr(5,n),n/10|0))))}function $pe(n){return XL(n.e.Rd().gc()*n.c.Rd().gc(),273,new I8n(n))}function CDn(n){return u(xf(n,K(FZn,DXn,12,n.c.length,0,1)),2042)}function xpe(n){return ko(),!fr(n)&&!(!fr(n)&&n.c.i.c==n.d.i.c)}function Fpe(n,e){return _p(),u(v(e,(lc(),I2)),17).a>=n.gc()}function Y6(n,e){vLe(e,n),JV(n.d),JV(u(v(n,(cn(),mI)),214))}function HN(n,e){kLe(e,n),QV(n.d),QV(u(v(n,(cn(),mI)),214))}function Bpe(n,e,t){n.d&&du(n.d.e,n),n.d=e,n.d&&b0(n.d.e,t,n)}function Rpe(n,e,t){return t.f.c.length>0?MW(n.a,e,t):MW(n.b,e,t)}function Kpe(n,e,t){var i;i=i9e();try{return Aae(n,e,t)}finally{D3e(i)}}function A0(n,e){var t,i;return t=dl(n,e),i=null,t&&(i=t.pe()),i}function Z6(n,e){var t,i;return t=dl(n,e),i=null,t&&(i=t.se()),i}function L4(n,e){var t,i;return t=Jb(n,e),i=null,t&&(i=t.se()),i}function bl(n,e){var t,i;return t=dl(n,e),i=null,t&&(i=gnn(t)),i}function _pe(n,e,t){var i;return i=wm(t),FA(n.g,i,e),FA(n.i,e,t),e}function mJ(n,e,t){this.d=new $7n(this),this.e=n,this.i=e,this.f=t}function MDn(n,e,t,i){this.e=null,this.c=n,this.d=e,this.a=t,this.b=i}function TDn(n,e,t,i){ETn(this),this.c=n,this.e=e,this.f=t,this.b=i}function vJ(n,e,t,i){this.d=n,this.n=e,this.g=t,this.o=i,this.p=-1}function ADn(n,e,t,i){return O(t,59)?new iAn(n,e,t,i):new vW(n,e,t,i)}function N4(n){return O(n,16)?u(n,16).dc():!n.Kc().Ob()}function SDn(n){if(n.e.g!=n.b)throw M(new Bo);return!!n.c&&n.d>0}function be(n){return oe(n.b!=n.d.c),n.c=n.b,n.b=n.b.a,++n.a,n.c.c}function kJ(n,e){Jn(e),$t(n.a,n.c,e),n.c=n.c+1&n.a.length-1,JRn(n)}function V1(n,e){Jn(e),n.b=n.b-1&n.a.length-1,$t(n.a,n.b,e),JRn(n)}function PDn(n){var e;e=n.Gh(),this.a=O(e,71)?u(e,71).Ii():e.Kc()}function Hpe(n){return new In(Ame(u(n.a.md(),16).gc(),n.a.ld()),16)}function IDn(){IDn=F,Gce=Ce((eC(),S(T($1n,1),G,490,0,[Bq])))}function ODn(){ODn=F,Xce=Ce((tC(),S(T(zce,1),G,558,0,[Rq])))}function DDn(){DDn=F,lue=Ce((f6(),S(T(tan,1),G,539,0,[Hj])))}function qpe(){return dd(),S(T(Lon,1),G,389,0,[Ow,Don,P_,I_])}function Upe(){return B4(),S(T(lP,1),G,304,0,[e_,t_,i_,r_])}function Gpe(){return Vp(),S(T(EYn,1),G,332,0,[uj,cj,oj,sj])}function zpe(){return A5(),S(T(TYn,1),G,406,0,[fj,wP,gP,hj])}function Xpe(){return N0(),S(T(yYn,1),G,417,0,[rj,ij,a_,d_])}function Vpe(){return nm(),S(T(MZn,1),G,416,0,[rb,Iw,Pw,a2])}function Wpe(){return $f(),S(T(ene,1),G,421,0,[j3,lv,av,B_])}function Jpe(){return OT(),S(T(UZn,1),G,371,0,[F_,HP,qP,wj])}function Qpe(){return cw(),S(T(RH,1),G,203,0,[TI,BH,S2,A2])}function Ype(){return lh(),S(T(Hhn,1),G,284,0,[k1,_hn,HH,qH])}function Zpe(n){var e;return n.j==(tn(),ae)&&(e=mHn(n),Au(e,Zn))}function n3e(n,e){var t;t=e.a,Zi(t,e.c.d),Ii(t,e.d.d),nw(t.a,n.n)}function yJ(n,e){var t;return t=u(Lf(n.b,e),67),!t&&(t=new Ct),t}function xp(n){return s6(),O(n.g,154)?u(n.g,154):null}function e3e(n){n.a=null,n.e=null,Pb(n.b.c,0),Pb(n.f.c,0),n.c=null}function KM(){KM=F,fH=new Zz(qm,0),Jsn=new Zz("TOP_LEFT",1)}function n5(){n5=F,r9=new eX("UPPER",0),i9=new eX("LOWER",1)}function t3e(n,e){return vp(new V(e.e.a+e.f.a/2,e.e.b+e.f.b/2),n)}function LDn(n,e){return u(ho(_b(u(ot(n.k,e),15).Oc(),b2)),113)}function NDn(n,e){return u(ho(Ap(u(ot(n.k,e),15).Oc(),b2)),113)}function i3e(){return Qp(),S(T(rln,1),G,405,0,[LI,c9,u9,o9])}function r3e(){return w5(),S(T(xln,1),G,353,0,[aq,BI,lq,hq])}function c3e(){return sA(),S(T(c1n,1),G,354,0,[Eq,i1n,r1n,t1n])}function u3e(){return go(),S(T(I9,1),G,386,0,[rE,Gd,iE,Qw])}function o3e(){return To(),S(T(Yue,1),G,291,0,[nE,nl,Ta,Zj])}function s3e(){return El(),S(T(aU,1),G,223,0,[lU,Yj,Kv,F3])}function f3e(){return qT(),S(T(Cdn,1),G,320,0,[wU,ydn,Edn,jdn])}function h3e(){return LT(),S(T(woe,1),G,415,0,[gU,Tdn,Mdn,Adn])}function l3e(n){return $M(),Zc(yU,n)?u(ee(yU,n),341).Qg():null}function Uo(n,e,t){return e<0?hF(n,t):u(t,69).wk().Bk(n,n.hi(),e)}function a3e(n,e,t){var i;return i=wm(t),FA(n.j,i,e),Xe(n.k,e,t),e}function d3e(n,e,t){var i;return i=wm(t),FA(n.d,i,e),Xe(n.e,e,t),e}function $Dn(n){var e,t;return e=(B1(),t=new HO,t),n&&AA(e,n),e}function jJ(n){var e;return e=n.aj(n.i),n.i>0&&Ic(n.g,0,e,0,n.i),e}function xDn(n,e){var t;for(t=n.j.c.length;t>24}function w3e(n){if(n.p!=1)throw M(new Cu);return Ae(n.k)<<24>>24}function g3e(n){if(n.p!=7)throw M(new Cu);return Ae(n.k)<<16>>16}function p3e(n){if(n.p!=7)throw M(new Cu);return Ae(n.f)<<16>>16}function Pg(n,e){return e.e==0||n.e==0?O8:(Am(),vF(n,e))}function RDn(n,e){return x(e)===x(n)?"(this Map)":e==null?gu:Jr(e)}function m3e(n,e,t){return tN(R(Kr(wr(n.f,e))),R(Kr(wr(n.f,t))))}function v3e(n,e,t){var i;i=u(ee(n.g,t),60),nn(n.a.c,new bi(e,i))}function KDn(n,e,t){n.i=0,n.e=0,e!=t&&(jFn(n,e,t),yFn(n,e,t))}function k3e(n,e,t,i,r){var c;c=yMe(r,t,i),nn(e,dEe(r,c)),rje(n,r,e)}function EJ(n,e,t,i,r){this.i=n,this.a=e,this.e=t,this.j=i,this.f=r}function _Dn(n,e){nJ.call(this),this.a=n,this.b=e,nn(this.a.b,this)}function HDn(n){this.b=new de,this.c=new de,this.d=new de,this.a=n}function qDn(n,e){var t;return t=new lp,n.Gd(t),t.a+="..",e.Hd(t),t.a}function UDn(n,e){var t;for(t=e;t;)a0(n,t.i,t.j),t=At(t);return n}function GDn(n,e,t){var i;return i=wm(t),Xe(n.b,i,e),Xe(n.c,e,t),e}function wl(n){var e;for(e=0;n.Ob();)n.Pb(),e=nr(e,1);return oT(e)}function Fh(n,e){dr();var t;return t=u(n,69).vk(),kje(t,e),t.xl(e)}function y3e(n,e,t){if(t){var i=t.oe();n.a[e]=i(t)}else delete n.a[e]}function CJ(n,e){var t;t=n.q.getHours(),n.q.setFullYear(e+fa),G5(n,t)}function j3e(n,e){return u(e==null?Kr(wr(n.f,null)):d6(n.i,e),288)}function MJ(n,e){return n==(Vn(),zt)&&e==zt?4:n==zt||e==zt?8:32}function _M(n,e,t){return RA(n,e,t,O(e,102)&&(u(e,19).Bb&hr)!=0)}function E3e(n,e,t){return Om(n,e,t,O(e,102)&&(u(e,19).Bb&hr)!=0)}function C3e(n,e,t){return bMe(n,e,t,O(e,102)&&(u(e,19).Bb&hr)!=0)}function TJ(n){n.b!=n.c&&(n.a=K(ki,Fn,1,8,5,1),n.b=0,n.c=0)}function e5(n){return oe(n.a=0&&n.a[t]===e[t];t--);return t<0}function HM(n){var e;return n?new fW(n):(e=new ih,A$(e,n),e)}function O3e(n,e){var t,i;i=!1;do t=lFn(n,e),i=i|t;while(t);return i}function D3e(n){n&&rme((az(),sun)),--cP,n&&uP!=-1&&(Ele(uP),uP=-1)}function qM(n){nnn(),LTn(this,Ae(vi(w0(n,24),YA)),Ae(vi(n,YA)))}function JDn(){JDn=F,HQn=Ce((YT(),S(T(Bun,1),G,436,0,[o_,Fun])))}function QDn(){QDn=F,qQn=Ce((cT(),S(T(Kun,1),G,435,0,[Run,s_])))}function YDn(){YDn=F,GYn=Ce((uT(),S(T(bon,1),G,432,0,[v_,vP])))}function ZDn(){ZDn=F,_Zn=Ce((V4(),S(T(KZn,1),G,517,0,[dj,L_])))}function nLn(){nLn=F,Ane=Ce((KM(),S(T(Qsn,1),G,429,0,[fH,Jsn])))}function eLn(){eLn=F,gne=Ce((pk(),S(T($sn,1),G,428,0,[WP,Nsn])))}function tLn(){tLn=F,kne=Ce((hk(),S(T(Bsn,1),G,488,0,[Fsn,QP])))}function iLn(){iLn=F,rie=Ce((wk(),S(T(qhn,1),G,430,0,[UH,GH])))}function rLn(){rLn=F,Die=Ce((n5(),S(T(Oie,1),G,531,0,[r9,i9])))}function cLn(){cLn=F,ane=Ce((QM(),S(T(Asn,1),G,431,0,[Tsn,V_])))}function uLn(){uLn=F,xre=Ce((FM(),S(T(Bln,1),G,433,0,[dq,Fln])))}function oLn(){oLn=F,_re=Ce((yT(),S(T(Rln,1),G,501,0,[RI,D2])))}function sLn(){sLn=F,Rie=Ce((sh(),S(T(Bie,1),G,523,0,[mb,y1])))}function fLn(){fLn=F,_ie=Ce((Sf(),S(T(Kie,1),G,522,0,[Rd,zf])))}function hLn(){hLn=F,tre=Ce((lf(),S(T(ere,1),G,528,0,[zw,ja])))}function lLn(){lLn=F,fre=Ce((M0(),S(T(sre,1),G,465,0,[Ea,P2])))}function aLn(){aLn=F,Ure=Ce((ZM(),S(T(_ln,1),G,434,0,[Kln,vq])))}function dLn(){dLn=F,Rce=Ce((GM(),S(T(S1n,1),G,491,0,[$q,A1n])))}function bLn(){bLn=F,_ce=Ce((N$(),S(T(N1n,1),G,492,0,[D1n,L1n])))}function wLn(){wLn=F,Vce=Ce((ck(),S(T(x1n,1),G,438,0,[Kq,JI])))}function gLn(){gLn=F,aue=Ce((Ak(),S(T(ran,1),G,437,0,[YI,ian])))}function pLn(){pLn=F,aoe=Ce((RL(),S(T(dO,1),G,347,0,[vdn,kdn])))}function L3e(){return ci(),S(T(E9,1),G,88,0,[Wf,Xr,Br,Vf,us])}function N3e(){return tn(),S(T(lr,1),Mc,64,0,[sc,Xn,Zn,ae,Wn])}function $3e(n,e,t){return u(e==null?Vc(n.f,null,t):$0(n.i,e,t),288)}function x3e(n){return(n.k==(Vn(),zt)||n.k==Zt)&&kt(n,(W(),H8))}function XN(n){return n.c&&n.d?aJ(n.c)+"->"+aJ(n.d):"e_"+l0(n)}function qi(n,e){var t,i;for(Jn(e),i=n.Kc();i.Ob();)t=i.Pb(),e.Cd(t)}function F3e(n,e){var t;t=new op,nd(t,"x",e.a),nd(t,"y",e.b),Ip(n,t)}function B3e(n,e){var t;t=new op,nd(t,"x",e.a),nd(t,"y",e.b),Ip(n,t)}function mLn(n,e){var t;for(t=e;t;)a0(n,-t.i,-t.j),t=At(t);return n}function SJ(n,e){var t,i;for(t=e,i=0;t>0;)i+=n.a[t],t-=t&-t;return i}function Go(n,e,t){var i;return i=(Ln(e,n.c.length),n.c[e]),n.c[e]=t,i}function PJ(n,e,t){n.a.c.length=0,fOe(n,e,t),n.a.c.length==0||xSe(n,e)}function tk(n){n.i=0,s7(n.b,null),s7(n.c,null),n.a=null,n.e=null,++n.g}function UM(){UM=F,qf=!0,DQn=!1,LQn=!1,$Qn=!1,NQn=!1}function VN(n){UM(),!qf&&(this.c=n,this.e=!0,this.a=new Z)}function vLn(n,e){this.c=0,this.b=e,HMn.call(this,n,17493),this.a=this.c}function kLn(n){jzn(),Syn(this),this.a=new Ct,sY(this,n),xe(this.a,n)}function yLn(){pL(this),this.b=new V(St,St),this.a=new V(li,li)}function GM(){GM=F,$q=new fX(cin,0),A1n=new fX("TARGET_WIDTH",1)}function Ig(n,e){return(ea(n),s4(new Tn(n,new tQ(e,n.a)))).Bd(v3)}function R3e(){return Vi(),S(T(Ion,1),G,367,0,[Xs,Jh,Oc,Kc,zr])}function K3e(){return ow(),S(T(ine,1),G,375,0,[gj,zP,XP,GP,UP])}function _3e(){return o1(),S(T(Lsn,1),G,348,0,[J_,Dsn,Q_,pv,gv])}function H3e(){return T5(),S(T($hn,1),G,323,0,[Nhn,KH,_H,Y8,Z8])}function q3e(){return Yo(),S(T(hfn,1),G,171,0,[Ej,U8,ka,G8,xw])}function U3e(){return wA(),S(T(Hre,1),G,368,0,[pq,bq,mq,wq,gq])}function G3e(){return R5(),S(T(Hce,1),G,373,0,[L2,D3,g9,w9,_j])}function z3e(){return Yk(),S(T(K1n,1),G,324,0,[F1n,_q,R1n,Hq,B1n])}function X3e(){return gf(),S(T(Zh,1),G,170,0,[xn,pi,Ph,Kd,E1])}function V3e(){return Fg(),S(T(A9,1),G,256,0,[Aa,eE,adn,T9,ddn])}function W3e(n){return HE(),function(){return Kpe(n,this,arguments)}}function fr(n){return!n.c||!n.d?!1:!!n.c.i&&n.c.i==n.d.i}function IJ(n,e){return O(e,143)?An(n.c,u(e,143).c):!1}function Zu(n){return n.t||(n.t=new myn(n),k5(new Njn(n),0,n.t)),n.t}function jLn(n){this.b=n,ne.call(this,n),this.a=u(Un(this.b.a,4),129)}function ELn(n){this.b=n,yp.call(this,n),this.a=u(Un(this.b.a,4),129)}function Bs(n,e,t,i,r){LLn.call(this,e,i,r),this.c=n,this.b=t}function OJ(n,e,t,i,r){VOn.call(this,e,i,r),this.c=n,this.a=t}function DJ(n,e,t,i,r){WOn.call(this,e,i,r),this.c=n,this.a=t}function LJ(n,e,t,i,r){LLn.call(this,e,i,r),this.c=n,this.a=t}function WN(n,e){var t;return t=u(Lf(n.d,e),23),t||u(Lf(n.e,e),23)}function CLn(n,e){var t,i;return t=e.ld(),i=n.Fe(t),!!i&&mc(i.e,e.md())}function MLn(n,e){var t;return t=e.ld(),new i0(t,n.e.pc(t,u(e.md(),16)))}function J3e(n,e){var t;return t=n.a.get(e),t??K(ki,Fn,1,0,5,1)}function TLn(n){var e;return e=n.length,An(Yn.substr(Yn.length-e,e),n)}function fe(n){if(pe(n))return n.c=n.a,n.a.Pb();throw M(new nc)}function NJ(n,e){return e==0||n.e==0?n:e>0?wqn(n,e):RBn(n,-e)}function Fp(n,e){return e==0||n.e==0?n:e>0?RBn(n,e):wqn(n,-e)}function $J(n){ole.call(this,n==null?gu:Jr(n),O(n,82)?u(n,82):null)}function ALn(n){var e;return n.c||(e=n.r,O(e,90)&&(n.c=u(e,29))),n.c}function JN(n){var e;return e=new E0,Ur(e,n),U(e,(cn(),Fr),null),e}function SLn(n){var e,t;return e=n.c.i,t=n.d.i,e.k==(Vn(),Zt)&&t.k==Zt}function QN(n){var e,t,i;return e=n&ro,t=n>>22&ro,i=n<0?Il:0,Yc(e,t,i)}function Q3e(n){var e,t,i,r;for(t=n,i=0,r=t.length;i=0?n.Lh(i,t,!0):H0(n,e,t)}function Z3e(n,e,t){return bt(vp(pm(n),Ki(e.b)),vp(pm(n),Ki(t.b)))}function n4e(n,e,t){return bt(vp(pm(n),Ki(e.e)),vp(pm(n),Ki(t.e)))}function e4e(n,e){return y.Math.min(W1(e.a,n.d.d.c),W1(e.b,n.d.d.c))}function ik(n,e){n._i(n.i+1),O6(n,n.i,n.Zi(n.i,e)),n.Mi(n.i++,e),n.Ni()}function t5(n){var e,t;++n.j,e=n.g,t=n.i,n.g=null,n.i=0,n.Oi(t,e),n.Ni()}function PLn(n,e,t){var i;i=new NX(n.a),f5(i,n.a.a),Vc(i.f,e,t),n.a.a=i}function xJ(n,e,t,i){var r;for(r=0;re)throw M(new Ir(Mnn(n,e,"index")));return n}function Yl(n,e){var t;return t=(Ln(e,n.c.length),n.c[e]),Pz(n.c,e,1),t}function RJ(n,e){var t,i;return t=(Jn(n),n),i=(Jn(e),e),t==i?0:te.p?-1:0}function FLn(n){var e;return n.a||(e=n.r,O(e,156)&&(n.a=u(e,156))),n.a}function o4e(n,e,t){var i;return++n.e,--n.f,i=u(n.d[e].gd(t),136),i.md()}function s4e(n){var e,t;return e=n.ld(),t=u(n.md(),16),x7(t.Nc(),new L8n(e))}function BLn(n,e){return Zc(n.a,e)?(Bp(n.a,e),!0):!1}function Rp(n,e,t){return ek(e,n.e.Rd().gc()),ek(t,n.c.Rd().gc()),n.a[e][t]}function XM(n,e,t){this.a=n,this.b=e,this.c=t,nn(n.t,this),nn(e.i,this)}function VM(n,e,t,i){this.f=n,this.e=e,this.d=t,this.b=i,this.c=i?i.d:null}function rk(){this.b=new Ct,this.a=new Ct,this.b=new Ct,this.a=new Ct}function $4(){$4=F;var n,e;MO=(o4(),e=new xE,e),TO=(n=new fD,n)}function f4e(n){var e;return ea(n),e=new ISn(n,n.a.e,n.a.d|4),new uV(n,e)}function RLn(n){var e;for(z1(n),e=0;n.a.Bd(new W0n);)e=nr(e,1);return e}function WM(n,e){return Jn(e),n.c=0,"Initial capacity must not be negative")}function JM(){JM=F,p9=new lt("org.eclipse.elk.labels.labelManager")}function KLn(){KLn=F,ysn=new Dt("separateLayerConnections",(OT(),F_))}function lf(){lf=F,zw=new rX("REGULAR",0),ja=new rX("CRITICAL",1)}function ck(){ck=F,Kq=new lX("FIXED",0),JI=new lX("CENTER_NODE",1)}function QM(){QM=F,Tsn=new Jz("QUADRATIC",0),V_=new Jz("SCANLINE",1)}function _Ln(){_Ln=F,dne=Ce((u5(),S(T(Psn,1),G,322,0,[B8,pj,Ssn])))}function HLn(){HLn=F,bne=Ce((bT(),S(T(Osn,1),G,351,0,[Isn,VP,W_])))}function qLn(){qLn=F,fne=Ce((D0(),S(T(R_,1),G,372,0,[ub,ma,cb])))}function ULn(){ULn=F,mne=Ce((hd(),S(T(pne,1),G,460,0,[Y_,mv,p2])))}function GLn(){GLn=F,Cne=Ce((Z4(),S(T(sH,1),G,299,0,[uH,oH,mj])))}function zLn(){zLn=F,Tne=Ce((vl(),S(T(Mne,1),G,311,0,[vj,v2,E3])))}function XLn(){XLn=F,Zte=Ce((g5(),S(T(Lhn,1),G,390,0,[FH,Dhn,MI])))}function VLn(){VLn=F,oie=Ce((ST(),S(T(zhn,1),G,387,0,[Uhn,zH,Ghn])))}function WLn(){WLn=F,sie=Ce((d5(),S(T(Xhn,1),G,349,0,[VH,XH,Ij])))}function JLn(){JLn=F,uie=Ce((gr(),S(T(cie,1),G,463,0,[n9,Vu,Jc])))}function QLn(){QLn=F,fie=Ce((om(),S(T(Whn,1),G,350,0,[WH,Vhn,e9])))}function YLn(){YLn=F,hie=Ce((dT(),S(T(Yhn,1),G,352,0,[Qhn,JH,Jhn])))}function ZLn(){ZLn=F,lie=Ce((DT(),S(T(Zhn,1),G,388,0,[QH,Ov,Gw])))}function nNn(){nNn=F,are=Ce((b5(),S(T(gln,1),G,392,0,[wln,nq,Lj])))}function eNn(){eNn=F,Gre=Ce((Ok(),S(T(Uln,1),G,393,0,[KI,Hln,qln])))}function tNn(){tNn=F,ace=Ce((AT(),S(T(s1n,1),G,300,0,[Cq,o1n,u1n])))}function iNn(){iNn=F,dce=Ce((XT(),S(T(f1n,1),G,445,0,[Bj,qI,Mq])))}function rNn(){rNn=F,wce=Ce((rA(),S(T(bce,1),G,456,0,[Tq,Sq,Aq])))}function cNn(){cNn=F,mce=Ce((_T(),S(T(a1n,1),G,394,0,[l1n,Oq,h1n])))}function uNn(){uNn=F,Kce=Ce((nT(),S(T(O1n,1),G,439,0,[xq,I1n,P1n])))}function oNn(){oNn=F,Aie=Ce((O0(),S(T(Tie,1),G,464,0,[Oj,t9,PI])))}function sNn(){sNn=F,WQn=Ce((Uu(),S(T(VQn,1),G,471,0,[Mh,ga,Gs])))}function fNn(){fNn=F,XQn=Ce((bf(),S(T(Sw,1),G,237,0,[bc,Wc,wc])))}function hNn(){hNn=F,QQn=Ce((bu(),S(T(JQn,1),G,472,0,[vf,pa,zs])))}function lNn(){lNn=F,xQn=Ce((Gu(),S(T(xr,1),G,108,0,[xun,Yr,Aw])))}function aNn(){aNn=F,pZn=Ce((i5(),S(T(Pon,1),G,391,0,[E_,j_,C_])))}function dNn(){dNn=F,Que=Ce((jl(),S(T(ldn,1),G,346,0,[uO,M1,M9])))}function bNn(){bNn=F,Uce=Ce((Fk(),S(T(Fq,1),G,444,0,[XI,VI,WI])))}function wNn(){wNn=F,Xue=Ce((Nf(),S(T(Zan,1),G,278,0,[Bv,Jw,Rv])))}function gNn(){gNn=F,loe=Ce((Gp(),S(T(mdn,1),G,280,0,[pdn,Yw,aO])))}function Df(n,e){return!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),wx(n.o,e)}function h4e(n,e){var t;n.C&&(t=u(Cr(n.b,e),127).n,t.d=n.C.d,t.a=n.C.a)}function UJ(n){var e,t,i,r;r=n.d,e=n.a,t=n.b,i=n.c,n.d=t,n.a=i,n.b=r,n.c=e}function l4e(n){return!n.g&&(n.g=new CE),!n.g.b&&(n.g.b=new byn(n)),n.g.b}function uk(n){return!n.g&&(n.g=new CE),!n.g.c&&(n.g.c=new pyn(n)),n.g.c}function a4e(n){return!n.g&&(n.g=new CE),!n.g.d&&(n.g.d=new wyn(n)),n.g.d}function d4e(n){return!n.g&&(n.g=new CE),!n.g.a&&(n.g.a=new gyn(n)),n.g.a}function b4e(n,e,t,i){return t&&(i=t.Rh(e,Ot(t.Dh(),n.c.uk()),null,i)),i}function w4e(n,e,t,i){return t&&(i=t.Th(e,Ot(t.Dh(),n.c.uk()),null,i)),i}function e$(n,e,t,i){var r;return r=K(ye,Ke,28,e+1,15,1),vPe(r,n,e,t,i),r}function K(n,e,t,i,r,c){var s;return s=_Rn(r,i),r!=10&&S(T(n,c),e,t,r,s),s}function g4e(n,e,t){var i,r;for(r=new Y4(e,n),i=0;it||e=0?n.Lh(t,!0,!0):H0(n,e,!0)}function L4e(n,e,t){var i;return i=vFn(n,e,t),n.b=new ET(i.c.length),den(n,i)}function N4e(n){if(n.b<=0)throw M(new nc);return--n.b,n.a-=n.c.c,Y(n.a)}function $4e(n){var e;if(!n.a)throw M(new PIn);return e=n.a,n.a=At(n.a),e}function x4e(n){for(;!n.a;)if(!eSn(n.c,new C9n(n)))return!1;return!0}function Kp(n){var e;return Se(n),O(n,204)?(e=u(n,204),e):new _8n(n)}function F4e(n){YM(),u(n.of((qe(),Ww)),181).Fc((zu(),tE)),n.qf(sU,null)}function YM(){YM=F,wue=new Emn,pue=new Cmn,gue=M6e((qe(),sU),wue,Ma,pue)}function ZM(){ZM=F,Kln=new sX("LEAF_NUMBER",0),vq=new sX("NODE_SIZE",1)}function u$(n){n.a=K(ye,Ke,28,n.b+1,15,1),n.c=K(ye,Ke,28,n.b,15,1),n.d=0}function B4e(n,e){n.a.Ne(e.d,n.b)>0&&(nn(n.c,new GV(e.c,e.d,n.d)),n.b=e.d)}function nQ(n,e){if(n.g==null||e>=n.i)throw M(new aL(e,n.i));return n.g[e]}function kNn(n,e,t){if(rm(n,t),t!=null&&!n.fk(t))throw M(new uD);return t}function o$(n,e){return gk(e)!=10&&S(wo(e),e.Sm,e.__elementTypeId$,gk(e),n),n}function F4(n,e,t,i){var r;i=(j0(),i||Pun),r=n.slice(e,t),Tnn(r,n,e,t,-e,i)}function zo(n,e,t,i,r){return e<0?H0(n,t,i):u(t,69).wk().yk(n,n.hi(),e,i,r)}function R4e(n,e){return bt($(R(v(n,(W(),fb)))),$(R(v(e,fb))))}function yNn(){yNn=F,IQn=Ce((B4(),S(T(lP,1),G,304,0,[e_,t_,i_,r_])))}function B4(){B4=F,e_=new uC("All",0),t_=new lTn,i_=new kTn,r_=new hTn}function Uu(){Uu=F,Mh=new FD(s3,0),ga=new FD(qm,1),Gs=new FD(f3,2)}function jNn(){jNn=F,KA(),s0n=St,mse=li,f0n=new V9(St),vse=new V9(li)}function ENn(){ENn=F,jYn=Ce((N0(),S(T(yYn,1),G,417,0,[rj,ij,a_,d_])))}function CNn(){CNn=F,AYn=Ce((A5(),S(T(TYn,1),G,406,0,[fj,wP,gP,hj])))}function MNn(){MNn=F,CYn=Ce((Vp(),S(T(EYn,1),G,332,0,[uj,cj,oj,sj])))}function TNn(){TNn=F,DZn=Ce((dd(),S(T(Lon,1),G,389,0,[Ow,Don,P_,I_])))}function ANn(){ANn=F,TZn=Ce((nm(),S(T(MZn,1),G,416,0,[rb,Iw,Pw,a2])))}function SNn(){SNn=F,tne=Ce(($f(),S(T(ene,1),G,421,0,[j3,lv,av,B_])))}function PNn(){PNn=F,GZn=Ce((OT(),S(T(UZn,1),G,371,0,[F_,HP,qP,wj])))}function INn(){INn=F,nie=Ce((cw(),S(T(RH,1),G,203,0,[TI,BH,S2,A2])))}function ONn(){ONn=F,iie=Ce((lh(),S(T(Hhn,1),G,284,0,[k1,_hn,HH,qH])))}function hk(){hk=F,Fsn=new Yz(kh,0),QP=new Yz("IMPROVE_STRAIGHTNESS",1)}function DNn(n,e){var t,i;return i=e/n.c.Rd().gc()|0,t=e%n.c.Rd().gc(),Rp(n,i,t)}function LNn(n){var e;if(n.nl())for(e=n.i-1;e>=0;--e)L(n,e);return jJ(n)}function eQ(n){var e,t;if(!n.b)return null;for(t=n.b;e=t.a[0];)t=e;return t}function NNn(n){var e,t;if(!n.b)return null;for(t=n.b;e=t.a[1];)t=e;return t}function K4e(n){return O(n,180)?""+u(n,180).a:n==null?null:Jr(n)}function _4e(n){return O(n,180)?""+u(n,180).a:n==null?null:Jr(n)}function $Nn(n,e){if(e.a)throw M(new ec(nXn));fi(n.a,e),e.a=n,!n.j&&(n.j=e)}function tQ(n,e){IC.call(this,e.zd(),e.yd()&-16449),Jn(n),this.a=n,this.c=e}function H4e(n,e){return new _L(e,a0(Ki(e.e),e.f.a+n,e.f.b+n),(_n(),!1))}function q4e(n,e){return k4(),nn(n,new bi(e,Y(e.e.c.length+e.g.c.length)))}function U4e(n,e){return k4(),nn(n,new bi(e,Y(e.e.c.length+e.g.c.length)))}function xNn(){xNn=F,lce=Ce((sA(),S(T(c1n,1),G,354,0,[Eq,i1n,r1n,t1n])))}function FNn(){FNn=F,$re=Ce((w5(),S(T(xln,1),G,353,0,[aq,BI,lq,hq])))}function BNn(){BNn=F,hre=Ce((Qp(),S(T(rln,1),G,405,0,[LI,c9,u9,o9])))}function RNn(){RNn=F,Vue=Ce((El(),S(T(aU,1),G,223,0,[lU,Yj,Kv,F3])))}function KNn(){KNn=F,Zue=Ce((To(),S(T(Yue,1),G,291,0,[nE,nl,Ta,Zj])))}function _Nn(){_Nn=F,foe=Ce((go(),S(T(I9,1),G,386,0,[rE,Gd,iE,Qw])))}function HNn(){HNn=F,doe=Ce((qT(),S(T(Cdn,1),G,320,0,[wU,ydn,Edn,jdn])))}function qNn(){qNn=F,goe=Ce((LT(),S(T(woe,1),G,415,0,[gU,Tdn,Mdn,Adn])))}function nT(){nT=F,xq=new oL(mVn,0),I1n=new oL(Crn,1),P1n=new oL(kh,2)}function Wb(n,e,t,i,r){return Jn(n),Jn(e),Jn(t),Jn(i),Jn(r),new AW(n,e,i)}function UNn(n,e){var t;return t=u(Bp(n.e,e),400),t?(tW(t),t.e):null}function du(n,e){var t;return t=qr(n,e,0),t==-1?!1:(Yl(n,t),!0)}function GNn(n,e,t){var i;return z1(n),i=new LO,i.a=e,n.a.Nb(new TCn(i,t)),i.a}function G4e(n){var e;return z1(n),e=K(Pi,Tr,28,0,15,1),hg(n.a,new y9n(e)),e}function iQ(n){var e;if(!E$(n))throw M(new nc);return n.e=1,e=n.d,n.d=null,e}function n1(n){var e;return Vr(n)&&(e=0-n,!isNaN(e))?e:Q1(tm(n))}function qr(n,e,t){for(;t=0?tA(n,t,!0,!0):H0(n,e,!0)}function cQ(n){var e;return e=cd(Un(n,32)),e==null&&(iu(n),e=cd(Un(n,32))),e}function uQ(n){var e;return n.Oh()||(e=se(n.Dh())-n.ji(),n.$h().Mk(e)),n.zh()}function QNn(n,e){con=new kE,MYn=e,L8=n,u(L8.b,68),XJ(L8,con,null),aGn(L8)}function i5(){i5=F,E_=new RD("XY",0),j_=new RD("X",1),C_=new RD("Y",2)}function bu(){bu=F,vf=new BD("TOP",0),pa=new BD(qm,1),zs=new BD(Ftn,2)}function vl(){vl=F,vj=new GD(kh,0),v2=new GD("TOP",1),E3=new GD(Ftn,2)}function wk(){wk=F,UH=new nX("INPUT_ORDER",0),GH=new nX("PORT_DEGREE",1)}function R4(){R4=F,hun=Yc(ro,ro,524287),bQn=Yc(0,0,Ty),lun=QN(1),QN(2),aun=QN(0)}function a$(n){var e;return n.d!=n.r&&(e=ws(n),n.e=!!e&&e.lk()==bJn,n.d=e),n.e}function d$(n,e,t){var i;return i=n.g[e],O6(n,e,n.Zi(e,t)),n.Ri(e,t,i),n.Ni(),i}function rT(n,e){var t;return t=n.dd(e),t>=0?(n.gd(t),!0):!1}function b$(n,e){var t;for(Se(n),Se(e),t=!1;e.Ob();)t=t|n.Fc(e.Pb());return t}function Lf(n,e){var t;return t=u(ee(n.e,e),400),t?(DTn(n,t),t.e):null}function YNn(n){var e,t;return e=n/60|0,t=n%60,t==0?""+e:""+e+":"+(""+t)}function Jb(n,e){var t=n.a[e],i=(K$(),WK)[typeof t];return i?i(t):wY(typeof t)}function rc(n,e){var t,i;return ea(n),i=new _J(e,n.a),t=new rSn(i),new Tn(n,t)}function w$(n){var e;return e=n.b.c.length==0?null:sn(n.b,0),e!=null&&M$(n,0),e}function W4e(n,e){var t,i,r;r=e.c.i,t=u(ee(n.f,r),60),i=t.d.c-t.e.c,BQ(e.a,i,0)}function oQ(n,e){var t;for(++n.d,++n.c[e],t=e+1;t=0;)++e[0]}function J4e(n,e){eu(n,e==null||GC((Jn(e),e))||isNaN((Jn(e),e))?0:(Jn(e),e))}function Q4e(n,e){tu(n,e==null||GC((Jn(e),e))||isNaN((Jn(e),e))?0:(Jn(e),e))}function Y4e(n,e){I0(n,e==null||GC((Jn(e),e))||isNaN((Jn(e),e))?0:(Jn(e),e))}function Z4e(n,e){P0(n,e==null||GC((Jn(e),e))||isNaN((Jn(e),e))?0:(Jn(e),e))}function nme(n,e,t){return vp(new V(t.e.a+t.f.a/2,t.e.b+t.f.b/2),n)==(Jn(e),e)}function eme(n,e){return O(e,102)&&u(e,19).Bb&hr?new dL(e,n):new Y4(e,n)}function tme(n,e){return O(e,102)&&u(e,19).Bb&hr?new dL(e,n):new Y4(e,n)}function gk(n){return n.__elementTypeCategory$==null?10:n.__elementTypeCategory$}function e$n(n,e){return e==(xL(),xL(),AQn)?n.toLocaleLowerCase():n.toLowerCase()}function t$n(n){if(!n.e)throw M(new nc);return n.c=n.a=n.e,n.e=n.e.e,--n.d,n.a.f}function sQ(n){if(!n.c)throw M(new nc);return n.e=n.a=n.c,n.c=n.c.c,++n.d,n.a.f}function i$n(n){var e;for(++n.a,e=n.c.a.length;n.an.a[i]&&(i=t);return i}function r$n(n){var e;return e=u(v(n,(W(),ob)),313),e?e.a==n:!1}function c$n(n){var e;return e=u(v(n,(W(),ob)),313),e?e.i==n:!1}function u$n(){u$n=F,yZn=Ce((Vi(),S(T(Ion,1),G,367,0,[Xs,Jh,Oc,Kc,zr])))}function o$n(){o$n=F,rne=Ce((ow(),S(T(ine,1),G,375,0,[gj,zP,XP,GP,UP])))}function s$n(){s$n=F,wne=Ce((o1(),S(T(Lsn,1),G,348,0,[J_,Dsn,Q_,pv,gv])))}function f$n(){f$n=F,eie=Ce((T5(),S(T($hn,1),G,323,0,[Nhn,KH,_H,Y8,Z8])))}function h$n(){h$n=F,Sne=Ce((Yo(),S(T(hfn,1),G,171,0,[Ej,U8,ka,G8,xw])))}function l$n(){l$n=F,qre=Ce((wA(),S(T(Hre,1),G,368,0,[pq,bq,mq,wq,gq])))}function a$n(){a$n=F,qce=Ce((R5(),S(T(Hce,1),G,373,0,[L2,D3,g9,w9,_j])))}function d$n(){d$n=F,Wce=Ce((Yk(),S(T(K1n,1),G,324,0,[F1n,_q,R1n,Hq,B1n])))}function b$n(){b$n=F,zue=Ce((ci(),S(T(E9,1),G,88,0,[Wf,Xr,Br,Vf,us])))}function w$n(){w$n=F,mue=Ce((gf(),S(T(Zh,1),G,170,0,[xn,pi,Ph,Kd,E1])))}function g$n(){g$n=F,eoe=Ce((Fg(),S(T(A9,1),G,256,0,[Aa,eE,adn,T9,ddn])))}function p$n(){p$n=F,roe=Ce((tn(),S(T(lr,1),Mc,64,0,[sc,Xn,Zn,ae,Wn])))}function cT(){cT=F,Run=new Uz("BY_SIZE",0),s_=new Uz("BY_SIZE_AND_SHAPE",1)}function uT(){uT=F,v_=new Xz("EADES",0),vP=new Xz("FRUCHTERMAN_REINGOLD",1)}function pk(){pk=F,WP=new Qz("READING_DIRECTION",0),Nsn=new Qz("ROTATION",1)}function r5(){r5=F,PZn=new rwn,IZn=new own,AZn=new swn,SZn=new uwn,OZn=new fwn}function m$n(n){this.b=new Z,this.a=new Z,this.c=new Z,this.d=new Z,this.e=n}function v$n(n){this.g=n,this.f=new Z,this.a=y.Math.min(this.g.c.c,this.g.d.c)}function k$n(n,e,t){qC.call(this),lQ(this),this.a=n,this.c=t,this.b=e.d,this.f=e.e}function sme(n,e,t){var i,r;for(r=new C(t);r.a=0&&e0?e-1:e,eEn($he(U$n(YV(new up,t),n.n),n.j),n.k)}function Nr(n){var e,t;t=(e=new hD,e),ve((!n.q&&(n.q=new q(As,n,11,10)),n.q),t)}function fQ(n){return(n.i&2?"interface ":n.i&1?"":"class ")+(ll(n),n.o)}function oT(n){return Ec(n,et)>0?et:Ec(n,Wi)<0?Wi:Ae(n)}function Qb(n){return n<3?(Co(n,$zn),n+1):n=-.01&&n.a<=Kf&&(n.a=0),n.b>=-.01&&n.b<=Kf&&(n.b=0),n}function Og(n){Xg();var e,t;for(t=Arn,e=0;et&&(t=n[e]);return t}function C$n(n,e){var t;if(t=oy(n.Dh(),e),!t)throw M(new Gn(da+e+sK));return t}function Yb(n,e){var t;for(t=n;At(t);)if(t=At(t),t==e)return!0;return!1}function vme(n,e){var t,i,r;for(i=e.a.ld(),t=u(e.a.md(),16).gc(),r=0;rn||n>e)throw M(new pz("fromIndex: 0, toIndex: "+n+Mtn+e))}function S0(n){if(n<0)throw M(new Gn("Illegal Capacity: "+n));this.g=this.aj(n)}function hQ(n,e){return Mf(),Rs(sa),y.Math.abs(n-e)<=sa||n==e||isNaN(n)&&isNaN(e)}function m$(n,e){var t,i,r,c;for(i=n.d,r=0,c=i.length;r0&&(n.a/=e,n.b/=e),n}function jo(n){var e;return n.w?n.w:(e=lpe(n),e&&!e.Vh()&&(n.w=e),e)}function K4(n,e){var t,i;i=n.a,t=w5e(n,e,null),i!=e&&!n.e&&(t=Nm(n,e,t)),t&&t.oj()}function P$n(n,e,t){var i,r;i=e;do r=$(n.p[i.p])+t,n.p[i.p]=r,i=n.a[i.p];while(i!=e)}function I$n(n,e,t){var i=function(){return n.apply(i,arguments)};return e.apply(i,t),i}function Tme(n){var e;return n==null?null:(e=u(n,195),Bye(e,e.length))}function L(n,e){if(n.g==null||e>=n.i)throw M(new aL(e,n.i));return n.Wi(e,n.g[e])}function Ame(n,e){Dn();var t,i;for(i=new Z,t=0;t=14&&e<=16))),n}function Ee(n,e){var t;return Jn(e),t=n[":"+e],B7(!!t,"Enum constant undefined: "+e),t}function we(n,e,t,i,r,c){var s;return s=bN(n,e),G$n(t,s),s.i=r?8:0,s.f=i,s.e=r,s.g=c,s}function dQ(n,e,t,i,r){this.d=e,this.k=i,this.f=r,this.o=-1,this.p=1,this.c=n,this.a=t}function bQ(n,e,t,i,r){this.d=e,this.k=i,this.f=r,this.o=-1,this.p=2,this.c=n,this.a=t}function wQ(n,e,t,i,r){this.d=e,this.k=i,this.f=r,this.o=-1,this.p=6,this.c=n,this.a=t}function gQ(n,e,t,i,r){this.d=e,this.k=i,this.f=r,this.o=-1,this.p=7,this.c=n,this.a=t}function pQ(n,e,t,i,r){this.d=e,this.j=i,this.e=r,this.o=-1,this.p=4,this.c=n,this.a=t}function z$n(n,e){var t,i,r,c;for(i=e,r=0,c=i.length;r=0))throw M(new Gn("tolerance ("+n+") must be >= 0"));return n}function V$n(n,e){var t;return O(e,44)?n.c.Mc(e):(t=wx(n,e),VT(n,e),t)}function Mr(n,e,t){return ad(n,e),zc(n,t),e1(n,0),Zb(n,1),u1(n,!0),c1(n,!0),n}function vk(n,e){var t;if(t=n.gc(),e<0||e>t)throw M(new Kb(e,t));return new SV(n,e)}function wT(n,e){n.b=y.Math.max(n.b,e.d),n.e+=e.r+(n.a.c.length==0?0:n.c),nn(n.a,e)}function W$n(n){Fb(n.c>=0),_8e(n.d,n.c)<0&&(n.a=n.a-1&n.d.a.length-1,n.b=n.d.c),n.c=-1}function gT(n){var e,t;for(t=n.c.Cc().Kc();t.Ob();)e=u(t.Pb(),16),e.$b();n.c.$b(),n.d=0}function Fme(n){var e,t,i,r;for(t=n.a,i=0,r=t.length;i=0}function CQ(n,e){n.r>0&&n.c0&&n.g!=0&&CQ(n.i,e/n.r*n.i.d))}function MQ(n,e){var t;t=n.c,n.c=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,1,t,n.c))}function y$(n,e){var t;t=n.c,n.c=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,4,t,n.c))}function X4(n,e){var t;t=n.k,n.k=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,2,t,n.k))}function j$(n,e){var t;t=n.D,n.D=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,2,t,n.D))}function mT(n,e){var t;t=n.f,n.f=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,8,t,n.f))}function vT(n,e){var t;t=n.i,n.i=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,7,t,n.i))}function TQ(n,e){var t;t=n.a,n.a=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,8,t,n.a))}function AQ(n,e){var t;t=n.b,n.b=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,0,t,n.b))}function SQ(n,e){var t;t=n.b,n.b=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,0,t,n.b))}function PQ(n,e){var t;t=n.c,n.c=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,1,t,n.c))}function IQ(n,e){var t;t=n.d,n.d=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,1,t,n.d))}function Ume(n,e,t){var i;n.b=e,n.a=t,i=(n.a&512)==512?new gjn:new rG,n.c=rAe(i,n.b,n.a)}function oxn(n,e){return Sl(n.e,e)?(dr(),a$(e)?new eM(e,n):new j7(e,n)):new $Mn(e,n)}function Gme(n){var e,t;return 0>n?new Dz:(e=n+1,t=new vLn(e,n),new oV(null,t))}function zme(n,e){Dn();var t;return t=new ap(1),Ai(n)?Dr(t,n,e):Vc(t.f,n,e),new eD(t)}function Xme(n,e){var t,i;return t=n.c,i=e.e[n.p],i>0?u(sn(t.a,i-1),10):null}function Vme(n,e){var t,i;return t=n.o+n.p,i=e.o+e.p,te?(e<<=1,e>0?e:Y5):e}function E$(n){switch(_X(n.e!=3),n.e){case 2:return!1;case 0:return!0}return i4e(n)}function fxn(n,e){var t;return O(e,8)?(t=u(e,8),n.a==t.a&&n.b==t.b):!1}function Jme(n,e){var t;t=new kE,u(e.b,68),u(e.b,68),u(e.b,68),nu(e.a,new BV(n,t,e))}function hxn(n,e){var t,i;for(i=e.vc().Kc();i.Ob();)t=u(i.Pb(),44),Vk(n,t.ld(),t.md())}function OQ(n,e){var t;t=n.d,n.d=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,11,t,n.d))}function kT(n,e){var t;t=n.j,n.j=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,13,t,n.j))}function DQ(n,e){var t;t=n.b,n.b=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,21,t,n.b))}function Qme(n,e){(UM(),qf?null:e.c).length==0&&TAn(e,new BU),Dr(n.a,qf?null:e.c,e)}function Yme(n,e){e.Ug("Hierarchical port constraint processing",1),g9e(n),xLe(n),e.Vg()}function D0(){D0=F,ub=new KD("START",0),ma=new KD("MIDDLE",1),cb=new KD("END",2)}function yT(){yT=F,RI=new oX("P1_NODE_PLACEMENT",0),D2=new oX("P2_EDGE_ROUTING",1)}function J1(){J1=F,y3=new lt(Jtn),jP=new lt(MXn),$8=new lt(TXn),lj=new lt(AXn)}function L0(n){var e;return FL(n.f.g,n.d),oe(n.b),n.c=n.a,e=u(n.a.Pb(),44),n.b=GQ(n),e}function LQ(n){var e;return n.b==null?(Gl(),Gl(),dE):(e=n.ul()?n.tl():n.sl(),e)}function lxn(n,e){var t;return t=e==null?-1:qr(n.b,e,0),t<0?!1:(M$(n,t),!0)}function Ks(n,e){var t;return Jn(e),t=e.g,n.b[t]?!1:($t(n.b,t,e),++n.c,!0)}function jT(n,e){var t,i;return t=1-e,i=n.a[t],n.a[t]=i.a[e],i.a[e]=n,n.b=!0,i.b=!1,i}function Zme(n,e){var t,i;for(i=e.Kc();i.Ob();)t=u(i.Pb(),272),n.b=!0,fi(n.e,t),t.b=n}function nve(n,e){var t,i;return t=u(v(n,(cn(),Hw)),8),i=u(v(e,Hw),8),bt(t.b,i.b)}function C$(n,e,t){var i,r,c;return c=e>>5,r=e&31,i=vi(U1(n.n[t][c],Ae(Fs(r,1))),3),i}function axn(n,e,t){var i,r,c;for(c=n.a.length-1,r=n.b,i=0;i0?1:0:(!n.c&&(n.c=Y7(vc(n.f))),n.c).e}function yxn(n,e){e?n.B==null&&(n.B=n.D,n.D=null):n.B!=null&&(n.D=n.B,n.B=null)}function rve(n,e){return nm(),n==rb&&e==Iw||n==Iw&&e==rb||n==a2&&e==Pw||n==Pw&&e==a2}function cve(n,e){return nm(),n==rb&&e==Pw||n==rb&&e==a2||n==Iw&&e==a2||n==Iw&&e==Pw}function jxn(n,e){return Mf(),Rs(Kf),y.Math.abs(0-e)<=Kf||e==0||isNaN(0)&&isNaN(e)?0:n/e}function Exn(n,e){return $(R(ho($k(_r(new Tn(null,new In(n.c.b,16)),new I7n(n)),e))))}function FQ(n,e){return $(R(ho($k(_r(new Tn(null,new In(n.c.b,16)),new P7n(n)),e))))}function uve(){return pr(),S(T(cH,1),G,259,0,[ZP,cs,K8,nI,yv,m2,_8,vv,kv,eI])}function ove(){return gs(),S(T(Khn,1),G,243,0,[AI,Sj,Pj,Fhn,Bhn,xhn,Rhn,SI,pb,Uw])}function sve(n,e){var t;e.Ug("General Compactor",1),t=d8e(u(z(n,(ua(),yq)),393)),t.Cg(n)}function fve(n,e){var t,i;return t=u(z(n,(ua(),_I)),17),i=u(z(e,_I),17),jc(t.a,i.a)}function BQ(n,e,t){var i,r;for(r=ge(n,0);r.b!=r.d.c;)i=u(be(r),8),i.a+=e,i.b+=t;return n}function o5(n,e,t){var i;for(i=n.b[t&n.f];i;i=i.b)if(t==i.a&&oh(e,i.g))return i;return null}function s5(n,e,t){var i;for(i=n.c[t&n.f];i;i=i.d)if(t==i.f&&oh(e,i.i))return i;return null}function hve(n,e,t){var i,r,c;for(i=0,r=0;r>>31;i!=0&&(n[t]=i)}function P$(n,e,t,i,r,c){var s;this.c=n,s=new Z,pZ(n,s,e,n.b,t,i,r,c),this.a=new xi(s,0)}function Cxn(){this.c=new XE(0),this.b=new XE(Trn),this.d=new XE(lVn),this.a=new XE(QB)}function Vo(n,e,t,i,r,c,s){je.call(this,n,e),this.d=t,this.e=i,this.c=r,this.b=c,this.a=If(s)}function Ut(n,e,t,i,r,c,s,f,h,l,a,d,g){return P_n(n,e,t,i,r,c,s,f,h,l,a,d,g),sx(n,!1),n}function lve(n){return n.b.c.i.k==(Vn(),Zt)?u(v(n.b.c.i,(W(),st)),12):n.b.c}function Mxn(n){return n.b.d.i.k==(Vn(),Zt)?u(v(n.b.d.i,(W(),st)),12):n.b.d}function ave(n){var e;return e=BM(n),o0(e.a,0)?(QE(),QE(),SQn):(QE(),new uAn(e.b))}function I$(n){var e;return e=gJ(n),o0(e.a,0)?(Ob(),Ob(),n_):(Ob(),new AL(e.b))}function O$(n){var e;return e=gJ(n),o0(e.a,0)?(Ob(),Ob(),n_):(Ob(),new AL(e.c))}function Txn(n){switch(n.g){case 2:return tn(),Wn;case 4:return tn(),Zn;default:return n}}function Axn(n){switch(n.g){case 1:return tn(),ae;case 3:return tn(),Xn;default:return n}}function Sxn(n){switch(n.g){case 0:return new hmn;case 1:return new lmn;default:return null}}function Hp(){Hp=F,x_=new Dt("edgelabelcenterednessanalysis.includelabel",(_n(),wa))}function RQ(){RQ=F,Mie=ah(WMn(Re(Re(new ii,(Vi(),Oc),(tr(),NP)),Kc,PP),zr),LP)}function Pxn(){Pxn=F,Pie=ah(WMn(Re(Re(new ii,(Vi(),Oc),(tr(),NP)),Kc,PP),zr),LP)}function D$(){D$=F,x9=new ljn,CU=S(T(ku,1),s2,179,0,[]),Joe=S(T(As,1),Gcn,62,0,[])}function V4(){V4=F,dj=new Vz("TO_INTERNAL_LTR",0),L_=new Vz("TO_INPUT_DIRECTION",1)}function Ou(){Ou=F,Ron=new wwn,Fon=new gwn,Bon=new pwn,xon=new mwn,Kon=new vwn,_on=new kwn}function dve(n,e){e.Ug(HXn,1),HY(Qhe(new IE((o6(),new kN(n,!1,!1,new qU))))),e.Vg()}function bve(n,e,t){t.Ug("DFS Treeifying phase",1),O8e(n,e),PTe(n,e),n.a=null,n.b=null,t.Vg()}function kk(n,e){return _n(),Ai(n)?RJ(n,Oe(e)):$b(n)?tN(n,R(e)):Nb(n)?rwe(n,un(e)):n.Fd(e)}function f5(n,e){var t,i;for(Jn(e),i=e.vc().Kc();i.Ob();)t=u(i.Pb(),44),n.zc(t.ld(),t.md())}function wve(n,e,t){var i;for(i=t.Kc();i.Ob();)if(!_M(n,e,i.Pb()))return!1;return!0}function gve(n,e,t,i,r){var c;return t&&(c=Ot(e.Dh(),n.c),r=t.Rh(e,-1-(c==-1?i:c),null,r)),r}function pve(n,e,t,i,r){var c;return t&&(c=Ot(e.Dh(),n.c),r=t.Th(e,-1-(c==-1?i:c),null,r)),r}function Ixn(n){var e;if(n.b==-2){if(n.e==0)e=-1;else for(e=0;n.a[e]==0;e++);n.b=e}return n.b}function mve(n){if(Jn(n),n.length==0)throw M(new eh("Zero length BigInteger"));ESe(this,n)}function KQ(n){this.i=n.gc(),this.i>0&&(this.g=this.aj(this.i+(this.i/8|0)+1),n.Qc(this.g))}function Oxn(n,e,t){this.g=n,this.d=e,this.e=t,this.a=new Z,IEe(this),Dn(),Yt(this.a,null)}function _Q(n,e){e.q=n,n.d=y.Math.max(n.d,e.r),n.b+=e.d+(n.a.c.length==0?0:n.c),nn(n.a,e)}function W4(n,e){var t,i,r,c;return r=n.c,t=n.c+n.b,c=n.d,i=n.d+n.a,e.a>r&&e.ac&&e.br?t=r:zn(e,t+1),n.a=qo(n.a,0,e)+(""+i)+$W(n.a,t)}function Kxn(n,e){n.a=nr(n.a,1),n.c=y.Math.min(n.c,e),n.b=y.Math.max(n.b,e),n.d=nr(n.d,e)}function Mve(n,e){return e1||n.Ob())return++n.a,n.g=0,e=n.i,n.Ob(),e;throw M(new nc)}function Uxn(n){switch(n.a.g){case 1:return new WCn;case 3:return new WRn;default:return new s8n}}function qQ(n,e){switch(e){case 1:return!!n.n&&n.n.i!=0;case 2:return n.k!=null}return wJ(n,e)}function vc(n){return Ay>22),r=n.h+e.h+(i>>22),Yc(t&ro,i&ro,r&Il)}function Yxn(n,e){var t,i,r;return t=n.l-e.l,i=n.m-e.m+(t>>22),r=n.h-e.h+(i>>22),Yc(t&ro,i&ro,r&Il)}function zve(n){var e,t;for(RDe(n),t=new C(n.d);t.ai)throw M(new Kb(e,i));return n.Si()&&(t=gOn(n,t)),n.Ei(e,t)}function em(n,e,t,i,r){var c,s;for(s=t;s<=r;s++)for(c=e;c<=i;c++)Rg(n,c,s)||xA(n,c,s,!0,!1)}function u6e(n){Xg();var e,t,i;for(t=K(Ei,J,8,2,0,1),i=0,e=0;e<2;e++)i+=.5,t[e]=Z9e(i,n);return t}function tm(n){var e,t,i;return e=~n.l+1&ro,t=~n.m+(e==0?1:0)&ro,i=~n.h+(e==0&&t==0?1:0)&Il,Yc(e,t,i)}function QQ(n){var e;if(n<0)return Wi;if(n==0)return 0;for(e=Y5;!(e&n);e>>=1);return e}function R$(n,e,t){return n>=128?!1:n<64?M6(vi(Fs(1,n),t),0):M6(vi(Fs(1,n-64),e),0)}function Pk(n,e,t){return t==null?(!n.q&&(n.q=new de),Bp(n.q,e)):(!n.q&&(n.q=new de),Xe(n.q,e,t)),n}function U(n,e,t){return t==null?(!n.q&&(n.q=new de),Bp(n.q,e)):(!n.q&&(n.q=new de),Xe(n.q,e,t)),n}function fFn(n){var e,t;return t=new zM,Ur(t,n),U(t,(J1(),y3),n),e=new de,$Pe(n,t,e),fDe(n,t,e),t}function hFn(n){var e,t;return e=n.t-n.k[n.o.p]*n.d+n.j[n.o.p]>n.f,t=n.u+n.e[n.o.p]*n.d>n.f*n.s*n.d,e||t}function lFn(n,e){var t,i,r,c;for(t=!1,i=n.a[e].length,c=0;c=0,"Negative initial capacity"),B7(e>=0,"Non-positive load factor"),Hu(this)}function s6e(n,e,t,i,r){var c,s;if(s=n.length,c=t.length,e<0||i<0||r<0||e+r>s||i+r>c)throw M(new qG)}function eY(n,e){Dn();var t,i,r,c,s;for(s=!1,i=e,r=0,c=i.length;r1||e>=0&&n.b<3)}function H$(n){var e,t,i;e=~n.l+1&ro,t=~n.m+(e==0?1:0)&ro,i=~n.h+(e==0&&t==0?1:0)&Il,n.l=e,n.m=t,n.h=i}function rY(n){Dn();var e,t,i;for(i=1,t=n.Kc();t.Ob();)e=t.Pb(),i=31*i+(e!=null?mt(e):0),i=i|0;return i}function d6e(n,e,t,i,r){var c;return c=Xnn(n,e),t&&H$(c),r&&(n=u7e(n,e),i?ba=tm(n):ba=Yc(n.l,n.m,n.h)),c}function yFn(n,e,t){n.g=uF(n,e,(tn(),Zn),n.b),n.d=uF(n,t,Zn,n.b),!(n.g.c==0||n.d.c==0)&&YKn(n)}function jFn(n,e,t){n.g=uF(n,e,(tn(),Wn),n.j),n.d=uF(n,t,Wn,n.j),!(n.g.c==0||n.d.c==0)&&YKn(n)}function cY(n,e){switch(e){case 7:return!!n.e&&n.e.i!=0;case 8:return!!n.d&&n.d.i!=0}return qY(n,e)}function b6e(n,e){switch(e.g){case 0:O(n.b,641)||(n.b=new Rxn);break;case 1:O(n.b,642)||(n.b=new BSn)}}function EFn(n){switch(n.g){case 0:return new gmn;default:throw M(new Gn(xS+(n.f!=null?n.f:""+n.g)))}}function CFn(n){switch(n.g){case 0:return new wmn;default:throw M(new Gn(xS+(n.f!=null?n.f:""+n.g)))}}function w6e(n,e,t){return!s4(ut(new Tn(null,new In(n.c,16)),new Z3(new hMn(e,t)))).Bd((Xa(),v3))}function MFn(n,e){return vp(pm(u(v(e,(lc(),vb)),88)),new V(n.c.e.a-n.b.e.a,n.c.e.b-n.b.e.b))<=0}function g6e(n,e){for(;n.g==null&&!n.c?cJ(n):n.g==null||n.i!=0&&u(n.g[n.i-1],51).Ob();)kle(e,CA(n))}function ld(n){var e,t;for(t=new C(n.a.b);t.ai?1:0}function v6e(n){return nn(n.c,(qp(),bue)),hQ(n.a,$(R(rn((bx(),EI)))))?new tvn:new $kn(n)}function k6e(n){for(;!n.d||!n.d.Ob();)if(n.b&&!i6(n.b))n.d=u(Sp(n.b),51);else return null;return n.d}function oY(n){switch(n.g){case 1:return lVn;default:case 2:return 0;case 3:return QB;case 4:return Trn}}function y6e(){nt();var n;return IU||(n=_1e(oa("M",!0)),n=uM(oa("M",!1),n),IU=n,IU)}function LT(){LT=F,gU=new CC("ELK",0),Tdn=new CC("JSON",1),Mdn=new CC("DOT",2),Adn=new CC("SVG",3)}function d5(){d5=F,VH=new WD("STACKED",0),XH=new WD("REVERSE_STACKED",1),Ij=new WD("SEQUENCED",2)}function b5(){b5=F,wln=new eL(kh,0),nq=new eL("MIDDLE_TO_MIDDLE",1),Lj=new eL("AVOID_OVERLAP",2)}function cm(){cm=F,Esn=new Ygn,Csn=new Zgn,JZn=new Jgn,WZn=new n2n,VZn=new Qgn,jsn=(Jn(VZn),new O0n)}function NT(){NT=F,hdn=new f0(15),Jue=new Ni((qe(),C1),hdn),C9=N3,udn=Pue,odn=Hd,fdn=K2,sdn=Vw}function Lg(n,e){var t,i,r,c,s;for(i=e,r=0,c=i.length;r=n.b.c.length||(fY(n,2*e+1),t=2*e+2,t0&&(e.Cd(t),t.i&&E5e(t))}function hY(n,e,t){var i;for(i=t-1;i>=0&&n[i]===e[i];i--);return i<0?0:ND(vi(n[i],mr),vi(e[i],mr))?-1:1}function SFn(n,e,t){var i,r;this.g=n,this.c=e,this.a=this,this.d=this,r=sxn(t),i=K(sQn,Cy,227,r,0,1),this.b=i}function X$(n,e,t,i,r){var c,s;for(s=t;s<=r;s++)for(c=e;c<=i;c++)if(Rg(n,c,s))return!0;return!1}function A6e(n,e){var t,i;for(i=n.Zb().Cc().Kc();i.Ob();)if(t=u(i.Pb(),16),t.Hc(e))return!0;return!1}function PFn(n,e,t){var i,r,c,s;for(Jn(t),s=!1,c=n.fd(e),r=t.Kc();r.Ob();)i=r.Pb(),c.Rb(i),s=!0;return s}function V$(n,e){var t,i;return i=u(Un(n.a,4),129),t=K(jU,MK,424,e,0,1),i!=null&&Ic(i,0,t,0,i.length),t}function IFn(n,e){var t;return t=new jF((n.f&256)!=0,n.i,n.a,n.d,(n.f&16)!=0,n.j,n.g,e),n.e!=null||(t.c=n),t}function S6e(n,e){var t;return n===e?!0:O(e,85)?(t=u(e,85),dnn(Wa(n),t.vc())):!1}function OFn(n,e,t){var i,r;for(r=t.Kc();r.Ob();)if(i=u(r.Pb(),44),n.Be(e,i.md()))return!0;return!1}function DFn(n,e,t){return n.d[e.p][t.p]||(O9e(n,e,t),n.d[e.p][t.p]=!0,n.d[t.p][e.p]=!0),n.a[e.p][t.p]}function P6e(n,e){var t;return!n||n==e||!kt(e,(W(),sb))?!1:(t=u(v(e,(W(),sb)),10),t!=n)}function W$(n){switch(n.i){case 2:return!0;case 1:return!1;case-1:++n.c;default:return n.$l()}}function LFn(n){switch(n.i){case-2:return!0;case-1:return!1;case 1:--n.c;default:return n._l()}}function NFn(n){yOn.call(this,"The given string does not match the expected format for individual spacings.",n)}function I6e(n,e){var t;e.Ug("Min Size Preprocessing",1),t=jnn(n),ht(n,(_h(),a9),t.a),ht(n,UI,t.b),e.Vg()}function O6e(n){var e,t,i;for(e=0,i=K(Ei,J,8,n.b,0,1),t=ge(n,0);t.b!=t.d.c;)i[e++]=u(be(t),8);return i}function J$(n,e,t){var i,r,c;for(i=new Ct,c=ge(t,0);c.b!=c.d.c;)r=u(be(c),8),xe(i,new rr(r));PFn(n,e,i)}function D6e(n,e){var t;return t=nr(n,e),ND(RN(n,e),0)|AC(RN(n,t),0)?t:nr(Ey,RN(U1(t,63),1))}function L6e(n,e){var t,i;return t=u(n.d.Bc(e),16),t?(i=n.e.hc(),i.Gc(t),n.e.d-=t.gc(),t.$b(),i):null}function $Fn(n){var e;if(e=n.a.c.length,e>0)return E4(e-1,n.a.c.length),Yl(n.a,e-1);throw M(new $yn)}function xFn(n,e,t){if(n>e)throw M(new Gn(ZA+n+Qzn+e));if(n<0||e>t)throw M(new pz(ZA+n+Stn+e+Mtn+t))}function um(n,e){n.D==null&&n.B!=null&&(n.D=n.B,n.B=null),j$(n,e==null?null:(Jn(e),e)),n.C&&n.hl(null)}function N6e(n,e){var t;t=rn((bx(),EI))!=null&&e.Sg()!=null?$(R(e.Sg()))/$(R(rn(EI))):1,Xe(n.b,e,t)}function lY(n,e){var t,i;if(i=n.c[e],i!=0)for(n.c[e]=0,n.d-=i,t=e+1;tPS?n-t>PS:t-n>PS}function XFn(n,e){var t;for(t=0;tr&&(EKn(e.q,r),i=t!=e.q.d)),i}function VFn(n,e){var t,i,r,c,s,f,h,l;return h=e.i,l=e.j,i=n.f,r=i.i,c=i.j,s=h-r,f=l-c,t=y.Math.sqrt(s*s+f*f),t}function pY(n,e){var t,i;return i=WT(n),i||(t=(UF(),$Hn(e)),i=new Cyn(t),ve(i.El(),n)),i}function Lk(n,e){var t,i;return t=u(n.c.Bc(e),16),t?(i=n.hc(),i.Gc(t),n.d-=t.gc(),t.$b(),n.mc(i)):n.jc()}function G6e(n,e){var t,i;for(i=to(n.d,1)!=0,t=!0;t;)t=!1,t=e.c.mg(e.e,i),t=t|sy(n,e,i,!1),i=!i;$Q(n)}function WFn(n,e,t,i){var r,c;n.a=e,c=i?0:1,n.f=(r=new s_n(n.c,n.a,t,c),new Kqn(t,n.a,r,n.e,n.b,n.c==(O0(),t9)))}function xT(n){var e;return oe(n.a!=n.b),e=n.d.a[n.a],EAn(n.b==n.d.c&&e!=null),n.c=n.a,n.a=n.a+1&n.d.a.length-1,e}function JFn(n){var e;if(n.c!=0)return n.c;for(e=0;e=n.c.b:n.a<=n.c.b))throw M(new nc);return e=n.a,n.a+=n.c.c,++n.b,Y(e)}function ex(n){var e;return e=new DX(n.a),Ur(e,n),U(e,(W(),st),n),e.o.a=n.g,e.o.b=n.f,e.n.a=n.i,e.n.b=n.j,e}function tx(n){return(tn(),mu).Hc(n.j)?$(R(v(n,(W(),jv)))):cc(S(T(Ei,1),J,8,0,[n.i.n,n.n,n.a])).b}function X6e(n){var e;return e=DC(Cie),u(v(n,(W(),Hc)),21).Hc((pr(),yv))&&Re(e,(Vi(),Oc),(tr(),FP)),e}function V6e(n){var e,t,i,r;for(r=new ni,i=new C(n);i.a=0?e:-e;i>0;)i%2==0?(t*=t,i=i/2|0):(r*=t,i-=1);return e<0?1/r:r}function Z6e(n,e){var t,i,r;for(r=1,t=n,i=e>=0?e:-e;i>0;)i%2==0?(t*=t,i=i/2|0):(r*=t,i-=1);return e<0?1/r:r}function na(n,e){var t,i,r,c;return c=(r=n?WT(n):null,O_n((i=e,r&&r.Gl(),i))),c==e&&(t=WT(n),t&&t.Gl()),c}function QFn(n,e,t){var i,r;return r=n.f,n.f=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,0,r,e),t?t.nj(i):t=i),t}function YFn(n,e,t){var i,r;return r=n.b,n.b=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,3,r,e),t?t.nj(i):t=i),t}function vY(n,e,t){var i,r;return r=n.a,n.a=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,1,r,e),t?t.nj(i):t=i),t}function ZFn(n){var e,t;if(n!=null)for(t=0;t=i||e-129&&n<128?(FSn(),e=n+128,t=pun[e],!t&&(t=pun[e]=new vG(n)),t):new vG(n)}function sm(n){var e,t;return n>-129&&n<128?(nPn(),e=n+128,t=yun[e],!t&&(t=yun[e]=new yG(n)),t):new yG(n)}function tBn(n,e){var t;n.a.c.length>0&&(t=u(sn(n.a,n.a.c.length-1),579),sY(t,e))||nn(n.a,new kLn(e))}function c5e(n){xs();var e,t;e=n.d.c-n.e.c,t=u(n.g,154),nu(t.b,new p7n(e)),nu(t.c,new m7n(e)),qi(t.i,new v7n(e))}function iBn(n){var e;return e=new x1,e.a+="VerticalSegment ",Dc(e,n.e),e.a+=" ",Be(e,RX(new yD,new C(n.k))),e.a}function ix(n,e){var t,i,r;for(t=0,r=uc(n,e).Kc();r.Ob();)i=u(r.Pb(),12),t+=v(i,(W(),Xu))!=null?1:0;return t}function xg(n,e,t){var i,r,c;for(i=0,c=ge(n,0);c.b!=c.d.c&&(r=$(R(be(c))),!(r>t));)r>=e&&++i;return i}function rBn(n,e){Se(n);try{return n._b(e)}catch(t){if(t=It(t),O(t,212)||O(t,169))return!1;throw M(t)}}function yY(n,e){Se(n);try{return n.Hc(e)}catch(t){if(t=It(t),O(t,212)||O(t,169))return!1;throw M(t)}}function u5e(n,e){Se(n);try{return n.Mc(e)}catch(t){if(t=It(t),O(t,212)||O(t,169))return!1;throw M(t)}}function tw(n,e){Se(n);try{return n.xc(e)}catch(t){if(t=It(t),O(t,212)||O(t,169))return null;throw M(t)}}function o5e(n,e){Se(n);try{return n.Bc(e)}catch(t){if(t=It(t),O(t,212)||O(t,169))return null;throw M(t)}}function p5(n,e){switch(e.g){case 2:case 1:return uc(n,e);case 3:case 4:return Qo(uc(n,e))}return Dn(),Dn(),sr}function m5(n){var e;return n.Db&64?_s(n):(e=new ls(_s(n)),e.a+=" (name: ",Er(e,n.zb),e.a+=")",e.a)}function s5e(n){var e;return e=u(Lf(n.c.c,""),233),e||(e=new Np(u4(c4(new ep,""),"Other")),s1(n.c.c,"",e)),e}function jY(n,e,t){var i,r;return r=n.sb,n.sb=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,4,r,e),t?t.nj(i):t=i),t}function EY(n,e,t){var i,r;return r=n.r,n.r=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,8,r,n.r),t?t.nj(i):t=i),t}function f5e(n,e,t){var i,r;return i=new ml(n.e,4,13,(r=e.c,r||(On(),Yf)),null,f1(n,e),!1),t?t.nj(i):t=i,t}function h5e(n,e,t){var i,r;return i=new ml(n.e,3,13,null,(r=e.c,r||(On(),Yf)),f1(n,e),!1),t?t.nj(i):t=i,t}function r1(n,e){var t,i;return t=u(e,691),i=t.el(),!i&&t.fl(i=O(e,90)?new xMn(n,u(e,29)):new cDn(n,u(e,156))),i}function Nk(n,e,t){var i;n._i(n.i+1),i=n.Zi(e,t),e!=n.i&&Ic(n.g,e,n.g,e+1,n.i-e),$t(n.g,e,i),++n.i,n.Mi(e,t),n.Ni()}function l5e(n,e){var t;return e.a&&(t=e.a.a.length,n.a?Be(n.a,n.b):n.a=new mo(n.d),aDn(n.a,e.a,e.d.length,t)),n}function a5e(n,e){var t;n.c=e,n.a=p8e(e),n.a<54&&(n.f=(t=e.d>1?hDn(e.a[0],e.a[1]):hDn(e.a[0],0),id(e.e>0?t:n1(t))))}function $k(n,e){var t;return t=new LO,n.a.Bd(t)?(b4(),new wD(Jn(GNn(n,t.a,e)))):(z1(n),b4(),b4(),Dun)}function cBn(n,e){var t;n.c.length!=0&&(t=u(xf(n,K(Qh,b1,10,n.c.length,0,1)),199),CX(t,new rgn),Y_n(t,e))}function uBn(n,e){var t;n.c.length!=0&&(t=u(xf(n,K(Qh,b1,10,n.c.length,0,1)),199),CX(t,new cgn),Y_n(t,e))}function rt(n,e){return Ai(n)?An(n,e):$b(n)?nSn(n,e):Nb(n)?(Jn(n),x(n)===x(e)):pW(n)?n.Fb(e):hW(n)?YMn(n,e):hJ(n,e)}function Wo(n,e,t){if(e<0)Pnn(n,t);else{if(!t.rk())throw M(new Gn(da+t.xe()+p8));u(t,69).wk().Ek(n,n.hi(),e)}}function oBn(n,e,t){if(n<0||e>t)throw M(new Ir(ZA+n+Stn+e+", size: "+t));if(n>e)throw M(new Gn(ZA+n+Qzn+e))}function sBn(n){var e;return n.Db&64?_s(n):(e=new ls(_s(n)),e.a+=" (source: ",Er(e,n.d),e.a+=")",e.a)}function fBn(n){return n>=65&&n<=70?n-65+10:n>=97&&n<=102?n-97+10:n>=48&&n<=57?n-48:0}function d5e(n){VA();var e,t,i,r;for(t=jx(),i=0,r=t.length;i=0?ta(n):G6(ta(n1(n))))}function aBn(n,e,t,i,r,c){this.e=new Z,this.f=(gr(),n9),nn(this.e,n),this.d=e,this.a=t,this.b=i,this.f=r,this.c=c}function g5e(n,e,t){n.n=Va(xa,[J,SB],[376,28],14,[t,wi(y.Math.ceil(e/32))],2),n.o=e,n.p=t,n.j=e-1>>1,n.k=t-1>>1}function dBn(n){return n-=n>>1&1431655765,n=(n>>2&858993459)+(n&858993459),n=(n>>4)+n&252645135,n+=n>>8,n+=n>>16,n&63}function bBn(n,e){var t,i;for(i=new ne(n);i.e!=i.i.gc();)if(t=u(ce(i),142),x(e)===x(t))return!0;return!1}function p5e(n,e,t){var i,r,c;return c=(r=Mm(n.b,e),r),c&&(i=u(qA(ak(n,c),""),29),i)?Qnn(n,i,e,t):null}function rx(n,e,t){var i,r,c;return c=(r=Mm(n.b,e),r),c&&(i=u(qA(ak(n,c),""),29),i)?Ynn(n,i,e,t):null}function m5e(n,e){var t;if(t=Dg(n.i,e),t==null)throw M(new nh("Node did not exist in input."));return HQ(e,t),null}function v5e(n,e){var t;if(t=oy(n,e),O(t,331))return u(t,35);throw M(new Gn(da+e+"' is not a valid attribute"))}function k5(n,e,t){var i;if(i=n.gc(),e>i)throw M(new Kb(e,i));if(n.Si()&&n.Hc(t))throw M(new Gn(Vy));n.Gi(e,t)}function k5e(n,e){e.Ug("Sort end labels",1),qt(ut(rc(new Tn(null,new In(n.b,16)),new Hwn),new qwn),new Uwn),e.Vg()}function ci(){ci=F,Wf=new v7(i8,0),Xr=new v7(f3,1),Br=new v7(s3,2),Vf=new v7(_B,3),us=new v7("UP",4)}function Fk(){Fk=F,XI=new sL("P1_STRUCTURE",0),VI=new sL("P2_PROCESSING_ORDER",1),WI=new sL("P3_EXECUTION",2)}function wBn(){wBn=F,Rre=ah(ah(l6(ah(ah(l6(Re(new ii,(Qp(),c9),(q5(),ZH)),u9),lln),dln),o9),oln),bln)}function y5e(n){switch(u(v(n,(W(),Od)),311).g){case 1:U(n,Od,(vl(),E3));break;case 2:U(n,Od,(vl(),v2))}}function j5e(n){switch(n){case 0:return new rjn;case 1:return new tjn;case 2:return new ijn;default:throw M(new Q9)}}function gBn(n){switch(n.g){case 2:return Xr;case 1:return Br;case 4:return Vf;case 3:return us;default:return Wf}}function AY(n,e){switch(n.b.g){case 0:case 1:return e;case 2:case 3:return new Ho(e.d,0,e.a,e.b);default:return null}}function SY(n){switch(n.g){case 1:return Wn;case 2:return Xn;case 3:return Zn;case 4:return ae;default:return sc}}function Bk(n){switch(n.g){case 1:return ae;case 2:return Wn;case 3:return Xn;case 4:return Zn;default:return sc}}function RT(n){switch(n.g){case 1:return Zn;case 2:return ae;case 3:return Wn;case 4:return Xn;default:return sc}}function PY(n,e,t,i){switch(e){case 1:return!n.n&&(n.n=new q(Ar,n,1,7)),n.n;case 2:return n.k}return yZ(n,e,t,i)}function y5(n,e,t){var i,r;return n.Pj()?(r=n.Qj(),i=lF(n,e,t),n.Jj(n.Ij(7,Y(t),i,e,r)),i):lF(n,e,t)}function cx(n,e){var t,i,r;n.d==null?(++n.e,--n.f):(r=e.ld(),t=e.Bi(),i=(t&et)%n.d.length,o4e(n,i,RHn(n,i,t,r)))}function fm(n,e){var t;t=(n.Bb&Us)!=0,e?n.Bb|=Us:n.Bb&=-1025,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,10,t,e))}function hm(n,e){var t;t=(n.Bb&vw)!=0,e?n.Bb|=vw:n.Bb&=-4097,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,12,t,e))}function lm(n,e){var t;t=(n.Bb&$u)!=0,e?n.Bb|=$u:n.Bb&=-8193,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,15,t,e))}function am(n,e){var t;t=(n.Bb&Tw)!=0,e?n.Bb|=Tw:n.Bb&=-2049,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,11,t,e))}function E5e(n){var e;n.g&&(e=n.c.kg()?n.f:n.a,len(e.a,n.o,!0),len(e.a,n.o,!1),U(n.o,(cn(),Kt),(Oi(),Ud)))}function C5e(n){var e;if(!n.a)throw M(new Or("Cannot offset an unassigned cut."));e=n.c-n.b,n.b+=e,_In(n,e),KIn(n,e)}function M5e(n,e){var t;if(t=ee(n.k,e),t==null)throw M(new nh("Port did not exist in input."));return HQ(e,t),null}function T5e(n){var e,t;for(t=xHn(jo(n)).Kc();t.Ob();)if(e=Oe(t.Pb()),U5(n,e))return A3e((mCn(),Boe),e);return null}function pBn(n){var e,t;for(t=n.p.a.ec().Kc();t.Ob();)if(e=u(t.Pb(),218),e.f&&n.b[e.c]<-1e-10)return e;return null}function A5e(n){var e,t;for(t=Ya(new x1,91),e=!0;n.Ob();)e||(t.a+=ur),e=!1,Dc(t,n.Pb());return(t.a+="]",t).a}function S5e(n){var e,t,i;for(e=new Z,i=new C(n.b);i.ae?1:n==e?n==0?bt(1/n,1/e):0:isNaN(n)?isNaN(e)?0:1:-1}function I5e(n){var e;return e=n.a[n.c-1&n.a.length-1],e==null?null:(n.c=n.c-1&n.a.length-1,$t(n.a,n.c,null),e)}function O5e(n){var e,t,i;for(i=0,t=n.length,e=0;e=1?Xr:Vf):t}function $5e(n){switch(u(v(n,(cn(),$l)),223).g){case 1:return new Ppn;case 3:return new Npn;default:return new Spn}}function ea(n){if(n.c)ea(n.c);else if(n.d)throw M(new Or("Stream already terminated, can't be modified or used"))}function $0(n,e,t){var i;return i=n.a.get(e),n.a.set(e,t===void 0?null:t),i===void 0?(++n.c,++n.b.g):++n.d,i}function x5e(n,e,t){var i,r;for(r=n.a.ec().Kc();r.Ob();)if(i=u(r.Pb(),10),Mk(t,u(sn(e,i.p),16)))return i;return null}function OY(n,e,t){var i;return i=0,e&&(mg(n.a)?i+=e.f.a/2:i+=e.f.b/2),t&&(mg(n.a)?i+=t.f.a/2:i+=t.f.b/2),i}function F5e(n,e,t){var i;i=t,!i&&(i=YV(new up,0)),i.Ug(PXn,2),jRn(n.b,e,i.eh(1)),YIe(n,e,i.eh(1)),eLe(e,i.eh(1)),i.Vg()}function DY(n,e,t){var i,r;return i=(B1(),r=new yE,r),aT(i,e),lT(i,t),n&&ve((!n.a&&(n.a=new ti(xo,n,5)),n.a),i),i}function ox(n){var e;return n.Db&64?_s(n):(e=new ls(_s(n)),e.a+=" (identifier: ",Er(e,n.k),e.a+=")",e.a)}function sx(n,e){var t;t=(n.Bb&kc)!=0,e?n.Bb|=kc:n.Bb&=-32769,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,18,t,e))}function LY(n,e){var t;t=(n.Bb&kc)!=0,e?n.Bb|=kc:n.Bb&=-32769,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,18,t,e))}function dm(n,e){var t;t=(n.Bb&wh)!=0,e?n.Bb|=wh:n.Bb&=-16385,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,16,t,e))}function NY(n,e){var t;t=(n.Bb&hr)!=0,e?n.Bb|=hr:n.Bb&=-65537,n.Db&4&&!(n.Db&1)&&it(n,new Bs(n,1,20,t,e))}function $Y(n){var e;return e=K(fs,gh,28,2,15,1),n-=hr,e[0]=(n>>10)+Sy&ui,e[1]=(n&1023)+56320&ui,hh(e,0,e.length)}function B5e(n){var e;return e=sw(n),e>34028234663852886e22?St:e<-34028234663852886e22?li:e}function nr(n,e){var t;return Vr(n)&&Vr(e)&&(t=n+e,Ay"+td(e.c):"e_"+mt(e),n.b&&n.c?td(n.b)+"->"+td(n.c):"e_"+mt(n))}function _5e(n,e){return An(e.b&&e.c?td(e.b)+"->"+td(e.c):"e_"+mt(e),n.b&&n.c?td(n.b)+"->"+td(n.c):"e_"+mt(n))}function x0(n,e){return Mf(),Rs(sa),y.Math.abs(n-e)<=sa||n==e||isNaN(n)&&isNaN(e)?0:ne?1:s0(isNaN(n),isNaN(e))}function El(){El=F,lU=new kC(i8,0),Yj=new kC("POLYLINE",1),Kv=new kC("ORTHOGONAL",2),F3=new kC("SPLINES",3)}function _T(){_T=F,l1n=new uL("ASPECT_RATIO_DRIVEN",0),Oq=new uL("MAX_SCALE_DRIVEN",1),h1n=new uL("AREA_DRIVEN",2)}function H5e(n,e,t){var i;try{l6e(n,e,t)}catch(r){throw r=It(r),O(r,606)?(i=r,M(new $J(i))):M(r)}return e}function q5e(n){var e,t,i;for(t=0,i=n.length;te&&i.Ne(n[c-1],n[c])>0;--c)s=n[c],$t(n,c,n[c-1]),$t(n,c-1,s)}function vn(n,e){var t,i,r,c,s;if(t=e.f,s1(n.c.d,t,e),e.g!=null)for(r=e.g,c=0,s=r.length;ce){wDn(t);break}}q7(t,e)}function X5e(n,e){var t,i,r;i=Sg(e),r=$(R(rw(i,(cn(),Vs)))),t=y.Math.max(0,r/2-.5),I5(e,t,1),nn(n,new NCn(e,t))}function V5e(n,e,t){var i;t.Ug("Straight Line Edge Routing",1),t.dh(e,xrn),i=u(z(e,(Mg(),O2)),27),iGn(n,i),t.dh(e,DS)}function xY(n,e){n.n.c.length==0&&nn(n.n,new NM(n.s,n.t,n.i)),nn(n.b,e),gZ(u(sn(n.n,n.n.c.length-1),209),e),RUn(n,e)}function j5(n){var e;this.a=(e=u(n.e&&n.e(),9),new _o(e,u($s(e,e.length),9),0)),this.b=K(ki,Fn,1,this.a.a.length,5,1)}function Jr(n){var e;return Array.isArray(n)&&n.Tm===J2?za(wo(n))+"@"+(e=mt(n)>>>0,e.toString(16)):n.toString()}function W5e(n,e){return n.h==Ty&&n.m==0&&n.l==0?(e&&(ba=Yc(0,0,0)),nTn((R4(),lun))):(e&&(ba=Yc(n.l,n.m,n.h)),Yc(0,0,0))}function J5e(n,e){switch(e.g){case 2:return n.b;case 1:return n.c;case 4:return n.d;case 3:return n.a;default:return!1}}function yBn(n,e){switch(e.g){case 2:return n.b;case 1:return n.c;case 4:return n.d;case 3:return n.a;default:return!1}}function FY(n,e,t,i){switch(e){case 3:return n.f;case 4:return n.g;case 5:return n.i;case 6:return n.j}return PY(n,e,t,i)}function HT(n,e){if(e==n.d)return n.e;if(e==n.e)return n.d;throw M(new Gn("Node "+e+" not part of edge "+n))}function Q5e(n,e){var t;if(t=oy(n.Dh(),e),O(t,102))return u(t,19);throw M(new Gn(da+e+"' is not a valid reference"))}function Jo(n,e,t,i){if(e<0)ten(n,t,i);else{if(!t.rk())throw M(new Gn(da+t.xe()+p8));u(t,69).wk().Ck(n,n.hi(),e,i)}}function eo(n){var e;if(n.b){if(eo(n.b),n.b.d!=n.c)throw M(new Bo)}else n.d.dc()&&(e=u(n.f.c.xc(n.e),16),e&&(n.d=e))}function Y5e(n){Bb();var e,t,i,r;for(e=n.o.b,i=u(u(ot(n.r,(tn(),ae)),21),87).Kc();i.Ob();)t=u(i.Pb(),117),r=t.e,r.b+=e}function Z5e(n){var e,t,i;for(this.a=new ih,i=new C(n);i.a=r)return e.c+t;return e.c+e.b.gc()}function e8e(n,e){m4();var t,i,r,c;for(i=LNn(n),r=e,F4(i,0,i.length,r),t=0;t0&&(i+=r,++t);return t>1&&(i+=n.d*(t-1)),i}function i8e(n){var e,t,i,r,c;return c=enn(n),t=e7(n.c),i=!t,i&&(r=new Ka,df(c,"knownLayouters",r),e=new lyn(r),qi(n.c,e)),c}function KY(n){var e,t,i;for(i=new Hl,i.a+="[",e=0,t=n.gc();e0&&(zn(e-1,n.length),n.charCodeAt(e-1)==58)&&!lx(n,N9,$9))}function _Y(n,e){var t;return x(n)===x(e)?!0:O(e,92)?(t=u(e,92),n.e==t.e&&n.d==t.d&&I3e(n,t.a)):!1}function zp(n){switch(tn(),n.g){case 4:return Xn;case 1:return Zn;case 3:return ae;case 2:return Wn;default:return sc}}function o8e(n){var e,t;if(n.b)return n.b;for(t=qf?null:n.d;t;){if(e=qf?null:t.b,e)return e;t=qf?null:t.d}return a4(),$un}function HY(n){var e,t,i;for(i=$(R(n.a.of((qe(),iO)))),t=new C(n.a.Sf());t.a>5,e=n&31,i=K(ye,Ke,28,t+1,15,1),i[t]=1<3;)r*=10,--c;n=(n+(r>>1))/r|0}return i.i=n,!0}function Ot(n,e){var t,i,r;if(t=(n.i==null&&bh(n),n.i),i=e.Lj(),i!=-1){for(r=t.length;i=0;--i)for(e=t[i],r=0;r>1,this.k=e-1>>1}function j8e(n){YM(),u(n.of((qe(),Ma)),181).Hc((io(),hO))&&(u(n.of(Ww),181).Fc((zu(),B3)),u(n.of(Ma),181).Mc(hO))}function SBn(n){var e,t;e=n.d==(Yp(),dv),t=GZ(n),e&&!t||!e&&t?U(n.a,(cn(),Th),(Rh(),Uj)):U(n.a,(cn(),Th),(Rh(),qj))}function bx(){bx=F,nC(),EI=(cn(),gb),Qte=If(S(T(Xq,1),Ern,149,0,[Tj,Vs,M2,wb,qw,IH,Av,Sv,OH,J8,C2,Bd,T2]))}function E8e(n,e){var t;return t=u(Wr(n,qu(new ju,new yu,new Eu,S(T(xr,1),G,108,0,[(Gu(),Yr)]))),15),t.Qc(WSn(t.gc()))}function PBn(n,e){var t,i;if(i=new Y3(n.a.ad(e,!0)),i.a.gc()<=1)throw M(new ip);return t=i.a.ec().Kc(),t.Pb(),u(t.Pb(),40)}function C8e(n,e,t){var i,r;return i=$(n.p[e.i.p])+$(n.d[e.i.p])+e.n.b+e.a.b,r=$(n.p[t.i.p])+$(n.d[t.i.p])+t.n.b+t.a.b,r-i}function WY(n,e){var t;return n.i>0&&(e.lengthn.i&&$t(e,n.i,null),e}function UT(n){var e;return n.Db&64?m5(n):(e=new ls(m5(n)),e.a+=" (instanceClassName: ",Er(e,n.D),e.a+=")",e.a)}function GT(n){var e,t,i,r;for(r=0,t=0,i=n.length;t0?(n._j(),i=e==null?0:mt(e),r=(i&et)%n.d.length,t=RHn(n,r,i,e),t!=-1):!1}function IBn(n,e){var t,i;n.a=nr(n.a,1),n.c=y.Math.min(n.c,e),n.b=y.Math.max(n.b,e),n.d+=e,t=e-n.f,i=n.e+t,n.f=i-n.e-t,n.e=i}function JY(n,e){switch(e){case 3:P0(n,0);return;case 4:I0(n,0);return;case 5:eu(n,0);return;case 6:tu(n,0);return}kY(n,e)}function F0(n,e){switch(e.g){case 1:return Cp(n.j,(Ou(),Fon));case 2:return Cp(n.j,(Ou(),Ron));default:return Dn(),Dn(),sr}}function QY(n){m0();var e;switch(e=n.Pc(),e.length){case 0:return qK;case 1:return new VL(Se(e[0]));default:return new PN(q5e(e))}}function OBn(n,e){n.Xj();try{n.d.bd(n.e++,e),n.f=n.d.j,n.g=-1}catch(t){throw t=It(t),O(t,77)?M(new Bo):M(t)}}function gx(){gx=F,TU=new Tvn,zdn=new Avn,Xdn=new Svn,Vdn=new Pvn,Wdn=new Ivn,Jdn=new Ovn,Qdn=new Dvn,Ydn=new Lvn,Zdn=new Nvn}function zT(n,e){kX();var t,i;return t=D7((KE(),KE(),P8)),i=null,e==t&&(i=u(Nc(fun,n),624)),i||(i=new JPn(n),e==t&&Dr(fun,n,i)),i}function DBn(n){cw();var e;return(n.q?n.q:(Dn(),Dn(),Wh))._b((cn(),db))?e=u(v(n,db),203):e=u(v(Hi(n),W8),203),e}function rw(n,e){var t,i;return i=null,kt(n,(cn(),yI))&&(t=u(v(n,yI),96),t.pf(e)&&(i=t.of(e))),i==null&&(i=v(Hi(n),e)),i}function LBn(n,e){var t,i,r;return O(e,44)?(t=u(e,44),i=t.ld(),r=tw(n.Rc(),i),oh(r,t.md())&&(r!=null||n.Rc()._b(i))):!1}function wf(n,e){var t,i,r;return n.f>0&&(n._j(),i=e==null?0:mt(e),r=(i&et)%n.d.length,t=xnn(n,r,i,e),t)?t.md():null}function Xc(n,e,t){var i,r,c;return n.Pj()?(i=n.i,c=n.Qj(),Nk(n,i,e),r=n.Ij(3,null,e,i,c),t?t.nj(r):t=r):Nk(n,n.i,e),t}function T8e(n,e,t){var i,r;return i=new ml(n.e,4,10,(r=e.c,O(r,90)?u(r,29):(On(),Ps)),null,f1(n,e),!1),t?t.nj(i):t=i,t}function A8e(n,e,t){var i,r;return i=new ml(n.e,3,10,null,(r=e.c,O(r,90)?u(r,29):(On(),Ps)),f1(n,e),!1),t?t.nj(i):t=i,t}function NBn(n){Bb();var e;return e=new rr(u(n.e.of((qe(),K2)),8)),n.B.Hc((io(),Hv))&&(e.a<=0&&(e.a=20),e.b<=0&&(e.b=20)),e}function ta(n){dh();var e,t;return t=Ae(n),e=Ae(U1(n,32)),e!=0?new HOn(t,e):t>10||t<0?new gl(1,t):kQn[t]}function Kk(n,e){var t;return Vr(n)&&Vr(e)&&(t=n%e,Ay=0?c=c.a[1]:(r=c,c=c.a[0])}return r}function Hk(n,e,t){var i,r,c;for(r=null,c=n.b;c;){if(i=n.a.Ne(e,c.d),t&&i==0)return c;i<=0?c=c.a[0]:(r=c,c=c.a[1])}return r}function L8e(n,e,t,i){var r,c,s;return r=!1,xOe(n.f,t,i)&&(e9e(n.f,n.a[e][t],n.a[e][i]),c=n.a[e],s=c[i],c[i]=c[t],c[t]=s,r=!0),r}function BBn(n,e,t){var i,r,c,s;for(r=u(ee(n.b,t),183),i=0,s=new C(e.j);s.a>5,e&=31,r=n.d+t+(e==0?0:1),i=K(ye,Ke,28,r,15,1),Oye(i,n.a,t,e),c=new Qa(n.e,r,i),Q6(c),c}function N8e(n,e){var t,i,r;for(i=new te(re(Qt(n).a.Kc(),new En));pe(i);)if(t=u(fe(i),18),r=t.d.i,r.c==e)return!1;return!0}function nZ(n,e,t){var i,r,c,s,f;return s=n.k,f=e.k,i=t[s.g][f.g],r=R(rw(n,i)),c=R(rw(e,i)),y.Math.max((Jn(r),r),(Jn(c),c))}function $8e(){return Error.stackTraceLimit>0?(y.Error.stackTraceLimit=Error.stackTraceLimit=64,!0):"stack"in new Error}function x8e(n,e){return Mf(),Mf(),Rs(sa),(y.Math.abs(n-e)<=sa||n==e||isNaN(n)&&isNaN(e)?0:ne?1:s0(isNaN(n),isNaN(e)))>0}function eZ(n,e){return Mf(),Mf(),Rs(sa),(y.Math.abs(n-e)<=sa||n==e||isNaN(n)&&isNaN(e)?0:ne?1:s0(isNaN(n),isNaN(e)))<0}function KBn(n,e){return Mf(),Mf(),Rs(sa),(y.Math.abs(n-e)<=sa||n==e||isNaN(n)&&isNaN(e)?0:ne?1:s0(isNaN(n),isNaN(e)))<=0}function mx(n,e){for(var t=0;!e[t]||e[t]=="";)t++;for(var i=e[t++];t0&&this.b>0&&(this.g=cM(this.c,this.b,this.a))}function F8e(n,e){var t=n.a,i;e=String(e),t.hasOwnProperty(e)&&(i=t[e]);var r=(K$(),WK)[typeof i],c=r?r(i):wY(typeof i);return c}function wm(n){var e,t,i;if(i=null,e=Eh in n.a,t=!e,t)throw M(new nh("Every element must have an id."));return i=Zp(dl(n,Eh)),i}function B0(n){var e,t;for(t=a_n(n),e=null;n.c==2;)Ye(n),e||(e=(nt(),nt(),new P6(2)),pd(e,t),t=e),t.Jm(a_n(n));return t}function VT(n,e){var t,i,r;return n._j(),i=e==null?0:mt(e),r=(i&et)%n.d.length,t=xnn(n,r,i,e),t?(V$n(n,t),t.md()):null}function XBn(n,e){return n.e>e.e?1:n.ee.d?n.e:n.d=48&&n<48+y.Math.min(10,10)?n-48:n>=97&&n<97?n-97+10:n>=65&&n<65?n-65+10:-1}function B8e(n,e){if(e.c==n)return e.d;if(e.d==n)return e.c;throw M(new Gn("Input edge is not connected to the input port."))}function R8e(n){if(JT(nv,n))return _n(),ov;if(JT(cK,n))return _n(),wa;throw M(new Gn("Expecting true or false"))}function rZ(n){switch(typeof n){case nB:return t1(n);case dtn:return pp(n);case i3:return SAn(n);default:return n==null?0:l0(n)}}function ah(n,e){if(n.a<0)throw M(new Or("Did not call before(...) or after(...) before calling add(...)."));return YX(n,n.a,e),n}function cZ(n){return $M(),O(n,162)?u(ee(hE,MQn),295).Rg(n):Zc(hE,wo(n))?u(ee(hE,wo(n)),295).Rg(n):null}function iu(n){var e,t;return n.Db&32||(t=(e=u(Un(n,16),29),se(e||n.ii())-se(n.ii())),t!=0&&Xp(n,32,K(ki,Fn,1,t,5,1))),n}function Xp(n,e,t){var i;n.Db&e?t==null?jCe(n,e):(i=Rx(n,e),i==-1?n.Eb=t:$t(cd(n.Eb),i,t)):t!=null&>e(n,e,t)}function K8e(n,e,t,i){var r,c;e.c.length!=0&&(r=$Me(t,i),c=xEe(e),qt(fT(new Tn(null,new In(c,1)),new L3n),new MIn(n,t,r,i)))}function _8e(n,e){var t,i,r,c;return i=n.a.length-1,t=e-n.b&i,c=n.c-e&i,r=n.c-n.b&i,EAn(t=c?(R6e(n,e),-1):(B6e(n,e),1)}function WT(n){var e,t,i;if(i=n.Jh(),!i)for(e=0,t=n.Ph();t;t=t.Ph()){if(++e>PB)return t.Qh();if(i=t.Jh(),i||t==n)break}return i}function WBn(n,e){var t;return x(e)===x(n)?!0:!O(e,21)||(t=u(e,21),t.gc()!=n.gc())?!1:n.Ic(t)}function H8e(n,e){return n.ee.e?1:n.fe.f?1:mt(n)-mt(e)}function JT(n,e){return Jn(n),e==null?!1:An(n,e)?!0:n.length==e.length&&An(n.toLowerCase(),e.toLowerCase())}function Ml(n){var e,t;return Ec(n,-129)>0&&Ec(n,128)<0?(ZSn(),e=Ae(n)+128,t=mun[e],!t&&(t=mun[e]=new kG(n)),t):new kG(n)}function dd(){dd=F,Ow=new aC(kh,0),Don=new aC("INSIDE_PORT_SIDE_GROUPS",1),P_=new aC("GROUP_MODEL_ORDER",2),I_=new aC(tin,3)}function q8e(n){var e;return n.b||xhe(n,(e=$ae(n.e,n.a),!e||!An(cK,wf((!e.b&&(e.b=new lo((On(),ar),pc,e)),e.b),"qualified")))),n.c}function U8e(n,e){var t,i;for(t=(zn(e,n.length),n.charCodeAt(e)),i=e+1;i2e3&&(hQn=n,uP=y.setTimeout(_he,10))),cP++==0?(ime((az(),sun)),!0):!1}function r9e(n,e,t){var i;(DQn?(o8e(n),!0):LQn||$Qn?(a4(),!0):NQn&&(a4(),!1))&&(i=new aSn(e),i.b=t,aje(n,i))}function kx(n,e){var t;t=!n.A.Hc((go(),Gd))||n.q==(Oi(),qc),n.u.Hc((zu(),Fl))?t?XDe(n,e):UGn(n,e):n.u.Hc(Pa)&&(t?dDe(n,e):czn(n,e))}function eRn(n){var e;x(z(n,(qe(),B2)))===x((jl(),uO))&&(At(n)?(e=u(z(At(n),B2),346),ht(n,B2,e)):ht(n,B2,M9))}function c9e(n){var e,t;return kt(n.d.i,(cn(),Cv))?(e=u(v(n.c.i,Cv),17),t=u(v(n.d.i,Cv),17),jc(e.a,t.a)>0):!1}function tRn(n,e,t){return new Ho(y.Math.min(n.a,e.a)-t/2,y.Math.min(n.b,e.b)-t/2,y.Math.abs(n.a-e.a)+t,y.Math.abs(n.b-e.b)+t)}function iRn(n){var e;this.d=new Z,this.j=new Li,this.g=new Li,e=n.g.b,this.f=u(v(Hi(e),(cn(),Do)),88),this.e=$(R(nA(e,qw)))}function rRn(n){this.d=new Z,this.e=new Ql,this.c=K(ye,Ke,28,(tn(),S(T(lr,1),Mc,64,0,[sc,Xn,Zn,ae,Wn])).length,15,1),this.b=n}function sZ(n,e,t){var i;switch(i=t[n.g][e],n.g){case 1:case 3:return new V(0,i);case 2:case 4:return new V(i,0);default:return null}}function cRn(n,e,t){var i,r;r=u(V7(e.f),205);try{r.rf(n,t),hIn(e.f,r)}catch(c){throw c=It(c),O(c,103)?(i=c,M(i)):M(c)}}function uRn(n,e,t){var i,r,c,s,f,h;return i=null,f=Zen(z4(),e),c=null,f&&(r=null,h=Qen(f,t),s=null,h!=null&&(s=n.qf(f,h)),r=s,c=r),i=c,i}function yx(n,e,t,i){var r;if(r=n.length,e>=r)return r;for(e=e>0?e:0;ei&&$t(e,i,null),e}function oRn(n,e){var t,i;for(i=n.a.length,e.lengthi&&$t(e,i,null),e}function gm(n,e){var t,i;if(++n.j,e!=null&&(t=(i=n.a.Cb,O(i,99)?u(i,99).th():null),hCe(e,t))){Xp(n.a,4,t);return}Xp(n.a,4,u(e,129))}function u9e(n){var e;if(n==null)return null;if(e=lMe(Fc(n,!0)),e==null)throw M(new kD("Invalid hexBinary value: '"+n+"'"));return e}function QT(n,e,t){var i;e.a.length>0&&(nn(n.b,new SSn(e.a,t)),i=e.a.length,0i&&(e.a+=ITn(K(fs,gh,28,-i,15,1))))}function sRn(n,e,t){var i,r,c;if(!t[e.d])for(t[e.d]=!0,r=new C($g(e));r.a=n.b>>1)for(i=n.c,t=n.b;t>e;--t)i=i.b;else for(i=n.a.a,t=0;t=0?n.Wh(r):hF(n,i)):t<0?hF(n,i):u(i,69).wk().Bk(n,n.hi(),t)}function aRn(n){var e,t,i;for(i=(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),n.o),t=i.c.Kc();t.e!=t.i.gc();)e=u(t.Yj(),44),e.md();return uk(i)}function rn(n){var e;if(O(n.a,4)){if(e=cZ(n.a),e==null)throw M(new Or(NVn+n.b+"'. "+LVn+(ll(lE),lE.k)+bcn));return e}else return n.a}function b9e(n,e){var t,i;if(n.j.length!=e.j.length)return!1;for(t=0,i=n.j.length;t=64&&e<128&&(r=hf(r,Fs(1,e-64)));return r}function nA(n,e){var t,i;return i=null,kt(n,(qe(),$3))&&(t=u(v(n,$3),96),t.pf(e)&&(i=t.of(e))),i==null&&Hi(n)&&(i=v(Hi(n),e)),i}function w9e(n,e){var t;return t=u(v(n,(cn(),Fr)),75),yL(e,LZn)?t?vo(t):(t=new Mu,U(n,Fr,t)):t&&U(n,Fr,null),t}function M5(){M5=F,aon=(qe(),qan),g_=Ean,DYn=$2,lon=C1,xYn=(aA(),Uun),$Yn=Hun,FYn=zun,NYn=_un,LYn=(Q$(),son),w_=PYn,hon=IYn,pP=OYn}function eA(n){switch($z(),this.c=new Z,this.d=n,n.g){case 0:case 2:this.a=qW(Oon),this.b=St;break;case 3:case 1:this.a=Oon,this.b=li}}function g9e(n){var e;Ep(u(v(n,(cn(),Kt)),101))&&(e=n.b,nHn((Ln(0,e.c.length),u(e.c[0],30))),nHn(u(sn(e,e.c.length-1),30)))}function p9e(n,e){e.Ug("Self-Loop post-processing",1),qt(ut(ut(rc(new Tn(null,new In(n.b,16)),new s2n),new f2n),new h2n),new l2n),e.Vg()}function dRn(n,e,t){var i,r;if(n.c)eu(n.c,n.c.i+e),tu(n.c,n.c.j+t);else for(r=new C(n.b);r.a=0&&(t.d=n.t);break;case 3:n.t>=0&&(t.a=n.t)}n.C&&(t.b=n.C.b,t.c=n.C.c)}function T5(){T5=F,Nhn=new d7(Crn,0),KH=new d7(sR,1),_H=new d7("LINEAR_SEGMENTS",2),Y8=new d7("BRANDES_KOEPF",3),Z8=new d7(sVn,4)}function A5(){A5=F,fj=new hC(eS,0),wP=new hC(HB,1),gP=new hC(qB,2),hj=new hC(UB,3),fj.a=!1,wP.a=!0,gP.a=!1,hj.a=!0}function Vp(){Vp=F,uj=new fC(eS,0),cj=new fC(HB,1),oj=new fC(qB,2),sj=new fC(UB,3),uj.a=!1,cj.a=!0,oj.a=!1,sj.a=!0}function Wp(n,e,t,i){var r;return t>=0?n.Sh(e,t,i):(n.Ph()&&(i=(r=n.Fh(),r>=0?n.Ah(i):n.Ph().Th(n,-1-r,null,i))),n.Ch(e,t,i))}function fZ(n,e){switch(e){case 7:!n.e&&(n.e=new Nn(Vt,n,7,4)),me(n.e);return;case 8:!n.d&&(n.d=new Nn(Vt,n,8,5)),me(n.d);return}JY(n,e)}function ht(n,e,t){return t==null?(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),VT(n.o,e)):(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),Vk(n.o,e,t)),n}function pRn(n,e){Dn();var t,i,r,c;for(t=n,c=e,O(n,21)&&!O(e,21)&&(t=e,c=n),r=t.Kc();r.Ob();)if(i=r.Pb(),c.Hc(i))return!1;return!0}function j9e(n,e,t,i){if(e.at.b)return!0}return!1}function Tx(n,e){return Ai(n)?!!iQn[e]:n.Sm?!!n.Sm[e]:$b(n)?!!tQn[e]:Nb(n)?!!eQn[e]:!1}function E9e(n){var e;e=n.a;do e=u(fe(new te(re(ji(e).a.Kc(),new En))),18).c.i,e.k==(Vn(),Mi)&&n.b.Fc(e);while(e.k==(Vn(),Mi));n.b=Qo(n.b)}function mRn(n,e){var t,i,r;for(r=n,i=new te(re(ji(e).a.Kc(),new En));pe(i);)t=u(fe(i),18),t.c.i.c&&(r=y.Math.max(r,t.c.i.c.p));return r}function C9e(n,e){var t,i,r;for(r=0,i=u(u(ot(n.r,e),21),87).Kc();i.Ob();)t=u(i.Pb(),117),r+=t.d.d+t.b.Mf().b+t.d.a,i.Ob()&&(r+=n.w);return r}function M9e(n,e){var t,i,r;for(r=0,i=u(u(ot(n.r,e),21),87).Kc();i.Ob();)t=u(i.Pb(),117),r+=t.d.b+t.b.Mf().a+t.d.c,i.Ob()&&(r+=n.w);return r}function vRn(n){var e,t,i,r;if(i=0,r=aw(n),r.c.length==0)return 1;for(t=new C(r);t.a=0?n.Lh(s,t,!0):H0(n,c,t)):u(c,69).wk().yk(n,n.hi(),r,t,i)}function P9e(n,e,t,i){var r,c;c=e.pf((qe(),R2))?u(e.of(R2),21):n.j,r=d5e(c),r!=(VA(),l_)&&(t&&!tZ(r)||bnn(aMe(n,r,i),e))}function I9e(n){switch(n.g){case 1:return N0(),rj;case 3:return N0(),ij;case 2:return N0(),d_;case 4:return N0(),a_;default:return null}}function O9e(n,e,t){if(n.e)switch(n.b){case 1:yge(n.c,e,t);break;case 0:jge(n.c,e,t)}else KDn(n.c,e,t);n.a[e.p][t.p]=n.c.i,n.a[t.p][e.p]=n.c.e}function kRn(n){var e,t;if(n==null)return null;for(t=K(Qh,J,199,n.length,0,2),e=0;e=0)return r;if(n.ol()){for(i=0;i=r)throw M(new Kb(e,r));if(n.Si()&&(i=n.dd(t),i>=0&&i!=e))throw M(new Gn(Vy));return n.Xi(e,t)}function hZ(n,e){if(this.a=u(Se(n),253),this.b=u(Se(e),253),n.Ed(e)>0||n==(dD(),_K)||e==(bD(),HK))throw M(new Gn("Invalid range: "+qDn(n,e)))}function yRn(n){var e,t;for(this.b=new Z,this.c=n,this.a=!1,t=new C(n.a);t.a0),(e&-e)==e)return wi(e*to(n,31)*4656612873077393e-25);do t=to(n,31),i=t%e;while(t-i+(e-1)<0);return wi(i)}function F9e(n,e,t){switch(t.g){case 1:n.a=e.a/2,n.b=0;break;case 2:n.a=e.a,n.b=e.b/2;break;case 3:n.a=e.a/2,n.b=e.b;break;case 4:n.a=0,n.b=e.b/2}}function qk(n,e,t,i){var r,c;for(r=e;r1&&(c=L9e(n,e)),c}function CRn(n){var e;return e=$(R(z(n,(qe(),Qj))))*y.Math.sqrt((!n.a&&(n.a=new q(Qe,n,10,11)),n.a).i),new V(e,e/$(R(z(n,rO))))}function Sx(n){var e;return n.f&&n.f.Vh()&&(e=u(n.f,54),n.f=u(na(n,e),84),n.f!=e&&n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,9,8,e,n.f))),n.f}function Px(n){var e;return n.i&&n.i.Vh()&&(e=u(n.i,54),n.i=u(na(n,e),84),n.i!=e&&n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,9,7,e,n.i))),n.i}function br(n){var e;return n.b&&n.b.Db&64&&(e=n.b,n.b=u(na(n,e),19),n.b!=e&&n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,9,21,e,n.b))),n.b}function uA(n,e){var t,i,r;n.d==null?(++n.e,++n.f):(i=e.Bi(),uTe(n,n.f+1),r=(i&et)%n.d.length,t=n.d[r],!t&&(t=n.d[r]=n.dk()),t.Fc(e),++n.f)}function dZ(n,e,t){var i;return e.tk()?!1:e.Ik()!=-2?(i=e.ik(),i==null?t==null:rt(i,t)):e.qk()==n.e.Dh()&&t==null}function oA(){var n;Co(16,$zn),n=sxn(16),this.b=K(UK,Cy,303,n,0,1),this.c=K(UK,Cy,303,n,0,1),this.a=null,this.e=null,this.i=0,this.f=n-1,this.g=0}function Tl(n){vV.call(this),this.k=(Vn(),zt),this.j=(Co(6,mw),new Gc(6)),this.b=(Co(2,mw),new Gc(2)),this.d=new sD,this.f=new nz,this.a=n}function R9e(n){var e,t;n.c.length<=1||(e=Sqn(n,(tn(),ae)),w_n(n,u(e.a,17).a,u(e.b,17).a),t=Sqn(n,Wn),w_n(n,u(t.a,17).a,u(t.b,17).a))}function K9e(n,e,t){var i,r;for(r=n.a.b,i=r.c.length;i102?-1:n<=57?n-48:n<65?-1:n<=70?n-65+10:n<97?-1:n-97+10}function Nx(n,e){if(n==null)throw M(new sp("null key in entry: null="+e));if(e==null)throw M(new sp("null value in entry: "+n+"=null"))}function q9e(n,e){for(var t,i;n.Ob();)if(!e.Ob()||(t=n.Pb(),i=e.Pb(),!(x(t)===x(i)||t!=null&&rt(t,i))))return!1;return!e.Ob()}function ARn(n,e){var t;return t=S(T(Pi,1),Tr,28,15,[Z$(n.a[0],e),Z$(n.a[1],e),Z$(n.a[2],e)]),n.d&&(t[0]=y.Math.max(t[0],t[2]),t[2]=t[0]),t}function SRn(n,e){var t;return t=S(T(Pi,1),Tr,28,15,[$T(n.a[0],e),$T(n.a[1],e),$T(n.a[2],e)]),n.d&&(t[0]=y.Math.max(t[0],t[2]),t[2]=t[0]),t}function wZ(n,e,t){Ep(u(v(e,(cn(),Kt)),101))||(PJ(n,e,h1(e,t)),PJ(n,e,h1(e,(tn(),ae))),PJ(n,e,h1(e,Xn)),Dn(),Yt(e.j,new N7n(n)))}function PRn(n){var e,t;for(n.c||sOe(n),t=new Mu,e=new C(n.a),E(e);e.a0&&(zn(0,e.length),e.charCodeAt(0)==43)?(zn(1,e.length+1),e.substr(1)):e))}function i7e(n){var e;return n==null?null:new H1((e=Fc(n,!0),e.length>0&&(zn(0,e.length),e.charCodeAt(0)==43)?(zn(1,e.length+1),e.substr(1)):e))}function pZ(n,e,t,i,r,c,s,f){var h,l;i&&(h=i.a[0],h&&pZ(n,e,t,h,r,c,s,f),qx(n,t,i.d,r,c,s,f)&&e.Fc(i),l=i.a[1],l&&pZ(n,e,t,l,r,c,s,f))}function Rg(n,e,t){try{return o0(C$(n,e,t),1)}catch(i){throw i=It(i),O(i,333)?M(new Ir(GB+n.o+"*"+n.p+zB+e+ur+t+XB)):M(i)}}function NRn(n,e,t){try{return o0(C$(n,e,t),0)}catch(i){throw i=It(i),O(i,333)?M(new Ir(GB+n.o+"*"+n.p+zB+e+ur+t+XB)):M(i)}}function $Rn(n,e,t){try{return o0(C$(n,e,t),2)}catch(i){throw i=It(i),O(i,333)?M(new Ir(GB+n.o+"*"+n.p+zB+e+ur+t+XB)):M(i)}}function xRn(n,e){if(n.g==-1)throw M(new Cu);n.Xj();try{n.d.hd(n.g,e),n.f=n.d.j}catch(t){throw t=It(t),O(t,77)?M(new Bo):M(t)}}function r7e(n){var e,t,i,r,c;for(i=new C(n.b);i.ac&&$t(e,c,null),e}function c7e(n,e){var t,i;if(i=n.gc(),e==null){for(t=0;t0&&(h+=r),l[a]=s,s+=f*(h+i)}function BRn(n){var e,t,i;for(i=n.f,n.n=K(Pi,Tr,28,i,15,1),n.d=K(Pi,Tr,28,i,15,1),e=0;e0?n.c:0),++r;n.b=i,n.d=c}function qRn(n,e){var t;return t=S(T(Pi,1),Tr,28,15,[aZ(n,(bf(),bc),e),aZ(n,Wc,e),aZ(n,wc,e)]),n.f&&(t[0]=y.Math.max(t[0],t[2]),t[2]=t[0]),t}function d7e(n,e,t){var i;try{xA(n,e+n.j,t+n.k,!1,!0)}catch(r){throw r=It(r),O(r,77)?(i=r,M(new Ir(i.g+iS+e+ur+t+")."))):M(r)}}function b7e(n,e,t){var i;try{xA(n,e+n.j,t+n.k,!0,!1)}catch(r){throw r=It(r),O(r,77)?(i=r,M(new Ir(i.g+iS+e+ur+t+")."))):M(r)}}function URn(n){var e;kt(n,(cn(),ab))&&(e=u(v(n,ab),21),e.Hc((lw(),Js))?(e.Mc(Js),e.Fc(Qs)):e.Hc(Qs)&&(e.Mc(Qs),e.Fc(Js)))}function GRn(n){var e;kt(n,(cn(),ab))&&(e=u(v(n,ab),21),e.Hc((lw(),Zs))?(e.Mc(Zs),e.Fc(Cs)):e.Hc(Cs)&&(e.Mc(Cs),e.Fc(Zs)))}function Kx(n,e,t,i){var r,c,s,f;return n.a==null&&gje(n,e),s=e.b.j.c.length,c=t.d.p,f=i.d.p,r=f-1,r<0&&(r=s-1),c<=r?n.a[r]-n.a[c]:n.a[s-1]-n.a[c]+n.a[r]}function w7e(n){var e,t;if(!n.b)for(n.b=RM(u(n.f,27).kh().i),t=new ne(u(n.f,27).kh());t.e!=t.i.gc();)e=u(ce(t),135),nn(n.b,new pD(e));return n.b}function g7e(n){var e,t;if(!n.e)for(n.e=RM(mN(u(n.f,27)).i),t=new ne(mN(u(n.f,27)));t.e!=t.i.gc();)e=u(ce(t),123),nn(n.e,new Bkn(e));return n.e}function zRn(n){var e,t;if(!n.a)for(n.a=RM(AM(u(n.f,27)).i),t=new ne(AM(u(n.f,27)));t.e!=t.i.gc();)e=u(ce(t),27),nn(n.a,new ML(n,e));return n.a}function K0(n){var e;if(!n.C&&(n.D!=null||n.B!=null))if(e=iDe(n),e)n.hl(e);else try{n.hl(null)}catch(t){if(t=It(t),!O(t,63))throw M(t)}return n.C}function p7e(n){switch(n.q.g){case 5:gKn(n,(tn(),Xn)),gKn(n,ae);break;case 4:mGn(n,(tn(),Xn)),mGn(n,ae);break;default:y_n(n,(tn(),Xn)),y_n(n,ae)}}function m7e(n){switch(n.q.g){case 5:pKn(n,(tn(),Zn)),pKn(n,Wn);break;case 4:vGn(n,(tn(),Zn)),vGn(n,Wn);break;default:j_n(n,(tn(),Zn)),j_n(n,Wn)}}function Kg(n,e){var t,i,r;for(r=new Li,i=n.Kc();i.Ob();)t=u(i.Pb(),36),Sm(t,r.a,0),r.a+=t.f.a+e,r.b=y.Math.max(r.b,t.f.b);return r.b>0&&(r.b+=e),r}function hA(n,e){var t,i,r;for(r=new Li,i=n.Kc();i.Ob();)t=u(i.Pb(),36),Sm(t,0,r.b),r.b+=t.f.b+e,r.a=y.Math.max(r.a,t.f.a);return r.a>0&&(r.a+=e),r}function XRn(n){var e,t,i;for(i=et,t=new C(n.a);t.a>16==6?n.Cb.Th(n,5,jf,e):(i=br(u($n((t=u(Un(n,16),29),t||n.ii()),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function v7e(n){O4();var e=n.e;if(e&&e.stack){var t=e.stack,i=e+`
+`;return t.substring(0,i.length)==i&&(t=t.substring(i.length)),t.split(`
+`)}return[]}function k7e(n){var e;return e=(Q$n(),wQn),e[n>>>28]|e[n>>24&15]<<4|e[n>>20&15]<<8|e[n>>16&15]<<12|e[n>>12&15]<<16|e[n>>8&15]<<20|e[n>>4&15]<<24|e[n&15]<<28}function JRn(n){var e,t,i;n.b==n.c&&(i=n.a.length,t=QQ(y.Math.max(8,i))<<1,n.b!=0?(e=$s(n.a,t),axn(n,e,i),n.a=e,n.b=0):Pb(n.a,t),n.c=i)}function y7e(n,e){var t;return t=n.b,t.pf((qe(),oo))?t.ag()==(tn(),Wn)?-t.Mf().a-$(R(t.of(oo))):e+$(R(t.of(oo))):t.ag()==(tn(),Wn)?-t.Mf().a:e}function Gk(n){var e;return n.b.c.length!=0&&u(sn(n.b,0),72).a?u(sn(n.b,0),72).a:(e=vN(n),e??""+(n.c?qr(n.c.a,n,0):-1))}function lA(n){var e;return n.f.c.length!=0&&u(sn(n.f,0),72).a?u(sn(n.f,0),72).a:(e=vN(n),e??""+(n.i?qr(n.i.j,n,0):-1))}function j7e(n,e){var t,i;if(e<0||e>=n.gc())return null;for(t=e;t0?n.c:0),r=y.Math.max(r,e.d),++i;n.e=c,n.b=r}function C7e(n){var e,t;if(!n.b)for(n.b=RM(u(n.f,123).kh().i),t=new ne(u(n.f,123).kh());t.e!=t.i.gc();)e=u(ce(t),135),nn(n.b,new pD(e));return n.b}function M7e(n,e){var t,i,r;if(e.dc())return m4(),m4(),aE;for(t=new LAn(n,e.gc()),r=new ne(n);r.e!=r.i.gc();)i=ce(r),e.Hc(i)&&ve(t,i);return t}function yZ(n,e,t,i){return e==0?i?(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),n.o):(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),uk(n.o)):tA(n,e,t,i)}function Hx(n){var e,t;if(n.rb)for(e=0,t=n.rb.i;e>22),r+=i>>22,r<0)?!1:(n.l=t&ro,n.m=i&ro,n.h=r&Il,!0)}function qx(n,e,t,i,r,c,s){var f,h;return!(e.Te()&&(h=n.a.Ne(t,i),h<0||!r&&h==0)||e.Ue()&&(f=n.a.Ne(t,c),f>0||!s&&f==0))}function P7e(n,e){cm();var t;if(t=n.j.g-e.j.g,t!=0)return 0;switch(n.j.g){case 2:return fx(e,Csn)-fx(n,Csn);case 4:return fx(n,Esn)-fx(e,Esn)}return 0}function I7e(n){switch(n.g){case 0:return Z_;case 1:return nH;case 2:return eH;case 3:return tH;case 4:return JP;case 5:return iH;default:return null}}function $r(n,e,t){var i,r;return i=(r=new lD,ad(r,e),zc(r,t),ve((!n.c&&(n.c=new q(yb,n,12,10)),n.c),r),r),e1(i,0),Zb(i,1),u1(i,!0),c1(i,!0),i}function Jp(n,e){var t,i;if(e>=n.i)throw M(new aL(e,n.i));return++n.j,t=n.g[e],i=n.i-e-1,i>0&&Ic(n.g,e+1,n.g,e,i),$t(n.g,--n.i,null),n.Qi(e,t),n.Ni(),t}function QRn(n,e){var t,i;return n.Db>>16==17?n.Cb.Th(n,21,Ts,e):(i=br(u($n((t=u(Un(n,16),29),t||n.ii()),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function O7e(n){var e,t,i,r;for(Dn(),Yt(n.c,n.a),r=new C(n.c);r.at.a.c.length))throw M(new Gn("index must be >= 0 and <= layer node count"));n.c&&du(n.c.a,n),n.c=t,t&&b0(t.a,e,n)}function tKn(n,e){var t,i,r;for(i=new te(re(Cl(n).a.Kc(),new En));pe(i);)return t=u(fe(i),18),r=u(e.Kb(t),10),new TE(Se(r.n.b+r.o.b/2));return n6(),n6(),KK}function iKn(n,e){this.c=new de,this.a=n,this.b=e,this.d=u(v(n,(W(),j2)),312),x(v(n,(cn(),shn)))===x((hk(),QP))?this.e=new Yyn:this.e=new Qyn}function P5(n,e){var t,i;return i=null,n.pf((qe(),$3))&&(t=u(n.of($3),96),t.pf(e)&&(i=t.of(e))),i==null&&n.Tf()&&(i=n.Tf().of(e)),i==null&&(i=rn(e)),i}function Ux(n,e){var t,i;t=n.fd(e);try{return i=t.Pb(),t.Qb(),i}catch(r){throw r=It(r),O(r,112)?M(new Ir("Can't remove element "+e)):M(r)}}function R7e(n,e){var t,i,r;if(i=new JE,r=new nY(i.q.getFullYear()-fa,i.q.getMonth(),i.q.getDate()),t=JPe(n,e,r),t==0||t0?e:0),++t;return new V(i,r)}function TZ(n,e){var t,i;return n.Db>>16==6?n.Cb.Th(n,6,Vt,e):(i=br(u($n((t=u(Un(n,16),29),t||(Cc(),bO)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function AZ(n,e){var t,i;return n.Db>>16==7?n.Cb.Th(n,1,oE,e):(i=br(u($n((t=u(Un(n,16),29),t||(Cc(),Pdn)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function SZ(n,e){var t,i;return n.Db>>16==9?n.Cb.Th(n,9,Qe,e):(i=br(u($n((t=u(Un(n,16),29),t||(Cc(),Odn)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function uKn(n,e){var t,i;return n.Db>>16==5?n.Cb.Th(n,9,EO,e):(i=br(u($n((t=u(Un(n,16),29),t||(On(),S1)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function oKn(n,e){var t,i;return n.Db>>16==7?n.Cb.Th(n,6,jf,e):(i=br(u($n((t=u(Un(n,16),29),t||(On(),I1)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function PZ(n,e){var t,i;return n.Db>>16==3?n.Cb.Th(n,0,fE,e):(i=br(u($n((t=u(Un(n,16),29),t||(On(),A1)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function sKn(){this.a=new dvn,this.g=new oA,this.j=new oA,this.b=new de,this.d=new oA,this.i=new oA,this.k=new de,this.c=new de,this.e=new de,this.f=new de}function H7e(n,e,t){var i,r,c;for(t<0&&(t=0),c=n.i,r=t;rPB)return mm(n,i);if(i==n)return!0}}return!1}function U7e(n){switch(KC(),n.q.g){case 5:U_n(n,(tn(),Xn)),U_n(n,ae);break;case 4:GHn(n,(tn(),Xn)),GHn(n,ae);break;default:VGn(n,(tn(),Xn)),VGn(n,ae)}}function G7e(n){switch(KC(),n.q.g){case 5:fHn(n,(tn(),Zn)),fHn(n,Wn);break;case 4:bRn(n,(tn(),Zn)),bRn(n,Wn);break;default:WGn(n,(tn(),Zn)),WGn(n,Wn)}}function z7e(n){var e,t;e=u(v(n,(qs(),nZn)),17),e?(t=e.a,t==0?U(n,(J1(),jP),new dx):U(n,(J1(),jP),new qM(t))):U(n,(J1(),jP),new qM(1))}function X7e(n,e){var t;switch(t=n.i,e.g){case 1:return-(n.n.b+n.o.b);case 2:return n.n.a-t.o.a;case 3:return n.n.b-t.o.b;case 4:return-(n.n.a+n.o.a)}return 0}function V7e(n,e){switch(n.g){case 0:return e==(Yo(),ka)?HP:qP;case 1:return e==(Yo(),ka)?HP:wj;case 2:return e==(Yo(),ka)?wj:qP;default:return wj}}function Xk(n,e){var t,i,r;for(du(n.a,e),n.e-=e.r+(n.a.c.length==0?0:n.c),r=Frn,i=new C(n.a);i.a>16==3?n.Cb.Th(n,12,Qe,e):(i=br(u($n((t=u(Un(n,16),29),t||(Cc(),Sdn)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function OZ(n,e){var t,i;return n.Db>>16==11?n.Cb.Th(n,10,Qe,e):(i=br(u($n((t=u(Un(n,16),29),t||(Cc(),Idn)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function fKn(n,e){var t,i;return n.Db>>16==10?n.Cb.Th(n,11,Ts,e):(i=br(u($n((t=u(Un(n,16),29),t||(On(),P1)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function hKn(n,e){var t,i;return n.Db>>16==10?n.Cb.Th(n,12,As,e):(i=br(u($n((t=u(Un(n,16),29),t||(On(),ig)),n.Db>>16),19)),n.Cb.Th(n,i.n,i.f,e))}function ws(n){var e;return!(n.Bb&1)&&n.r&&n.r.Vh()&&(e=u(n.r,54),n.r=u(na(n,e),142),n.r!=e&&n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,9,8,e,n.r))),n.r}function Gx(n,e,t){var i;return i=S(T(Pi,1),Tr,28,15,[inn(n,(bf(),bc),e,t),inn(n,Wc,e,t),inn(n,wc,e,t)]),n.f&&(i[0]=y.Math.max(i[0],i[2]),i[2]=i[0]),i}function W7e(n,e){var t,i,r;if(r=v9e(n,e),r.c.length!=0)for(Yt(r,new Pgn),t=r.c.length,i=0;i>19,l=e.h>>19,h!=l?l-h:(r=n.h,f=e.h,r!=f?r-f:(i=n.m,s=e.m,i!=s?i-s:(t=n.l,c=e.l,t-c)))}function aA(){aA=F,Xun=(NA(),f_),zun=new Mn(Otn,Xun),Gun=(cT(),s_),Uun=new Mn(Dtn,Gun),qun=(YT(),o_),Hun=new Mn(Ltn,qun),_un=new Mn(Ntn,(_n(),!0))}function I5(n,e,t){var i,r;i=e*t,O(n.g,154)?(r=xp(n),r.f.d?r.f.a||(n.d.a+=i+Kf):(n.d.d-=i+Kf,n.d.a+=i+Kf)):O(n.g,10)&&(n.d.d-=i,n.d.a+=2*i)}function lKn(n,e,t){var i,r,c,s,f;for(r=n[t.g],f=new C(e.d);f.a0?n.b:0),++t;e.b=i,e.e=r}function aKn(n){var e,t,i;if(i=n.b,iCn(n.i,i.length)){for(t=i.length*2,n.b=K(UK,Cy,303,t,0,1),n.c=K(UK,Cy,303,t,0,1),n.f=t-1,n.i=0,e=n.a;e;e=e.c)ty(n,e,e);++n.g}}function tke(n,e,t,i){var r,c,s,f;for(r=0;rs&&(f=s/i),r>c&&(h=c/r),rh(n,y.Math.min(f,h)),n}function rke(){KA();var n,e;try{if(e=u(HZ((R1(),Ss),tv),2113),e)return e}catch(t){if(t=It(t),O(t,103))n=t,OW((Ie(),n));else throw M(t)}return new fvn}function cke(){KA();var n,e;try{if(e=u(HZ((R1(),Ss),vs),2040),e)return e}catch(t){if(t=It(t),O(t,103))n=t,OW((Ie(),n));else throw M(t)}return new $vn}function uke(){jNn();var n,e;try{if(e=u(HZ((R1(),Ss),Sd),2122),e)return e}catch(t){if(t=It(t),O(t,103))n=t,OW((Ie(),n));else throw M(t)}return new S6n}function oke(n,e,t){var i,r;return r=n.e,n.e=e,n.Db&4&&!(n.Db&1)&&(i=new Ci(n,1,4,r,e),t?t.nj(i):t=i),r!=e&&(e?t=Nm(n,MA(n,e),t):t=Nm(n,n.a,t)),t}function dKn(){JE.call(this),this.e=-1,this.a=!1,this.p=Wi,this.k=-1,this.c=-1,this.b=-1,this.g=!1,this.f=-1,this.j=-1,this.n=-1,this.i=-1,this.d=-1,this.o=Wi}function ske(n,e){var t,i,r;if(i=n.b.d.d,n.a||(i+=n.b.d.a),r=e.b.d.d,e.a||(r+=e.b.d.a),t=bt(i,r),t==0){if(!n.a&&e.a)return-1;if(!e.a&&n.a)return 1}return t}function fke(n,e){var t,i,r;if(i=n.b.b.d,n.a||(i+=n.b.b.a),r=e.b.b.d,e.a||(r+=e.b.b.a),t=bt(i,r),t==0){if(!n.a&&e.a)return-1;if(!e.a&&n.a)return 1}return t}function hke(n,e){var t,i,r;if(i=n.b.g.d,n.a||(i+=n.b.g.a),r=e.b.g.d,e.a||(r+=e.b.g.a),t=bt(i,r),t==0){if(!n.a&&e.a)return-1;if(!e.a&&n.a)return 1}return t}function LZ(){LZ=F,mZn=Pu(Re(Re(Re(new ii,(Vi(),Kc),(tr(),fsn)),Kc,hsn),zr,lsn),zr,Yon),kZn=Re(Re(new ii,Kc,Gon),Kc,Zon),vZn=Pu(new ii,zr,esn)}function lke(n){var e,t,i,r,c;for(e=u(v(n,(W(),H8)),85),c=n.n,i=e.Cc().Kc();i.Ob();)t=u(i.Pb(),314),r=t.i,r.c+=c.a,r.d+=c.b,t.c?Dqn(t):Lqn(t);U(n,H8,null)}function ake(n,e,t){var i,r;switch(r=n.b,i=r.d,e.g){case 1:return-i.d-t;case 2:return r.o.a+i.c+t;case 3:return r.o.b+i.a+t;case 4:return-i.b-t;default:return-1}}function dke(n,e,t){var i,r;for(t.Ug("Interactive node placement",1),n.a=u(v(e,(W(),j2)),312),r=new C(e.b);r.a0&&(s=(c&et)%n.d.length,r=xnn(n,s,c,e),r)?(f=r.nd(t),f):(i=n.ck(c,e,t),n.c.Fc(i),null)}function xZ(n,e){var t,i,r,c;switch(r1(n,e).Kl()){case 3:case 2:{for(t=Wg(e),r=0,c=t.i;r=0;i--)if(An(n[i].d,e)||An(n[i].d,t)){n.length>=i+1&&n.splice(0,i+1);break}return n}function Wk(n,e){var t;return Vr(n)&&Vr(e)&&(t=n/e,Ay0&&(n.b+=2,n.a+=i):(n.b+=1,n.a+=y.Math.min(i,r))}function kKn(n){var e;e=u(v(u(Zo(n.b,0),40),(lc(),Iln)),107),U(n,(pt(),Dv),new V(0,0)),lUn(new rk,n,e.b+e.c-$(R(v(n,rq))),e.d+e.a-$(R(v(n,cq))))}function yKn(n,e){var t,i;if(i=!1,Ai(e)&&(i=!0,Ip(n,new qb(Oe(e)))),i||O(e,242)&&(i=!0,Ip(n,(t=IV(u(e,242)),new AE(t)))),!i)throw M(new vD(Lcn))}function Ike(n,e,t,i){var r,c,s;return r=new ml(n.e,1,10,(s=e.c,O(s,90)?u(s,29):(On(),Ps)),(c=t.c,O(c,90)?u(c,29):(On(),Ps)),f1(n,e),!1),i?i.nj(r):i=r,i}function RZ(n){var e,t;switch(u(v(Hi(n),(cn(),ehn)),429).g){case 0:return e=n.n,t=n.o,new V(e.a+t.a/2,e.b+t.b/2);case 1:return new rr(n.n);default:return null}}function Jk(){Jk=F,YP=new m6(kh,0),Ksn=new m6("LEFTUP",1),Hsn=new m6("RIGHTUP",2),Rsn=new m6("LEFTDOWN",3),_sn=new m6("RIGHTDOWN",4),rH=new m6("BALANCED",5)}function Oke(n,e,t){var i,r,c;if(i=bt(n.a[e.p],n.a[t.p]),i==0){if(r=u(v(e,(W(),T3)),15),c=u(v(t,T3),15),r.Hc(t))return-1;if(c.Hc(e))return 1}return i}function Dke(n){switch(n.g){case 1:return new U4n;case 2:return new G4n;case 3:return new q4n;case 0:return null;default:throw M(new Gn(GR+(n.f!=null?n.f:""+n.g)))}}function KZ(n,e,t){switch(e){case 1:!n.n&&(n.n=new q(Ar,n,1,7)),me(n.n),!n.n&&(n.n=new q(Ar,n,1,7)),Bt(n.n,u(t,16));return;case 2:X4(n,Oe(t));return}uY(n,e,t)}function _Z(n,e,t){switch(e){case 3:P0(n,$(R(t)));return;case 4:I0(n,$(R(t)));return;case 5:eu(n,$(R(t)));return;case 6:tu(n,$(R(t)));return}KZ(n,e,t)}function dA(n,e,t){var i,r,c;c=(i=new lD,i),r=Ff(c,e,null),r&&r.oj(),zc(c,t),ve((!n.c&&(n.c=new q(yb,n,12,10)),n.c),c),e1(c,0),Zb(c,1),u1(c,!0),c1(c,!0)}function HZ(n,e){var t,i,r;return t=d6(n.i,e),O(t,241)?(r=u(t,241),r.zi()==null,r.wi()):O(t,507)?(i=u(t,2037),r=i.b,r):null}function Lke(n,e,t,i){var r,c;return Se(e),Se(t),c=u(x6(n.d,e),17),VNn(!!c,"Row %s not in %s",e,n.e),r=u(x6(n.b,t),17),VNn(!!r,"Column %s not in %s",t,n.c),cFn(n,c.a,r.a,i)}function jKn(n,e,t,i,r,c,s){var f,h,l,a,d;if(a=r[c],l=c==s-1,f=l?i:0,d=_Rn(f,a),i!=10&&S(T(n,s-c),e[c],t[c],f,d),!l)for(++c,h=0;h1||f==-1?(c=u(h,15),r.Wb(g8e(n,c))):r.Wb(IF(n,u(h,58)))))}function Kke(n,e,t,i){DEn();var r=RK;function c(){for(var s=0;s0)return!1;return!0}function qke(n){var e,t,i,r,c;for(i=new sd(new qa(n.b).a);i.b;)t=L0(i),e=u(t.ld(),10),c=u(u(t.md(),42).a,10),r=u(u(t.md(),42).b,8),tt(sf(e.n),tt(Ki(c.n),r))}function Uke(n){switch(u(v(n.b,(cn(),Vfn)),387).g){case 1:qt(_r(rc(new Tn(null,new In(n.d,16)),new ypn),new jpn),new Epn);break;case 2:RAe(n);break;case 0:pEe(n)}}function Gke(n,e,t){var i,r,c;for(i=t,!i&&(i=new up),i.Ug("Layout",n.a.c.length),c=new C(n.a);c.a_R)return t;r>-1e-6&&++t}return t}function UZ(n,e){var t;e!=n.b?(t=null,n.b&&(t=OM(n.b,n,-4,t)),e&&(t=Wp(e,n,-4,t)),t=YFn(n,e,t),t&&t.oj()):n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,3,e,e))}function MKn(n,e){var t;e!=n.f?(t=null,n.f&&(t=OM(n.f,n,-1,t)),e&&(t=Wp(e,n,-1,t)),t=QFn(n,e,t),t&&t.oj()):n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,0,e,e))}function Wke(n,e,t,i){var r,c,s,f;return fo(n.e)&&(r=e.Lk(),f=e.md(),c=t.md(),s=X1(n,1,r,f,c,r.Jk()?Om(n,r,c,O(r,102)&&(u(r,19).Bb&hr)!=0):-1,!0),i?i.nj(s):i=s),i}function TKn(n){var e,t,i;if(n==null)return null;if(t=u(n,15),t.dc())return"";for(i=new Hl,e=t.Kc();e.Ob();)Er(i,(at(),Oe(e.Pb()))),i.a+=" ";return bL(i,i.a.length-1)}function AKn(n){var e,t,i;if(n==null)return null;if(t=u(n,15),t.dc())return"";for(i=new Hl,e=t.Kc();e.Ob();)Er(i,(at(),Oe(e.Pb()))),i.a+=" ";return bL(i,i.a.length-1)}function Jke(n,e,t){var i,r;return i=n.c[e.c.p][e.p],r=n.c[t.c.p][t.p],i.a!=null&&r.a!=null?tN(i.a,r.a):i.a!=null?-1:r.a!=null?1:0}function Qke(n,e,t){return t.Ug("Tree layout",1),U7(n.b),ff(n.b,(Qp(),LI),LI),ff(n.b,c9,c9),ff(n.b,u9,u9),ff(n.b,o9,o9),n.a=gy(n.b,e),Gke(n,e,t.eh(1)),t.Vg(),e}function Yke(n,e){var t,i,r,c,s,f;if(e)for(c=e.a.length,t=new Ja(c),f=(t.b-t.a)*t.c<0?(K1(),$a):new q1(t);f.Ob();)s=u(f.Pb(),17),r=L4(e,s.a),i=new Vkn(n),uge(i.a,r)}function Zke(n,e){var t,i,r,c,s,f;if(e)for(c=e.a.length,t=new Ja(c),f=(t.b-t.a)*t.c<0?(K1(),$a):new q1(t);f.Ob();)s=u(f.Pb(),17),r=L4(e,s.a),i=new Rkn(n),cge(i.a,r)}function nye(n){var e;if(n!=null&&n.length>0&&Xi(n,n.length-1)==33)try{return e=$Hn(qo(n,0,n.length-1)),e.e==null}catch(t){if(t=It(t),!O(t,33))throw M(t)}return!1}function eye(n,e,t){var i,r,c;switch(i=Hi(e),r=KT(i),c=new Pc,ic(c,e),t.g){case 1:gi(c,Bk(zp(r)));break;case 2:gi(c,zp(r))}return U(c,(cn(),Kw),R(v(n,Kw))),c}function GZ(n){var e,t;return e=u(fe(new te(re(ji(n.a).a.Kc(),new En))),18),t=u(fe(new te(re(Qt(n.a).a.Kc(),new En))),18),on(un(v(e,(W(),Gf))))||on(un(v(t,Gf)))}function ow(){ow=F,gj=new h7("ONE_SIDE",0),zP=new h7("TWO_SIDES_CORNER",1),XP=new h7("TWO_SIDES_OPPOSING",2),GP=new h7("THREE_SIDES",3),UP=new h7("FOUR_SIDES",4)}function SKn(n,e){var t,i,r,c;for(c=new Z,r=0,i=e.Kc();i.Ob();){for(t=Y(u(i.Pb(),17).a+r);t.a=n.f)break;Bn(c.c,t)}return c}function tye(n,e){var t,i,r,c,s;for(c=new C(e.a);c.a0&&YRn(this,this.c-1,(tn(),Zn)),this.c0&&n[0].length>0&&(this.c=on(un(v(Hi(n[0][0]),(W(),ifn))))),this.a=K(jie,J,2117,n.length,0,2),this.b=K(Eie,J,2118,n.length,0,2),this.d=new zFn}function oye(n){return n.c.length==0?!1:(Ln(0,n.c.length),u(n.c[0],18)).c.i.k==(Vn(),Mi)?!0:Ig(_r(new Tn(null,new In(n,16)),new t3n),new i3n)}function OKn(n,e){var t,i,r,c,s,f,h;for(f=aw(e),c=e.f,h=e.g,s=y.Math.sqrt(c*c+h*h),r=0,i=new C(f);i.a=0?(t=Wk(n,QA),i=Kk(n,QA)):(e=U1(n,1),t=Wk(e,5e8),i=Kk(e,5e8),i=nr(Fs(i,1),vi(n,1))),hf(Fs(i,32),vi(t,mr))}function NKn(n,e,t){var i,r;switch(i=(oe(e.b!=0),u(Xo(e,e.a.a),8)),t.g){case 0:i.b=0;break;case 2:i.b=n.f;break;case 3:i.a=0;break;default:i.a=n.g}return r=ge(e,0),q7(r,i),e}function $Kn(n,e,t,i){var r,c,s,f,h;switch(h=n.b,c=e.d,s=c.j,f=sZ(s,h.d[s.g],t),r=tt(Ki(c.n),c.a),c.j.g){case 1:case 3:f.a+=r.a;break;case 2:case 4:f.b+=r.b}xt(i,f,i.c.b,i.c)}function vye(n,e,t){var i,r,c,s;for(s=qr(n.e,e,0),c=new QG,c.b=t,i=new xi(n.e,s);i.b1;e>>=1)e&1&&(i=Pg(i,t)),t.d==1?t=Pg(t,t):t=new QBn(pUn(t.a,t.d,K(ye,Ke,28,t.d<<1,15,1)));return i=Pg(i,t),i}function nnn(){nnn=F;var n,e,t,i;for(Lun=K(Pi,Tr,28,25,15,1),Nun=K(Pi,Tr,28,33,15,1),i=152587890625e-16,e=32;e>=0;e--)Nun[e]=i,i*=.5;for(t=1,n=24;n>=0;n--)Lun[n]=t,t*=.5}function Mye(n){var e,t;if(on(un(z(n,(cn(),Rw))))){for(t=new te(re(Al(n).a.Kc(),new En));pe(t);)if(e=u(fe(t),74),_0(e)&&on(un(z(e,Nd))))return!0}return!1}function xKn(n,e){var t,i,r;fi(n.f,e)&&(e.b=n,i=e.c,qr(n.j,i,0)!=-1||nn(n.j,i),r=e.d,qr(n.j,r,0)!=-1||nn(n.j,r),t=e.a.b,t.c.length!=0&&(!n.i&&(n.i=new iRn(n)),Ive(n.i,t)))}function Tye(n){var e,t,i,r,c;return t=n.c.d,i=t.j,r=n.d.d,c=r.j,i==c?t.p=0&&An(n.substr(e,3),"GMT")||e>=0&&An(n.substr(e,3),"UTC"))&&(t[0]=e+3),Len(n,t,i)}function Sye(n,e){var t,i,r,c,s;for(c=n.g.a,s=n.g.b,i=new C(n.d);i.at;c--)n[c]|=e[c-t-1]>>>s,n[c-1]=e[c-t-1]<0&&Ic(n.g,e,n.g,e+i,f),s=t.Kc(),n.i+=i,r=0;r>4&15,c=n[i]&15,s[r++]=Ddn[t],s[r++]=Ddn[c];return hh(s,0,s.length)}function wu(n){var e,t;return n>=hr?(e=Sy+(n-hr>>10&1023)&ui,t=56320+(n-hr&1023)&ui,String.fromCharCode(e)+(""+String.fromCharCode(t))):String.fromCharCode(n&ui)}function Rye(n,e){Bb();var t,i,r,c;return r=u(u(ot(n.r,e),21),87),r.gc()>=2?(i=u(r.Kc().Pb(),117),t=n.u.Hc((zu(),P9)),c=n.u.Hc(B3),!i.a&&!t&&(r.gc()==2||c)):!1}function RKn(n,e,t,i,r){var c,s,f;for(c=Cqn(n,e,t,i,r),f=!1;!c;)EA(n,r,!0),f=!0,c=Cqn(n,e,t,i,r);f&&EA(n,r,!1),s=B$(r),s.c.length!=0&&(n.d&&n.d.Gg(s),RKn(n,r,t,i,s))}function pA(){pA=F,dU=new j6(kh,0),tdn=new j6("DIRECTED",1),rdn=new j6("UNDIRECTED",2),ndn=new j6("ASSOCIATION",3),idn=new j6("GENERALIZATION",4),edn=new j6("DEPENDENCY",5)}function Kye(n,e){var t;if(!Af(n))throw M(new Or(eWn));switch(t=Af(n),e.g){case 1:return-(n.j+n.f);case 2:return n.i-t.g;case 3:return n.j-t.f;case 4:return-(n.i+n.g)}return 0}function _ye(n,e,t){var i,r,c;return i=e.Lk(),c=e.md(),r=i.Jk()?X1(n,4,i,c,null,Om(n,i,c,O(i,102)&&(u(i,19).Bb&hr)!=0),!0):X1(n,i.tk()?2:1,i,c,i.ik(),-1,!0),t?t.nj(r):t=r,t}function ym(n,e){var t,i;for(Jn(e),i=n.b.c.length,nn(n.b,e);i>0;){if(t=i,i=(i-1)/2|0,n.a.Ne(sn(n.b,i),e)<=0)return Go(n.b,t,e),!0;Go(n.b,t,sn(n.b,i))}return Go(n.b,i,e),!0}function inn(n,e,t,i){var r,c;if(r=0,t)r=$T(n.a[t.g][e.g],i);else for(c=0;c=f)}function KKn(n){switch(n.g){case 0:return new cmn;case 1:return new umn;default:throw M(new Gn("No implementation is available for the width approximator "+(n.f!=null?n.f:""+n.g)))}}function rnn(n,e,t,i){var r;if(r=!1,Ai(i)&&(r=!0,j4(e,t,Oe(i))),r||Nb(i)&&(r=!0,rnn(n,e,t,i)),r||O(i,242)&&(r=!0,nd(e,t,u(i,242))),!r)throw M(new vD(Lcn))}function qye(n,e){var t,i,r;if(t=e.qi(n.a),t&&(r=wf((!t.b&&(t.b=new lo((On(),ar),pc,t)),t.b),ms),r!=null)){for(i=1;i<(Du(),t0n).length;++i)if(An(t0n[i],r))return i}return 0}function Uye(n,e){var t,i,r;if(t=e.qi(n.a),t&&(r=wf((!t.b&&(t.b=new lo((On(),ar),pc,t)),t.b),ms),r!=null)){for(i=1;i<(Du(),i0n).length;++i)if(An(i0n[i],r))return i}return 0}function _Kn(n,e){var t,i,r,c;if(Jn(e),c=n.a.gc(),c0?1:0;c.a[r]!=t;)c=c.a[r],r=n.a.Ne(t.d,c.d)>0?1:0;c.a[r]=i,i.b=t.b,i.a[0]=t.a[0],i.a[1]=t.a[1],t.a[0]=null,t.a[1]=null}function Xye(n){var e,t,i,r;for(e=new Z,t=K(so,Xh,28,n.a.c.length,16,1),TW(t,t.length),r=new C(n.a);r.a0&&dUn((Ln(0,t.c.length),u(t.c[0],30)),n),t.c.length>1&&dUn(u(sn(t,t.c.length-1),30),n),e.Vg()}function Wye(n){zu();var e,t;return e=yt(Fl,S(T(oO,1),G,279,0,[Pa])),!(jk(LM(e,n))>1||(t=yt(P9,S(T(oO,1),G,279,0,[S9,B3])),jk(LM(t,n))>1))}function unn(n,e){var t;t=Nc((R1(),Ss),n),O(t,507)?Dr(Ss,n,new LMn(this,e)):Dr(Ss,n,this),tF(this,e),e==(o4(),Udn)?(this.wb=u(this,2038),u(e,2040)):this.wb=(G1(),Hn)}function Jye(n){var e,t,i;if(n==null)return null;for(e=null,t=0;t=d1?"error":i>=900?"warn":i>=800?"info":"log"),nIn(t,n.a),n.b&&sen(e,t,n.b,"Exception: ",!0))}function v(n,e){var t,i;return i=(!n.q&&(n.q=new de),ee(n.q,e)),i??(t=e.Sg(),O(t,4)&&(t==null?(!n.q&&(n.q=new de),Bp(n.q,e)):(!n.q&&(n.q=new de),Xe(n.q,e,t))),t)}function Vi(){Vi=F,Xs=new f7("P1_CYCLE_BREAKING",0),Jh=new f7("P2_LAYERING",1),Oc=new f7("P3_NODE_ORDERING",2),Kc=new f7("P4_NODE_PLACEMENT",3),zr=new f7("P5_EDGE_ROUTING",4)}function Qye(n,e){r5();var t;if(n.c==e.c){if(n.b==e.b||rve(n.b,e.b)){if(t=Ple(n.b)?1:-1,n.a&&!e.a)return t;if(!n.a&&e.a)return-t}return jc(n.b.g,e.b.g)}else return bt(n.c,e.c)}function zKn(n,e){var t,i,r;if(snn(n,e))return!0;for(i=new C(e);i.a=r||e<0)throw M(new Ir(vK+e+Td+r));if(t>=r||t<0)throw M(new Ir(kK+t+Td+r));return e!=t?i=(c=n.Cj(t),n.qj(e,c),c):i=n.xj(t),i}function WKn(n){var e,t,i;if(i=n,n)for(e=0,t=n.Eh();t;t=t.Eh()){if(++e>PB)return WKn(t);if(i=t,t==n)throw M(new Or("There is a cycle in the containment hierarchy of "+n))}return i}function ra(n){var e,t,i;for(i=new fd(ur,"[","]"),t=n.Kc();t.Ob();)e=t.Pb(),pl(i,x(e)===x(n)?"(this Collection)":e==null?gu:Jr(e));return i.a?i.e.length==0?i.a.a:i.a.a+(""+i.e):i.c}function snn(n,e){var t,i;if(i=!1,e.gc()<2)return!1;for(t=0;t1&&(n.j.b+=n.e)):(n.j.a+=t.a,n.j.b=y.Math.max(n.j.b,t.b),n.d.c.length>1&&(n.j.a+=n.e))}function ca(){ca=F,une=S(T(lr,1),Mc,64,0,[(tn(),Xn),Zn,ae]),cne=S(T(lr,1),Mc,64,0,[Zn,ae,Wn]),one=S(T(lr,1),Mc,64,0,[ae,Wn,Xn]),sne=S(T(lr,1),Mc,64,0,[Wn,Xn,Zn])}function Zye(n,e,t,i){var r,c,s,f,h,l,a;if(s=n.c.d,f=n.d.d,s.j!=f.j)for(a=n.b,r=s.j,h=null;r!=f.j;)h=e==0?RT(r):SY(r),c=sZ(r,a.d[r.g],t),l=sZ(h,a.d[h.g],t),xe(i,tt(c,l)),r=h}function nje(n,e,t,i){var r,c,s,f,h;return s=ZRn(n.a,e,t),f=u(s.a,17).a,c=u(s.b,17).a,i&&(h=u(v(e,(W(),Xu)),10),r=u(v(t,Xu),10),h&&r&&(KDn(n.b,h,r),f+=n.b.i,c+=n.b.e)),f>c}function QKn(n){var e,t,i,r,c,s,f,h,l;for(this.a=kRn(n),this.b=new Z,t=n,i=0,r=t.length;iOL(n.d).c?(n.i+=n.g.c,px(n.d)):OL(n.d).c>OL(n.g).c?(n.e+=n.d.c,px(n.g)):(n.i+=sPn(n.g),n.e+=sPn(n.d),px(n.g),px(n.d))}function rje(n,e,t){var i,r,c,s;for(c=e.q,s=e.r,new ed((lf(),ja),e,c,1),new ed(ja,c,s,1),r=new C(t);r.af&&(h=f/i),r>c&&(l=c/r),s=y.Math.min(h,l),n.a+=s*(e.a-n.a),n.b+=s*(e.b-n.b)}function sje(n,e,t,i,r){var c,s;for(s=!1,c=u(sn(t.b,0),27);FPe(n,e,c,i,r)&&(s=!0,Bke(t,c),t.b.c.length!=0);)c=u(sn(t.b,0),27);return t.b.c.length==0&&Xk(t.j,t),s&&fA(e.q),s}function fje(n,e){Xg();var t,i,r,c;if(e.b<2)return!1;for(c=ge(e,0),t=u(be(c),8),i=t;c.b!=c.d.c;){if(r=u(be(c),8),mF(n,i,r))return!0;i=r}return!!mF(n,i,t)}function hnn(n,e,t,i){var r,c;return t==0?(!n.o&&(n.o=new Iu((Cc(),il),T1,n,0)),UC(n.o,e,i)):(c=u($n((r=u(Un(n,16),29),r||n.ii()),t),69),c.wk().Ak(n,iu(n),t-se(n.ii()),e,i))}function tF(n,e){var t;e!=n.sb?(t=null,n.sb&&(t=u(n.sb,54).Th(n,1,D9,t)),e&&(t=u(e,54).Rh(n,1,D9,t)),t=jY(n,e,t),t&&t.oj()):n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,4,e,e))}function hje(n,e){var t,i,r,c;if(e)r=yl(e,"x"),t=new Gkn(n),_4(t.a,(Jn(r),r)),c=yl(e,"y"),i=new zkn(n),q4(i.a,(Jn(c),c));else throw M(new nh("All edge sections need an end point."))}function lje(n,e){var t,i,r,c;if(e)r=yl(e,"x"),t=new Hkn(n),H4(t.a,(Jn(r),r)),c=yl(e,"y"),i=new qkn(n),U4(i.a,(Jn(c),c));else throw M(new nh("All edge sections need a start point."))}function aje(n,e){var t,i,r,c,s,f,h;for(i=AFn(n),c=0,f=i.length;c>22-e,r=n.h<>22-e):e<44?(t=0,i=n.l<>44-e):(t=0,i=0,r=n.l<n)throw M(new Gn("k must be smaller than n"));return e==0||e==n?1:n==0?0:FZ(n)/(FZ(e)*FZ(n-e))}function lnn(n,e){var t,i,r,c;for(t=new AX(n);t.g==null&&!t.c?cJ(t):t.g==null||t.i!=0&&u(t.g[t.i-1],51).Ob();)if(c=u(CA(t),58),O(c,167))for(i=u(c,167),r=0;r>4],e[t*2+1]=SO[c&15];return hh(e,0,e.length)}function Sje(n){yM();var e,t,i;switch(i=n.c.length,i){case 0:return rQn;case 1:return e=u(B_n(new C(n)),44),ybe(e.ld(),e.md());default:return t=u(xf(n,K(Pd,WA,44,n.c.length,0,1)),173),new hz(t)}}function Pje(n){var e,t,i,r,c,s;for(e=new Eg,t=new Eg,V1(e,n),V1(t,n);t.b!=t.c;)for(r=u(Sp(t),36),s=new C(r.a);s.a0&&hy(n,t,e),r):pCe(n,e,t)}function ua(){ua=F,fce=(qe(),N3),hce=qd,cce=Hd,uce=K2,oce=Ma,rce=R2,Jln=Wj,sce=Ww,kq=(Men(),Xre),yq=Vre,Yln=Yre,jq=ece,Zln=Zre,n1n=nce,Qln=Wre,_I=Jre,HI=Qre,Fj=tce,e1n=ice,Wln=zre}function c_n(n,e){var t,i,r,c,s;if(n.e<=e||Z2e(n,n.g,e))return n.g;for(c=n.r,i=n.g,s=n.r,r=(c-i)/2+i;i+11&&(n.e.b+=n.a)):(n.e.a+=t.a,n.e.b=y.Math.max(n.e.b,t.b),n.d.c.length>1&&(n.e.a+=n.a))}function Nje(n){var e,t,i,r;switch(r=n.i,e=r.b,i=r.j,t=r.g,r.a.g){case 0:t.a=(n.g.b.o.a-i.a)/2;break;case 1:t.a=e.d.n.a+e.d.a.a;break;case 2:t.a=e.d.n.a+e.d.a.a-i.a;break;case 3:t.b=e.d.n.b+e.d.a.b}}function $je(n,e,t){var i,r,c;for(r=new te(re(Cl(t).a.Kc(),new En));pe(r);)i=u(fe(r),18),!fr(i)&&!(!fr(i)&&i.c.i.c==i.d.i.c)&&(c=WHn(n,i,t,new Zyn),c.c.length>1&&Bn(e.c,c))}function o_n(n,e,t,i,r){if(ii&&(n.a=i),n.br&&(n.b=r),n}function xje(n){if(O(n,143))return dTe(u(n,143));if(O(n,233))return i8e(u(n,233));if(O(n,23))return bje(u(n,23));throw M(new Gn(Ncn+ra(new Ku(S(T(ki,1),Fn,1,5,[n])))))}function Fje(n,e,t,i,r){var c,s,f;for(c=!0,s=0;s>>r|t[s+i+1]<>>r,++s}return c}function wnn(n,e,t,i){var r,c,s;if(e.k==(Vn(),Mi)){for(c=new te(re(ji(e).a.Kc(),new En));pe(c);)if(r=u(fe(c),18),s=r.c.i.k,s==Mi&&n.c.a[r.c.i.c.p]==i&&n.c.a[e.c.p]==t)return!0}return!1}function Bje(n,e){var t,i,r,c;return e&=63,t=n.h&Il,e<22?(c=t>>>e,r=n.m>>e|t<<22-e,i=n.l>>e|n.m<<22-e):e<44?(c=0,r=t>>>e-22,i=n.m>>e-22|n.h<<44-e):(c=0,r=0,i=t>>>e-44),Yc(i&ro,r&ro,c&Il)}function s_n(n,e,t,i){var r;this.b=i,this.e=n==(O0(),t9),r=e[t],this.d=Va(so,[J,Xh],[183,28],16,[r.length,r.length],2),this.a=Va(ye,[J,Ke],[53,28],15,[r.length,r.length],2),this.c=new JZ(e,t)}function Rje(n){var e,t,i;for(n.k=new sJ((tn(),S(T(lr,1),Mc,64,0,[sc,Xn,Zn,ae,Wn])).length,n.j.c.length),i=new C(n.j);i.a=t)return Em(n,e,i.p),!0;return!1}function qg(n,e,t,i){var r,c,s,f,h,l;for(s=t.length,c=0,r=-1,l=e$n((zn(e,n.length+1),n.substr(e)),(xL(),Oun)),f=0;fc&&awe(l,e$n(t[f],Oun))&&(r=f,c=h);return r>=0&&(i[0]=e+c),r}function h_n(n){var e;return n.Db&64?iF(n):(e=new mo(Ecn),!n.a||Be(Be((e.a+=' "',e),n.a),'"'),Be(t0(Be(t0(Be(t0(Be(t0((e.a+=" (",e),n.i),","),n.j)," | "),n.g),","),n.f),")"),e.a)}function l_n(n,e,t){var i,r,c,s,f;for(f=ru(n.e.Dh(),e),r=u(n.g,124),i=0,s=0;st?Mnn(n,t,"start index"):e<0||e>t?Mnn(e,t,"end index"):H5("end index (%s) must not be less than start index (%s)",S(T(ki,1),Fn,1,5,[Y(e),Y(n)]))}function d_n(n,e){var t,i,r,c;for(i=0,r=n.length;i0&&b_n(n,c,t));e.p=0}function ln(n){var e;this.c=new Ct,this.f=n.e,this.e=n.d,this.i=n.g,this.d=n.c,this.b=n.b,this.k=n.j,this.a=n.a,n.i?this.j=n.i:this.j=(e=u(uf(Zh),9),new _o(e,u($s(e,e.length),9),0)),this.g=n.f}function Gje(n){var e,t,i,r;for(e=Ya(Be(new mo("Predicates."),"and"),40),t=!0,r=new Xv(n);r.b0?f[s-1]:K(Qh,b1,10,0,0,1),r=f[s],l=s=0?n.ki(r):Pnn(n,i);else throw M(new Gn(da+i.xe()+p8));else throw M(new Gn(aWn+e+dWn));else Wo(n,t,i)}function gnn(n){var e,t;if(t=null,e=!1,O(n,211)&&(e=!0,t=u(n,211).a),e||O(n,263)&&(e=!0,t=""+u(n,263).a),e||O(n,493)&&(e=!0,t=""+u(n,493).a),!e)throw M(new vD(Lcn));return t}function pnn(n,e,t){var i,r,c,s,f,h;for(h=ru(n.e.Dh(),e),i=0,f=n.i,r=u(n.g,124),s=0;s=n.d.b.c.length&&(e=new Lc(n.d),e.p=i.p-1,nn(n.d.b,e),t=new Lc(n.d),t.p=i.p,nn(n.d.b,t)),$i(i,u(sn(n.d.b,i.p),30))}function knn(n,e,t){var i,r,c;if(!n.b[e.g]){for(n.b[e.g]=!0,i=t,!i&&(i=new rk),xe(i.b,e),c=n.a[e.g].Kc();c.Ob();)r=u(c.Pb(),65),r.b!=e&&knn(n,r.b,i),r.c!=e&&knn(n,r.c,i),xe(i.a,r);return i}return null}function Wje(n){switch(n.g){case 0:case 1:case 2:return tn(),Xn;case 3:case 4:case 5:return tn(),ae;case 6:case 7:case 8:return tn(),Wn;case 9:case 10:case 11:return tn(),Zn;default:return tn(),sc}}function Jje(n,e){var t;return n.c.length==0?!1:(t=DBn((Ln(0,n.c.length),u(n.c[0],18)).c.i),ko(),t==(cw(),S2)||t==A2?!0:Ig(_r(new Tn(null,new In(n,16)),new r3n),new Y7n(e)))}function oF(n,e){if(O(e,207))return Ule(n,u(e,27));if(O(e,193))return Gle(n,u(e,123));if(O(e,452))return qle(n,u(e,166));throw M(new Gn(Ncn+ra(new Ku(S(T(ki,1),Fn,1,5,[e])))))}function k_n(n,e,t){var i,r;if(this.f=n,i=u(ee(n.b,e),260),r=i?i.a:0,BJ(t,r),t>=(r/2|0))for(this.e=i?i.c:null,this.d=r;t++0;)sQ(this);this.b=e,this.a=null}function Qje(n,e){var t,i;e.a?OTe(n,e):(t=u(ID(n.b,e.b),60),t&&t==n.a[e.b.f]&&t.a&&t.a!=e.b.a&&t.c.Fc(e.b),i=u(PD(n.b,e.b),60),i&&n.a[i.f]==e.b&&i.a&&i.a!=e.b.a&&e.b.c.Fc(i),EL(n.b,e.b))}function y_n(n,e){var t,i;if(t=u(Cr(n.b,e),127),u(u(ot(n.r,e),21),87).dc()){t.n.b=0,t.n.c=0;return}t.n.b=n.C.b,t.n.c=n.C.c,n.A.Hc((go(),Gd))&&Xqn(n,e),i=M9e(n,e),kF(n,e)==(Fg(),Aa)&&(i+=2*n.w),t.a.a=i}function j_n(n,e){var t,i;if(t=u(Cr(n.b,e),127),u(u(ot(n.r,e),21),87).dc()){t.n.d=0,t.n.a=0;return}t.n.d=n.C.d,t.n.a=n.C.a,n.A.Hc((go(),Gd))&&Vqn(n,e),i=C9e(n,e),kF(n,e)==(Fg(),Aa)&&(i+=2*n.w),t.a.b=i}function Yje(n,e){var t,i,r,c;for(c=new Z,i=new C(e);i.ai&&(zn(e-1,n.length),n.charCodeAt(e-1)<=32);)--e;return i>0||et.a&&(i.Hc((wd(),m9))?r=(e.a-t.a)/2:i.Hc(v9)&&(r=e.a-t.a)),e.b>t.b&&(i.Hc((wd(),y9))?c=(e.b-t.b)/2:i.Hc(k9)&&(c=e.b-t.b)),cnn(n,r,c)}function P_n(n,e,t,i,r,c,s,f,h,l,a,d,g){O(n.Cb,90)&&hw(Zu(u(n.Cb,90)),4),zc(n,t),n.f=s,hm(n,f),am(n,h),fm(n,l),lm(n,a),u1(n,d),dm(n,g),c1(n,!0),e1(n,r),n.Zk(c),ad(n,e),i!=null&&(n.i=null,kT(n,i))}function Mnn(n,e,t){if(n<0)return H5(Tzn,S(T(ki,1),Fn,1,5,[t,Y(n)]));if(e<0)throw M(new Gn(Azn+e));return H5("%s (%s) must not be greater than size (%s)",S(T(ki,1),Fn,1,5,[t,Y(n),Y(e)]))}function Tnn(n,e,t,i,r,c){var s,f,h,l;if(s=i-t,s<7){z5e(e,t,i,c);return}if(h=t+r,f=i+r,l=h+(f-h>>1),Tnn(e,n,h,l,-r,c),Tnn(e,n,l,f,-r,c),c.Ne(n[l-1],n[l])<=0){for(;t=0?n.bi(c,t):ten(n,r,t);else throw M(new Gn(da+r.xe()+p8));else throw M(new Gn(aWn+e+dWn));else Jo(n,i,r,t)}function I_n(n){var e,t;if(n.f){for(;n.n>0;){if(e=u(n.k.Xb(n.n-1),76),t=e.Lk(),O(t,102)&&u(t,19).Bb&kc&&(!n.e||t.pk()!=qv||t.Lj()!=0)&&e.md()!=null)return!0;--n.n}return!1}else return n.n>0}function O_n(n){var e,t,i,r;if(t=u(n,54)._h(),t)try{if(i=null,e=Mm((R1(),Ss),gUn(r8e(t))),e&&(r=e.ai(),r&&(i=r.Fl(che(t.e)))),i&&i!=n)return O_n(i)}catch(c){if(c=It(c),!O(c,63))throw M(c)}return n}function bEe(n,e,t){var i,r,c;t.Ug("Remove overlaps",1),t.dh(e,xrn),i=u(z(e,(Mg(),O2)),27),n.f=i,n.a=Ax(u(z(e,(ua(),Fj)),300)),r=R(z(e,(qe(),qd))),mG(n,(Jn(r),r)),c=aw(i),BGn(n,e,c,t),t.dh(e,DS)}function wEe(n){var e,t,i;if(on(un(z(n,(qe(),Xj))))){for(i=new Z,t=new te(re(Al(n).a.Kc(),new En));pe(t);)e=u(fe(t),74),_0(e)&&on(un(z(e,eU)))&&Bn(i.c,e);return i}else return Dn(),Dn(),sr}function D_n(n){if(!n)return Djn(),dQn;var e=n.valueOf?n.valueOf():n;if(e!==n){var t=WK[typeof e];return t?t(e):wY(typeof e)}else return n instanceof Array||n instanceof y.Array?new aG(n):new z9(n)}function L_n(n,e,t){var i,r,c;switch(c=n.o,i=u(Cr(n.p,t),252),r=i.i,r.b=$5(i),r.a=N5(i),r.b=y.Math.max(r.b,c.a),r.b>c.a&&!e&&(r.b=c.a),r.c=-(r.b-c.a)/2,t.g){case 1:r.d=-r.a;break;case 3:r.d=c.b}LF(i),NF(i)}function N_n(n,e,t){var i,r,c;switch(c=n.o,i=u(Cr(n.p,t),252),r=i.i,r.b=$5(i),r.a=N5(i),r.a=y.Math.max(r.a,c.b),r.a>c.b&&!e&&(r.a=c.b),r.d=-(r.a-c.b)/2,t.g){case 4:r.c=-r.b;break;case 2:r.c=c.a}LF(i),NF(i)}function gEe(n,e){var t,i,r,c,s;if(!e.dc()){if(r=u(e.Xb(0),131),e.gc()==1){lqn(n,r,r,1,0,e);return}for(t=1;t0)try{r=Ao(e,Wi,et)}catch(c){throw c=It(c),O(c,130)?(i=c,M(new eT(i))):M(c)}return t=(!n.a&&(n.a=new iD(n)),n.a),r=0?u(L(t,r),58):null}function kEe(n,e){if(n<0)return H5(Tzn,S(T(ki,1),Fn,1,5,["index",Y(n)]));if(e<0)throw M(new Gn(Azn+e));return H5("%s (%s) must be less than size (%s)",S(T(ki,1),Fn,1,5,["index",Y(n),Y(e)]))}function yEe(n){var e,t,i,r,c;if(n==null)return gu;for(c=new fd(ur,"[","]"),t=n,i=0,r=t.length;i",M(new Gn(i.a))}function NEe(n){var e,t;return t=-n.a,e=S(T(fs,1),gh,28,15,[43,48,48,48,48]),t<0&&(e[0]=45,t=-t),e[1]=e[1]+((t/60|0)/10|0)&ui,e[2]=e[2]+(t/60|0)%10&ui,e[3]=e[3]+(t%60/10|0)&ui,e[4]=e[4]+t%10&ui,hh(e,0,e.length)}function Snn(n){var e,t,i,r;for(n.g=new j5(u(Se(lr),297)),i=0,t=(tn(),Xn),e=0;e=0?n.Lh(t,!0,!0):H0(n,r,!0),160)),u(i,220).Zl(e);else throw M(new Gn(da+e.xe()+p8))}function Inn(n){var e,t;return n>-0x800000000000&&n<0x800000000000?n==0?0:(e=n<0,e&&(n=-n),t=wi(y.Math.floor(y.Math.log(n)/.6931471805599453)),(!e||n!=y.Math.pow(2,t))&&++t,t):Qxn(vc(n))}function xEe(n){var e,t,i,r,c,s,f;for(c=new ih,t=new C(n);t.a2&&f.e.b+f.j.b<=2&&(r=f,i=s),c.a.zc(r,c),r.q=i);return c}function FEe(n,e,t){t.Ug("Eades radial",1),t.dh(e,DS),n.d=u(z(e,(Mg(),O2)),27),n.c=$(R(z(e,(ua(),HI)))),n.e=Ax(u(z(e,Fj),300)),n.a=a8e(u(z(e,e1n),434)),n.b=Dke(u(z(e,Qln),354)),bke(n),t.dh(e,DS)}function BEe(n,e){if(e.Ug("Target Width Setter",1),Df(n,(Bf(),Nq)))ht(n,(_h(),Xw),R(z(n,Nq)));else throw M(new _l("A target width has to be set if the TargetWidthWidthApproximator should be used."));e.Vg()}function R_n(n,e){var t,i,r;return i=new Tl(n),Ur(i,e),U(i,(W(),cI),e),U(i,(cn(),Kt),(Oi(),qc)),U(i,Th,(Rh(),nO)),_a(i,(Vn(),Zt)),t=new Pc,ic(t,i),gi(t,(tn(),Wn)),r=new Pc,ic(r,i),gi(r,Zn),i}function K_n(n){switch(n.g){case 0:return new gD((O0(),Oj));case 1:return new i8n;case 2:return new r8n;default:throw M(new Gn("No implementation is available for the crossing minimizer "+(n.f!=null?n.f:""+n.g)))}}function __n(n,e){var t,i,r,c,s;for(n.c[e.p]=!0,nn(n.a,e),s=new C(e.j);s.a=c)s.$b();else for(r=s.Kc(),i=0;i0?wz():s<0&&G_n(n,e,-s),!0):!1}function N5(n){var e,t,i,r,c,s,f;if(f=0,n.b==0){for(s=ARn(n,!0),e=0,i=s,r=0,c=i.length;r0&&(f+=t,++e);e>1&&(f+=n.c*(e-1))}else f=Ujn(I$(Ub(ut(CW(n.a),new fbn),new hbn)));return f>0?f+n.n.d+n.n.a:0}function $5(n){var e,t,i,r,c,s,f;if(f=0,n.b==0)f=Ujn(I$(Ub(ut(CW(n.a),new obn),new sbn)));else{for(s=SRn(n,!0),e=0,i=s,r=0,c=i.length;r0&&(f+=t,++e);e>1&&(f+=n.c*(e-1))}return f>0?f+n.n.b+n.n.c:0}function GEe(n){var e,t;if(n.c.length!=2)throw M(new Or("Order only allowed for two paths."));e=(Ln(0,n.c.length),u(n.c[0],18)),t=(Ln(1,n.c.length),u(n.c[1],18)),e.d.i!=t.c.i&&(n.c.length=0,Bn(n.c,t),Bn(n.c,e))}function z_n(n,e,t){var i;for(vg(t,e.g,e.f),Ro(t,e.i,e.j),i=0;i<(!e.a&&(e.a=new q(Qe,e,10,11)),e.a).i;i++)z_n(n,u(L((!e.a&&(e.a=new q(Qe,e,10,11)),e.a),i),27),u(L((!t.a&&(t.a=new q(Qe,t,10,11)),t.a),i),27))}function zEe(n,e){var t,i,r,c;for(c=u(Cr(n.b,e),127),t=c.a,r=u(u(ot(n.r,e),21),87).Kc();r.Ob();)i=u(r.Pb(),117),i.c&&(t.a=y.Math.max(t.a,eW(i.c)));if(t.a>0)switch(e.g){case 2:c.n.c=n.s;break;case 4:c.n.b=n.s}}function XEe(n,e){var t,i,r;return t=u(v(e,(qs(),k3)),17).a-u(v(n,k3),17).a,t==0?(i=mi(Ki(u(v(n,(J1(),lj)),8)),u(v(n,$8),8)),r=mi(Ki(u(v(e,lj),8)),u(v(e,$8),8)),bt(i.a*i.b,r.a*r.b)):t}function VEe(n,e){var t,i,r;return t=u(v(e,(lc(),FI)),17).a-u(v(n,FI),17).a,t==0?(i=mi(Ki(u(v(n,(pt(),Nj)),8)),u(v(n,Dv),8)),r=mi(Ki(u(v(e,Nj),8)),u(v(e,Dv),8)),bt(i.a*i.b,r.a*r.b)):t}function X_n(n){var e,t;return t=new x1,t.a+="e_",e=_ve(n),e!=null&&(t.a+=""+e),n.c&&n.d&&(Be((t.a+=" ",t),lA(n.c)),Be(Dc((t.a+="[",t),n.c.i),"]"),Be((t.a+=iR,t),lA(n.d)),Be(Dc((t.a+="[",t),n.d.i),"]")),t.a}function V_n(n){switch(n.g){case 0:return new d8n;case 1:return new b8n;case 2:return new l8n;case 3:return new h8n;default:throw M(new Gn("No implementation is available for the layout phase "+(n.f!=null?n.f:""+n.g)))}}function Lnn(n,e,t,i,r){var c;switch(c=0,r.g){case 1:c=y.Math.max(0,e.b+n.b-(t.b+i));break;case 3:c=y.Math.max(0,-n.b-i);break;case 2:c=y.Math.max(0,-n.a-i);break;case 4:c=y.Math.max(0,e.a+n.a-(t.a+i))}return c}function WEe(n,e,t){var i,r,c,s,f;if(t)for(r=t.a.length,i=new Ja(r),f=(i.b-i.a)*i.c<0?(K1(),$a):new q1(i);f.Ob();)s=u(f.Pb(),17),c=L4(t,s.a),Acn in c.a||pK in c.a?fSe(n,c,e):SLe(n,c,e),A1e(u(ee(n.b,wm(c)),74))}function Nnn(n){var e,t;switch(n.b){case-1:return!0;case 0:return t=n.t,t>1||t==-1?(n.b=-1,!0):(e=ws(n),e&&(dr(),e.lk()==bJn)?(n.b=-1,!0):(n.b=1,!1));default:case 1:return!1}}function $nn(n,e){var t,i,r,c;if(Ye(n),n.c!=0||n.a!=123)throw M(new Le($e((Ie(),xWn))));if(c=e==112,i=n.d,t=w4(n.i,125,i),t<0)throw M(new Le($e((Ie(),FWn))));return r=qo(n.i,i,t),n.d=t+1,mNn(r,c,(n.e&512)==512)}function W_n(n){var e,t,i,r,c,s,f;if(i=n.a.c.length,i>0)for(s=n.c.d,f=n.d.d,r=rh(mi(new V(f.a,f.b),s),1/(i+1)),c=new V(s.a,s.b),t=new C(n.a);t.a=0&&i=0?n.Lh(t,!0,!0):H0(n,r,!0),160)),u(i,220).Wl(e);throw M(new Gn(da+e.xe()+sK))}function ZEe(){Fz();var n;return Yoe?u(Mm((R1(),Ss),vs),2038):(Ue(Pd,new k6n),VOe(),n=u(O(Nc((R1(),Ss),vs),560)?Nc(Ss,vs):new aIn,560),Yoe=!0,WLe(n),tNe(n),Xe((xz(),qdn),n,new xvn),Dr(Ss,vs,n),n)}function nCe(n,e){var t,i,r,c;n.j=-1,fo(n.e)?(t=n.i,c=n.i!=0,ik(n,e),i=new ml(n.e,3,n.c,null,e,t,c),r=e.zl(n.e,n.c,null),r=PKn(n,e,r),r?(r.nj(i),r.oj()):it(n.e,i)):(ik(n,e),r=e.zl(n.e,n.c,null),r&&r.oj())}function yA(n,e){var t,i,r;if(r=0,i=e[0],i>=n.length)return-1;for(t=(zn(i,n.length),n.charCodeAt(i));t>=48&&t<=57&&(r=r*10+(t-48),++i,!(i>=n.length));)t=(zn(i,n.length),n.charCodeAt(i));return i>e[0]?e[0]=i:r=-1,r}function eCe(n){var e,t,i,r,c;return r=u(n.a,17).a,c=u(n.b,17).a,t=r,i=c,e=y.Math.max(y.Math.abs(r),y.Math.abs(c)),r<=0&&r==c?(t=0,i=c-1):r==-e&&c!=e?(t=c,i=r,c>=0&&++t):(t=-c,i=r),new bi(Y(t),Y(i))}function tCe(n,e,t,i){var r,c,s,f,h,l;for(r=0;r=0&&l>=0&&h=n.i)throw M(new Ir(vK+e+Td+n.i));if(t>=n.i)throw M(new Ir(kK+t+Td+n.i));return i=n.g[t],e!=t&&(e>16),e=i>>16&16,t=16-e,n=n>>e,i=n-256,e=i>>16&8,t+=e,n<<=e,i=n-vw,e=i>>16&4,t+=e,n<<=e,i=n-wh,e=i>>16&2,t+=e,n<<=e,i=n>>14,e=i&~(i>>1),t+2-e)}function rCe(n){Lp();var e,t,i,r;for(mP=new Z,m_=new de,p_=new Z,e=(!n.a&&(n.a=new q(Qe,n,10,11)),n.a),VDe(e),r=new ne(e);r.e!=r.i.gc();)i=u(ce(r),27),qr(mP,i,0)==-1&&(t=new Z,nn(p_,t),ZBn(i,t));return p_}function cCe(n,e,t){var i,r,c,s;n.a=t.b.d,O(e,326)?(r=zg(u(e,74),!1,!1),c=Zk(r),i=new F9n(n),qi(c,i),dy(c,r),e.of((qe(),kb))!=null&&qi(u(e.of(kb),75),i)):(s=u(e,422),s.rh(s.nh()+n.a.a),s.sh(s.oh()+n.a.b))}function uCe(n,e){var t,i,r;for(r=new Z,i=ge(e.a,0);i.b!=i.d.c;)t=u(be(i),65),t.c.g==n.g&&x(v(t.b,(lc(),Sh)))!==x(v(t.c,Sh))&&!Ig(new Tn(null,new In(r,16)),new hkn(t))&&Bn(r.c,t);return Yt(r,new U3n),r}function Q_n(n,e,t){var i,r,c,s;return O(e,153)&&O(t,153)?(c=u(e,153),s=u(t,153),n.a[c.a][s.a]+n.a[s.a][c.a]):O(e,250)&&O(t,250)&&(i=u(e,250),r=u(t,250),i.a==r.a)?u(v(r.a,(qs(),k3)),17).a:0}function Y_n(n,e){var t,i,r,c,s,f,h,l;for(l=$(R(v(e,(cn(),J8)))),h=n[0].n.a+n[0].o.a+n[0].d.c+l,f=1;f=0?t:(f=X6(mi(new V(s.c+s.b/2,s.d+s.a/2),new V(c.c+c.b/2,c.d+c.a/2))),-(CUn(c,s)-1)*f)}function sCe(n,e,t){var i;qt(new Tn(null,(!t.a&&(t.a=new q(Mt,t,6,6)),new In(t.a,16))),new dMn(n,e)),qt(new Tn(null,(!t.n&&(t.n=new q(Ar,t,1,7)),new In(t.n,16))),new bMn(n,e)),i=u(z(t,(qe(),kb)),75),i&&BQ(i,n,e)}function H0(n,e,t){var i,r,c;if(c=Jg((Du(),zi),n.Dh(),e),c)return dr(),u(c,69).xk()||(c=$p(Lr(zi,c))),r=(i=n.Ih(c),u(i>=0?n.Lh(i,!0,!0):H0(n,c,!0),160)),u(r,220).Sl(e,t);throw M(new Gn(da+e.xe()+sK))}function xnn(n,e,t,i){var r,c,s,f,h;if(r=n.d[e],r){if(c=r.g,h=r.i,i!=null){for(f=0;f=t&&(i=e,l=(h.c+h.a)/2,s=l-t,h.c<=l-t&&(r=new KL(h.c,s),b0(n,i++,r)),f=l+t,f<=h.a&&(c=new KL(f,h.a),zb(i,n.c.length),b6(n.c,i,c)))}function eHn(n,e,t){var i,r,c,s,f,h;if(!e.dc()){for(r=new Ct,h=e.Kc();h.Ob();)for(f=u(h.Pb(),40),Xe(n.a,Y(f.g),Y(t)),s=(i=ge(new sl(f).a.d,0),new sg(i));Z9(s.a);)c=u(be(s.a),65).c,xt(r,c,r.c.b,r.c);eHn(n,r,t+1)}}function Fnn(n){var e;if(!n.c&&n.g==null)n.d=n.bj(n.f),ve(n,n.d),e=n.d;else{if(n.g==null)return!0;if(n.i==0)return!1;e=u(n.g[n.i-1],51)}return e==n.b&&null.Vm>=null.Um()?(CA(n),Fnn(n)):e.Ob()}function tHn(n){if(this.a=n,n.c.i.k==(Vn(),Zt))this.c=n.c,this.d=u(v(n.c.i,(W(),gc)),64);else if(n.d.i.k==Zt)this.c=n.d,this.d=u(v(n.d.i,(W(),gc)),64);else throw M(new Gn("Edge "+n+" is not an external edge."))}function iHn(n,e){var t,i,r;r=n.b,n.b=e,n.Db&4&&!(n.Db&1)&&it(n,new Ci(n,1,3,r,n.b)),e?e!=n&&(zc(n,e.zb),v$(n,e.d),t=(i=e.c,i??e.zb),y$(n,t==null||An(t,e.zb)?null:t)):(zc(n,null),v$(n,0),y$(n,null))}function rHn(n,e){var t;this.e=(m0(),Se(n),m0(),QY(n)),this.c=(Se(e),QY(e)),KX(this.e.Rd().dc()==this.c.Rd().dc()),this.d=vBn(this.e),this.b=vBn(this.c),t=Va(ki,[J,Fn],[5,1],5,[this.e.Rd().gc(),this.c.Rd().gc()],2),this.a=t,Fme(this)}function cHn(n){!XK&&(XK=uLe());var e=n.replace(/[\x00-\x1f\xad\u0600-\u0603\u06dd\u070f\u17b4\u17b5\u200b-\u200f\u2028-\u202e\u2060-\u2064\u206a-\u206f\ufeff\ufff9-\ufffb"\\]/g,function(t){return h2e(t)});return'"'+e+'"'}function Bnn(n,e,t,i,r,c){var s,f,h,l,a;if(r!=0)for(x(n)===x(t)&&(n=n.slice(e,e+r),e=0),h=t,f=e,l=e+r;f=s)throw M(new Kb(e,s));return r=t[e],s==1?i=null:(i=K(jU,MK,424,s-1,0,1),Ic(t,0,i,0,e),c=s-e-1,c>0&&Ic(t,e+1,i,e,c)),gm(n,i),S_n(n,e,r),r}function oHn(n){var e,t;if(n.f){for(;n.n0?c=zp(t):c=Bk(zp(t))),ht(e,Mv,c)}function wCe(n,e){var t;e.Ug("Partition preprocessing",1),t=u(Wr(ut(rc(ut(new Tn(null,new In(n.a,16)),new zgn),new Xgn),new Vgn),qu(new ju,new yu,new Eu,S(T(xr,1),G,108,0,[(Gu(),Yr)]))),15),qt(t.Oc(),new Wgn),e.Vg()}function gCe(n,e){var t,i,r,c,s;for(s=n.j,e.a!=e.b&&Yt(s,new Mpn),r=s.c.length/2|0,i=0;i0&&hy(n,t,e),c):i.a!=null?(hy(n,e,t),-1):r.a!=null?(hy(n,t,e),1):0}function mCe(n,e){var t,i,r,c,s;for(r=e.b.b,n.a=K(rs,kw,15,r,0,1),n.b=K(so,Xh,28,r,16,1),s=ge(e.b,0);s.b!=s.d.c;)c=u(be(s),40),n.a[c.g]=new Ct;for(i=ge(e.a,0);i.b!=i.d.c;)t=u(be(i),65),n.a[t.b.g].Fc(t),n.a[t.c.g].Fc(t)}function lHn(n,e){var t,i,r,c;n.Pj()?(t=n.Ej(),c=n.Qj(),++n.j,n.qj(t,n.Zi(t,e)),i=n.Ij(3,null,e,t,c),n.Mj()?(r=n.Nj(e,null),r?(r.nj(i),r.oj()):n.Jj(i)):n.Jj(i)):(eIn(n,e),n.Mj()&&(r=n.Nj(e,null),r&&r.oj()))}function Rnn(n,e,t){var i,r,c;n.Pj()?(c=n.Qj(),Nk(n,e,t),i=n.Ij(3,null,t,e,c),n.Mj()?(r=n.Nj(t,null),n.Tj()&&(r=n.Uj(t,r)),r?(r.nj(i),r.oj()):n.Jj(i)):n.Jj(i)):(Nk(n,e,t),n.Mj()&&(r=n.Nj(t,null),r&&r.oj()))}function jA(n,e){var t,i,r,c,s;for(s=ru(n.e.Dh(),e),r=new EE,t=u(n.g,124),c=n.i;--c>=0;)i=t[c],s.am(i.Lk())&&ve(r,i);!uzn(n,r)&&fo(n.e)&&t4(n,e.Jk()?X1(n,6,e,(Dn(),sr),null,-1,!1):X1(n,e.tk()?2:1,e,null,null,-1,!1))}function vCe(n,e){var t,i,r,c,s;return n.a==(jm(),R8)?!0:(c=e.a.c,t=e.a.c+e.a.b,!(e.j&&(i=e.A,s=i.c.c.a-i.o.a/2,r=c-(i.n.a+i.o.a),r>s)||e.q&&(i=e.C,s=i.c.c.a-i.o.a/2,r=i.n.a-t,r>s)))}function aHn(n){NN();var e,t,i,r,c,s,f;for(t=new Ql,r=new C(n.e.b);r.a1?n.e*=$(n.a):n.f/=$(n.a),_6e(n),X8e(n),UAe(n),U(n.b,(M5(),pP),n.g)}function gHn(n,e,t){var i,r,c,s,f,h;for(i=0,h=t,e||(i=t*(n.c.length-1),h*=-1),c=new C(n);c.a=0?n.Ah(null):n.Ph().Th(n,-1-e,null,null)),n.Bh(u(r,54),t),i&&i.oj(),n.vh()&&n.wh()&&t>-1&&it(n,new Ci(n,9,t,c,r)),r):c}function Hnn(n,e){var t,i,r,c,s;for(c=n.b.Ce(e),i=(t=n.a.get(c),t??K(ki,Fn,1,0,5,1)),s=0;s>5,r>=n.d)return n.e<0;if(t=n.a[r],e=1<<(e&31),n.e<0){if(i=Ixn(n),r>16)),15).dd(c),f0&&(!(hl(n.a.c)&&e.n.d)&&!(mg(n.a.c)&&e.n.b)&&(e.g.d+=y.Math.max(0,i/2-.5)),!(hl(n.a.c)&&e.n.a)&&!(mg(n.a.c)&&e.n.c)&&(e.g.a-=i-1))}function MHn(n){var e,t,i,r,c;if(r=new Z,c=kUn(n,r),e=u(v(n,(W(),Xu)),10),e)for(i=new C(e.j);i.a>e,c=n.m>>e|t<<22-e,r=n.l>>e|n.m<<22-e):e<44?(s=i?Il:0,c=t>>e-22,r=n.m>>e-22|t<<44-e):(s=i?Il:0,c=i?ro:0,r=t>>e-44),Yc(r&ro,c&ro,s&Il)}function bF(n){var e,t,i,r,c,s;for(this.c=new Z,this.d=n,i=St,r=St,e=li,t=li,s=ge(n,0);s.b!=s.d.c;)c=u(be(s),8),i=y.Math.min(i,c.a),r=y.Math.min(r,c.b),e=y.Math.max(e,c.a),t=y.Math.max(t,c.b);this.a=new Ho(i,r,e-i,t-r)}function AHn(n,e){var t,i,r,c,s,f;for(c=new C(n.b);c.a0&&O(e,44)&&(n.a._j(),l=u(e,44),h=l.ld(),c=h==null?0:mt(h),s=dV(n.a,c),t=n.a.d[s],t)){for(i=u(t.g,379),a=t.i,f=0;f=2)for(t=r.Kc(),e=R(t.Pb());t.Ob();)c=e,e=R(t.Pb()),i=y.Math.min(i,(Jn(e),e-(Jn(c),c)));return i}function _Ce(n,e){var t,i,r;for(r=new Z,i=ge(e.a,0);i.b!=i.d.c;)t=u(be(i),65),t.b.g==n.g&&!An(t.b.c,IS)&&x(v(t.b,(lc(),Sh)))!==x(v(t.c,Sh))&&!Ig(new Tn(null,new In(r,16)),new lkn(t))&&Bn(r.c,t);return Yt(r,new V3n),r}function HCe(n,e){var t,i,r;if(x(e)===x(Se(n)))return!0;if(!O(e,15)||(i=u(e,15),r=n.gc(),r!=i.gc()))return!1;if(O(i,59)){for(t=0;t0&&(r=t),s=new C(n.f.e);s.a0?(e-=1,t-=1):i>=0&&r<0?(e+=1,t+=1):i>0&&r>=0?(e-=1,t+=1):(e+=1,t-=1),new bi(Y(e),Y(t))}function tMe(n,e){return n.ce.c?1:n.be.b?1:n.a!=e.a?mt(n.a)-mt(e.a):n.d==(n5(),r9)&&e.d==i9?-1:n.d==i9&&e.d==r9?1:0}function NHn(n,e){var t,i,r,c,s;return c=e.a,c.c.i==e.b?s=c.d:s=c.c,c.c.i==e.b?i=c.c:i=c.d,r=C8e(n.a,s,i),r>0&&r0):r<0&&-r0):!1}function iMe(n,e,t,i){var r,c,s,f,h,l,a,d;for(r=(e-n.d)/n.c.c.length,c=0,n.a+=t,n.d=e,d=new C(n.c);d.a>24;return s}function cMe(n){if(n.ze()){var e=n.c;e.Ae()?n.o="["+e.n:e.ze()?n.o="["+e.xe():n.o="[L"+e.xe()+";",n.b=e.we()+"[]",n.k=e.ye()+"[]";return}var t=n.j,i=n.d;i=i.split("/"),n.o=mx(".",[t,mx("$",i)]),n.b=mx(".",[t,mx(".",i)]),n.k=i[i.length-1]}function uMe(n,e){var t,i,r,c,s;for(s=null,c=new C(n.e.a);c.a=0;e-=2)for(t=0;t<=e;t+=2)(n.b[t]>n.b[t+2]||n.b[t]===n.b[t+2]&&n.b[t+1]>n.b[t+3])&&(i=n.b[t+2],n.b[t+2]=n.b[t],n.b[t]=i,i=n.b[t+3],n.b[t+3]=n.b[t+1],n.b[t+1]=i);n.c=!0}}function fMe(n,e){var t,i,r,c,s,f,h,l,a;for(l=-1,a=0,s=n,f=0,h=s.length;f0&&++a;++l}return a}function _s(n){var e,t;return t=new mo(za(n.Rm)),t.a+="@",Be(t,(e=mt(n)>>>0,e.toString(16))),n.Vh()?(t.a+=" (eProxyURI: ",Dc(t,n._h()),n.Kh()&&(t.a+=" eClass: ",Dc(t,n.Kh())),t.a+=")"):n.Kh()&&(t.a+=" (eClass: ",Dc(t,n.Kh()),t.a+=")"),t.a}function B5(n){var e,t,i,r;if(n.e)throw M(new Or((ll(u_),FB+u_.k+BB)));for(n.d==(ci(),Wf)&&UA(n,Br),t=new C(n.a.a);t.a>24}return t}function aMe(n,e,t){var i,r,c;if(r=u(Cr(n.i,e),314),!r)if(r=new k$n(n.d,e,t),Pp(n.i,e,r),tZ(e))g1e(n.a,e.c,e.b,r);else switch(c=Wje(e),i=u(Cr(n.p,c),252),c.g){case 1:case 3:r.j=!0,mD(i,e.b,r);break;case 4:case 2:r.k=!0,mD(i,e.c,r)}return r}function dMe(n,e){var t,i,r,c,s,f,h,l,a;for(h=Dh(n.c-n.b&n.a.length-1),l=null,a=null,c=new W6(n);c.a!=c.b;)r=u(xT(c),10),t=(f=u(v(r,(W(),kf)),12),f?f.i:null),i=(s=u(v(r,js),12),s?s.i:null),(l!=t||a!=i)&&(pHn(h,e),l=t,a=i),Bn(h.c,r);pHn(h,e)}function bMe(n,e,t,i){var r,c,s,f,h,l;if(f=new EE,h=ru(n.e.Dh(),e),r=u(n.g,124),dr(),u(e,69).xk())for(s=0;s=0)return r;for(c=1,f=new C(e.j);f.a=0)return r;for(c=1,f=new C(e.j);f.a0&&e.Ne((Ln(r-1,n.c.length),u(n.c[r-1],10)),c)>0;)Go(n,r,(Ln(r-1,n.c.length),u(n.c[r-1],10))),--r;Ln(r,n.c.length),n.c[r]=c}t.a=new de,t.b=new de}function wMe(n,e,t){var i,r,c,s,f,h,l,a;for(a=(i=u(e.e&&e.e(),9),new _o(i,u($s(i,i.length),9),0)),h=ww(t,"[\\[\\]\\s,]+"),c=h,s=0,f=c.length;s