From 75631604d6802d84152f894a6c8029f447ee33e9 Mon Sep 17 00:00:00 2001 From: DeepThinkingLiuZhou Date: Sun, 5 Apr 2026 12:55:24 +0000 Subject: [PATCH 1/9] feat: refresh docker deployment and cu12 requirements --- .dockerignore | 3 + .gitignore | 1 + DEPLOY.md | 33 ++ Dockerfile | 21 +- README.md | 77 ++- README_CN.md | 76 ++- deploy/docker-build.sh | 11 + deploy/docker-down.sh | 10 + deploy/docker-logs.sh | 10 + deploy/docker-up.sh | 16 + deploy/docker.env.example | 28 ++ deploy/docker_env.sh | 40 ++ docker-compose.host.yml | 12 + docker-compose.yml | 116 ++++- docs/contributing.md | 5 +- docs/guides/open_source_deployment.md | 7 +- docs/installation.md | 9 +- docs/quickstart.md | 5 +- frontend-workflow/Dockerfile | 30 +- frontend-workflow/docker-nginx-start.sh | 12 + .../{nginx.conf => nginx.conf.template} | 8 +- requirements-base.txt | 6 + requirements-cu12.txt | 21 + requirements-paper-backup.txt | 451 +----------------- requirements-paper.txt | 32 +- requirements-system-ubuntu.txt | 26 + 26 files changed, 519 insertions(+), 547 deletions(-) create mode 100644 deploy/docker-build.sh create mode 100644 deploy/docker-down.sh create mode 100644 deploy/docker-logs.sh create mode 100644 deploy/docker-up.sh create mode 100644 deploy/docker.env.example create mode 100644 deploy/docker_env.sh create mode 100644 docker-compose.host.yml create mode 100644 frontend-workflow/docker-nginx-start.sh rename frontend-workflow/{nginx.conf => nginx.conf.template} (87%) create mode 100644 requirements-cu12.txt create mode 100644 requirements-system-ubuntu.txt diff --git a/.dockerignore b/.dockerignore index c9663b50..1e0620b0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,8 @@ .git .gitignore +fastapi_app/.env +deploy/docker.env +deploy/profiles/*.env __pycache__/ *.pyc *.pyo diff --git a/.gitignore b/.gitignore index fae185e7..5bd91f60 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,4 @@ deploy/precise_clean.sh # Runtime temp directories /tmp*/ /outputs/system/tmp/ +deploy/docker.env diff --git a/DEPLOY.md b/DEPLOY.md index 1a55cc75..16c5293a 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,5 +1,38 @@ # Paper2Any Deployment +## 0. 运行前依赖边界 + +这个项目现在需要区分 4 类依赖,不要再把它们都塞进一个 requirements 里: + +- `requirements-base.txt` + 通用 Python 运行时依赖。 +- `requirements-paper.txt` + 论文 / PDF / 科研绘图相关额外 Python 包。 +- `requirements-cu12.txt` + NVIDIA Linux + CUDA 12 的额外 GPU 运行时包。 +- `requirements-system-ubuntu.txt` + Ubuntu/Debian 系统工具包名,不是 Python 包。 + +几个关键事实: + +- `ffmpeg` +- `libreoffice/soffice` +- `inkscape` +- `poppler-utils` +- `wkhtmltopdf` +- `tectonic` + +这些都不是 `pip` 包。 + +当前 `deploy/start.sh` / `deploy/start_nv.sh` / `deploy/start_muxi.sh` 只负责: + +- 读取 profile +- 选择 Python +- 校验部分 Python 运行时 +- 启动模型服务 / 后端 / 前端 + +它们**不会自动安装系统包**,也**不会自动安装 npm / conda / pip 依赖**。 + ## 1. 配置文件职责 这个项目现在只保留三类配置文件,各管各的,不要重复写同一套 URL / key。 diff --git a/Dockerfile b/Dockerfile index e473cc24..e32d69b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,22 @@ -FROM python:3.11-slim +ARG PYTHON_BASE_IMAGE=python:3.11-slim +FROM ${PYTHON_BASE_IMAGE} + +ARG INSTALL_CUDA=0 WORKDIR /app ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_ROOT_USER_ACTION=ignore \ + PAPER2ANY_RUNTIME_TMPDIR=/app/outputs/system/tmp RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ + ca-certificates \ curl \ + ffmpeg \ git \ inkscape \ libgl1 \ @@ -29,13 +36,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm wkhtmltox_0.12.6.1-3.bookworm_amd64.deb \ && rm -rf /var/lib/apt/lists/* -COPY requirements-base.txt requirements-paper.txt requirements-paper-backup.txt ./ +COPY requirements-base.txt requirements-paper.txt requirements-paper-backup.txt requirements-cu12.txt ./ RUN pip install --upgrade pip && \ - (pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt) + pip install -r requirements-paper.txt && \ + if [ "$INSTALL_CUDA" = "1" ]; then pip install -r requirements-cu12.txt; fi COPY . . +RUN pip install -e . && \ + mkdir -p /app/outputs/system/tmp /app/models /app/logs /app/data /app/database /app/raw_data_store /app/rebuttal_sessions + EXPOSE 8000 -CMD ["uvicorn", "fastapi_app.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["python", "-m", "uvicorn", "fastapi_app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index 00e53443..dee03472 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,7 @@ cd Paper2Any # 2. Configure environment variables cp fastapi_app/.env.example fastapi_app/.env cp frontend-workflow/.env.example frontend-workflow/.env +cp deploy/docker.env.example deploy/docker.env ``` **Required configuration:** @@ -399,52 +400,59 @@ MINERU_API_KEY=your_mineru_api_key # Must match BACKEND_API_KEY in fastapi_app/.env VITE_API_KEY=your-backend-api-key -# Required: LLM API URLs available in the UI dropdown (comma separated) -VITE_DEFAULT_LLM_API_URL=https://api.openai.com/v1 -VITE_LLM_API_URLS=https://api.openai.com/v1 +# Usually keep VITE_API_BASE_URL empty in Docker, because nginx proxies /api and /outputs +VITE_API_BASE_URL= -# Optional: DrawIO page model candidates shown in the UI -VITE_PAPER2DRAWIO_MODEL=claude-sonnet-4-5-20250929,gpt-5.2 # Optional: Supabase (keep consistent with backend) # VITE_SUPABASE_URL=https://your-project-id.supabase.co # VITE_SUPABASE_ANON_KEY=your_supabase_anon_key ``` +`deploy/docker.env` (compose overrides): +```bash +BACKEND_PORT=8000 +FRONTEND_PORT=3000 +DOCKER_APP_WORKERS=1 + +# Optional: enable local SAM3 container by running DOCKER_WITH_SAM3=1 bash deploy/docker-up.sh +SAM3_PORT=8021 +SAM3_SERVER_URLS= +``` + ```bash # 3. Build + run -docker compose up -d --build +bash deploy/docker-up.sh ``` Open: - Frontend: http://localhost:3000 - Backend health: http://localhost:8000/health -> **GPU services note:** Docker only starts the frontend and backend. No GPU model services are included. +> **GPU services note:** Docker starts backend + frontend by default. > - Paper2PPT, Paper2Figure, Knowledge Base, etc. only need LLM APIs and work out of the box. -> - **PDF2PPT, Image2PPT, Image2Drawio** require the SAM3 segmentation service (needs GPU), deployed separately: +> - **PDF2PPT, Image2PPT, Image2Drawio** require SAM3 segmentation. +> - You can either point backend `.env` to an external SAM3 service with `SAM3_SERVER_URLS=...`, +> or start the optional local SAM3 compose profile: > ```bash -> # On a machine with GPU -> python -m dataflow_agent.toolkits.model_servers.sam3_server \ -> --port 8001 --checkpoint models/sam3/sam3.pt \ -> --bpe models/sam3/bpe_simple_vocab_16e6.txt.gz --device cuda +> DOCKER_WITH_SAM3=1 bash deploy/docker-up.sh > ``` -> Then add to `fastapi_app/.env`: `SAM3_SERVER_URLS=http://GPU_MACHINE_IP:8001` > > See the "Advanced: Local Model Server Load Balancing" section below for details. Modify & update: -- After changing code or `.env`, rebuild: `docker compose up -d --build` +- After changing code or `.env`, rebuild: `bash deploy/docker-up.sh` - Pull latest code and rebuild: - `git pull` - - `docker compose up -d --build` + - `bash deploy/docker-up.sh` Common commands: -- View logs: `docker compose logs -f` -- Stop services: `docker compose down` +- View logs: `bash deploy/docker-logs.sh` +- Stop services: `bash deploy/docker-down.sh` +- Build only: `bash deploy/docker-build.sh` Notes: - The first build may take a while (system deps + Python deps). -- Frontend env is baked at build time (compose build args). If you change it, rebuild with `docker compose up -d --build`. +- Frontend env is baked at build time. If you change `frontend-workflow/.env` or `deploy/docker.env`, rebuild with `bash deploy/docker-up.sh`. - Outputs/models are mounted to the host (`./outputs`, `./models`) for persistence. @@ -473,23 +481,37 @@ pip install -e . #### 2. Install Paper2Any-specific Dependencies (Required) -Paper2Any involves LaTeX rendering, vector graphics processing as well as PPT/PDF conversion, which require extra dependencies: +Paper2Any involves LaTeX rendering, vector graphics processing as well as PPT/PDF conversion, which require extra dependencies. + +The dependency boundary is now: +- `requirements-base.txt`: shared cross-platform Python runtime +- `requirements-paper.txt`: paper / PDF / figure extras +- `requirements-cu12.txt`: NVIDIA CUDA 12 Linux GPU extras +- `requirements-system-ubuntu.txt`: Ubuntu/Debian system packages, not Python packages ```bash -# 1. Python dependencies -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +# 1. Paper / PDF / figure Python extras +pip install -r requirements-paper.txt + +# 2. NVIDIA GPU runtime extras (Linux + CUDA 12 only) +pip install -r requirements-cu12.txt -# 2. LaTeX engine (tectonic) - recommended via conda +# 3. LaTeX engine (tectonic) - recommended via conda conda install -c conda-forge tectonic -y -# 3. Resolve doclayout_yolo dependency conflicts (Important) +# 4. Resolve doclayout_yolo dependency conflicts (Important) pip install doclayout_yolo --no-deps -# 4. System dependencies (Ubuntu example) +# 5. System dependencies (Ubuntu example; full list is mirrored in requirements-system-ubuntu.txt) sudo apt-get update -sudo apt-get install -y inkscape libreoffice poppler-utils wkhtmltopdf +sudo apt-get install -y ffmpeg inkscape libreoffice poppler-utils wkhtmltopdf ``` +> [!IMPORTANT] +> `ffmpeg`, `libreoffice/soffice`, `inkscape`, `poppler-utils`, `wkhtmltopdf`, and `tectonic` +> are external system tools. They are not installed by `pip`, and `deploy/start*.sh` +> does not auto-install them. + #### 3. Environment Variables ```bash @@ -656,12 +678,15 @@ pip install -e . #### 2. Install Paper2Any-specific Dependencies (Recommended) -Paper2Any involves LaTeX rendering and vector graphics processing, which require extra dependencies (see `requirements-paper.txt`): +Paper2Any involves LaTeX rendering and vector graphics processing, which require extra dependencies: ```bash # Python dependencies pip install -r requirements-paper.txt +# NVIDIA GPU runtime extras (Linux only; skip on Windows) +# pip install -r requirements-cu12.txt + # tectonic: LaTeX engine (recommended via conda) conda install -c conda-forge tectonic -y ``` diff --git a/README_CN.md b/README_CN.md index 0e487787..c9b97c00 100644 --- a/README_CN.md +++ b/README_CN.md @@ -356,6 +356,7 @@ cd Paper2Any # 2. 配置环境变量 cp fastapi_app/.env.example fastapi_app/.env cp frontend-workflow/.env.example frontend-workflow/.env +cp deploy/docker.env.example deploy/docker.env ``` **必须修改的配置项:** @@ -390,52 +391,59 @@ MINERU_API_KEY=your_mineru_api_key # 必须与后端 BACKEND_API_KEY 完全一致 VITE_API_KEY=your-backend-api-key -# 必填:前端可用的 LLM API 地址(逗号分隔,显示在 UI 下拉菜单中) -VITE_DEFAULT_LLM_API_URL=https://api.openai.com/v1 -VITE_LLM_API_URLS=https://api.openai.com/v1 +# Docker 下通常保持为空,由 nginx 反代 /api 和 /outputs +VITE_API_BASE_URL= -# 可选:DrawIO 页面展示的模型候选列表 -VITE_PAPER2DRAWIO_MODEL=claude-sonnet-4-5-20250929,gpt-5.2 # 可选:Supabase(与后端保持一致) # VITE_SUPABASE_URL=https://your-project-id.supabase.co # VITE_SUPABASE_ANON_KEY=your_supabase_anon_key ``` +`deploy/docker.env`(compose 覆盖项): +```bash +BACKEND_PORT=8000 +FRONTEND_PORT=3000 +DOCKER_APP_WORKERS=1 + +# 可选:本地 SAM3 容器端口 +SAM3_PORT=8021 +SAM3_SERVER_URLS= +``` + ```bash # 3. 构建并启动 -docker compose up -d --build +bash deploy/docker-up.sh ``` 访问地址: - 前端:http://localhost:3000 - 后端健康检查:http://localhost:8000/health -> **GPU 服务说明:** Docker 默认启动的是前后端服务,不包含 GPU 模型服务。 +> **GPU 服务说明:** Docker 默认启动后端 + 前端。 > - Paper2PPT、Paper2Figure、知识库等功能仅依赖 LLM API,Docker 启动后即可使用。 -> - **PDF2PPT、Image2PPT、Image2Drawio** 依赖 SAM3 图像分割服务(需要 GPU),需额外部署: +> - **PDF2PPT、Image2PPT、Image2Drawio** 依赖 SAM3 图像分割。 +> - 你可以在 `fastapi_app/.env` 里配置外部 `SAM3_SERVER_URLS=...`, +> 或者直接启用 compose 里的本地 SAM3 profile: > ```bash -> # 在有 GPU 的机器上启动 SAM3 服务 -> python -m dataflow_agent.toolkits.model_servers.sam3_server \ -> --port 8001 --checkpoint models/sam3/sam3.pt \ -> --bpe models/sam3/bpe_simple_vocab_16e6.txt.gz --device cuda +> DOCKER_WITH_SAM3=1 bash deploy/docker-up.sh > ``` -> 然后在 `fastapi_app/.env` 中添加:`SAM3_SERVER_URLS=http://GPU机器IP:8001` > > 详见下方「高级配置:本地模型服务负载均衡」部分。 修改与更新: -- 代码或 `.env` 变更后重新构建:`docker compose up -d --build` +- 代码或 `.env` 变更后重新构建:`bash deploy/docker-up.sh` - 拉取最新代码并重建: - `git pull` - - `docker compose up -d --build` + - `bash deploy/docker-up.sh` 常用命令: -- 查看日志:`docker compose logs -f` -- 停止服务:`docker compose down` +- 查看日志:`bash deploy/docker-logs.sh` +- 停止服务:`bash deploy/docker-down.sh` +- 只构建:`bash deploy/docker-build.sh` 说明: - 首次构建会比较慢(系统依赖 + Python 依赖)。 -- 前端配置在构建期生效(compose build args),修改后需重新 `docker compose up -d --build`。 +- 前端配置在构建期生效,修改 `frontend-workflow/.env` 或 `deploy/docker.env` 后需重新 `bash deploy/docker-up.sh`。 - 输出和模型目录会挂载到宿主机(`./outputs`、`./models`),数据不会丢。 @@ -464,23 +472,36 @@ pip install -e . #### 2. 安装 Paper2Any 相关依赖(必须) -Paper2Any 涉及 LaTeX 渲染、矢量图处理以及 PPT/PDF 转换,需要额外依赖: +Paper2Any 涉及 LaTeX 渲染、矢量图处理以及 PPT/PDF 转换,需要额外依赖。 + +当前依赖边界建议如下: +- `requirements-base.txt`:跨平台通用 Python 运行时 +- `requirements-paper.txt`:论文 / PDF / 科研绘图相关额外 Python 包 +- `requirements-cu12.txt`:NVIDIA CUDA 12 的 Linux GPU 额外依赖 +- `requirements-system-ubuntu.txt`:Ubuntu/Debian 系统包,不是 Python 包 ```bash -# 1. Python 依赖 -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +# 1. 论文 / PDF / 科研绘图额外 Python 依赖 +pip install -r requirements-paper.txt + +# 2. NVIDIA GPU 运行时额外依赖(仅 Linux + CUDA 12) +pip install -r requirements-cu12.txt -# 2. LaTeX 引擎 (tectonic) - 推荐用 conda 安装 +# 3. LaTeX 引擎 (tectonic) - 推荐用 conda 安装 conda install -c conda-forge tectonic -y -# 3. 解决 doclayout_yolo 依赖冲突(重要) +# 4. 解决 doclayout_yolo 依赖冲突(重要) pip install doclayout_yolo --no-deps -# 4. 系统依赖 (Ubuntu 示例) +# 5. 系统依赖 (Ubuntu 示例;完整列表见 requirements-system-ubuntu.txt) sudo apt-get update -sudo apt-get install -y inkscape libreoffice poppler-utils wkhtmltopdf +sudo apt-get install -y ffmpeg inkscape libreoffice poppler-utils wkhtmltopdf ``` +> [!IMPORTANT] +> `ffmpeg`、`libreoffice/soffice`、`inkscape`、`poppler-utils`、`wkhtmltopdf`、`tectonic` +> 这些都是系统工具,不是 `pip` 包;`deploy/start*.sh` 也不会自动安装它们。 + #### 3. 配置环境变量 ```bash @@ -644,12 +665,15 @@ pip install -e . #### 2. 安装 Paper2Any 相关依赖(推荐) -Paper2Any 涉及 LaTeX 渲染与矢量图处理,需要额外依赖(见 requirements-paper.txt): +Paper2Any 涉及 LaTeX 渲染与矢量图处理,需要额外依赖: ```bash # Python 依赖 pip install -r requirements-paper.txt +# NVIDIA GPU 运行时额外依赖(仅 Linux,需要时再装) +# pip install -r requirements-cu12.txt + # tectonic:LaTeX 引擎(推荐用 conda 安装) conda install -c conda-forge tectonic -y ``` diff --git a/deploy/docker-build.sh b/deploy/docker-build.sh new file mode 100644 index 00000000..72bb7f16 --- /dev/null +++ b/deploy/docker-build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" || exit 1 + +# shellcheck disable=SC1091 +source "$PROJECT_ROOT/deploy/docker_env.sh" + +docker compose "${COMPOSE_FILE_ARGS[@]}" build "$@" diff --git a/deploy/docker-down.sh b/deploy/docker-down.sh new file mode 100644 index 00000000..c27bcb70 --- /dev/null +++ b/deploy/docker-down.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" || exit 1 + +source "$PROJECT_ROOT/deploy/docker_env.sh" + +docker compose "${COMPOSE_FILE_ARGS[@]}" down "$@" diff --git a/deploy/docker-logs.sh b/deploy/docker-logs.sh new file mode 100644 index 00000000..099b090f --- /dev/null +++ b/deploy/docker-logs.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" || exit 1 + +source "$PROJECT_ROOT/deploy/docker_env.sh" + +docker compose "${COMPOSE_FILE_ARGS[@]}" logs -f "$@" diff --git a/deploy/docker-up.sh b/deploy/docker-up.sh new file mode 100644 index 00000000..5163cc0c --- /dev/null +++ b/deploy/docker-up.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" || exit 1 + +# shellcheck disable=SC1091 +source "$PROJECT_ROOT/deploy/docker_env.sh" + +extra_args=() +if [ "${DOCKER_WITH_SAM3:-0}" = "1" ]; then + extra_args+=(--profile sam3) +fi + +docker compose "${COMPOSE_FILE_ARGS[@]}" "${extra_args[@]}" up -d --build "$@" diff --git a/deploy/docker.env.example b/deploy/docker.env.example new file mode 100644 index 00000000..94905110 --- /dev/null +++ b/deploy/docker.env.example @@ -0,0 +1,28 @@ +# Docker / docker compose defaults + +BACKEND_PORT=8000 +FRONTEND_PORT=3000 +DOCKER_BACKEND_BIND_PORT=8000 +DOCKER_FRONTEND_BIND_PORT=80 +DOCKER_SAM3_BIND_PORT=8021 +DOCKER_HOST_NETWORK=0 +DOCKER_APP_WORKERS=1 +DOCKER_BACKEND_INSTALL_CUDA=0 +PYTHON_BASE_IMAGE=python:3.11-slim +NODE_BASE_IMAGE=node:20-alpine +NGINX_BASE_IMAGE=nginx:alpine + +# Optional local SAM3 container profile +SAM3_PORT=8021 +SAM3_HOME= +SAM3_CHECKPOINT_PATH= +SAM3_BPE_PATH= +SAM3_SERVER_URLS= + +# Optional frontend public build args overrides +VITE_API_KEY= +VITE_API_BASE_URL= +VITE_DEFAULT_LLM_API_URL= +VITE_LLM_API_URLS= +VITE_SUPABASE_URL= +VITE_SUPABASE_ANON_KEY= diff --git a/deploy/docker_env.sh b/deploy/docker_env.sh new file mode 100644 index 00000000..20f843a7 --- /dev/null +++ b/deploy/docker_env.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +load_env_file() { + local file="$1" + if [ -f "$file" ]; then + set -a + # shellcheck disable=SC1090 + source "$file" + set +a + fi +} + +# Compose-level defaults first. +load_env_file "$PROJECT_ROOT/deploy/docker.env.example" + +# Backend runtime vars used by env_file and optional SAM3 defaults. +load_env_file "$PROJECT_ROOT/fastapi_app/.env" + +# Frontend public defaults for static build. +load_env_file "$PROJECT_ROOT/frontend-workflow/.env" + +# Optional machine profile for local SAM3 path defaults. +if [ -f "$PROJECT_ROOT/deploy/profiles/nv.env" ]; then + load_env_file "$PROJECT_ROOT/deploy/profiles/nv.env" +elif [ -f "$PROJECT_ROOT/deploy/profiles/muxi.env" ]; then + load_env_file "$PROJECT_ROOT/deploy/profiles/muxi.env" +fi + +# Local docker overrides win last. +load_env_file "$PROJECT_ROOT/deploy/docker.env" + +COMPOSE_FILE_ARGS=(-f "$PROJECT_ROOT/docker-compose.yml") +if [ "${DOCKER_HOST_NETWORK:-0}" = "1" ]; then + COMPOSE_FILE_ARGS+=(-f "$PROJECT_ROOT/docker-compose.host.yml") +fi diff --git a/docker-compose.host.yml b/docker-compose.host.yml new file mode 100644 index 00000000..f3f3143f --- /dev/null +++ b/docker-compose.host.yml @@ -0,0 +1,12 @@ +services: + paper2any-backend: + network_mode: host + ports: !reset [] + + paper2any-frontend: + network_mode: host + ports: !reset [] + + paper2any-sam3: + network_mode: host + ports: !reset [] diff --git a/docker-compose.yml b/docker-compose.yml index 1e063e6a..e69c1db4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,55 @@ -version: "3.8" - services: paper2any-backend: build: context: . dockerfile: Dockerfile + args: + INSTALL_CUDA: ${DOCKER_BACKEND_INSTALL_CUDA:-0} + PYTHON_BASE_IMAGE: ${PYTHON_BASE_IMAGE:-python:3.11-slim} + image: paper2any-backend:local container_name: paper2any-backend - network_mode: host + env_file: + - ./fastapi_app/.env environment: - - PYTHONUNBUFFERED=1 - - RMBG_MODEL_PATH=/app/models/RMBG-2.0 + PYTHONUNBUFFERED: "1" + PAPER2ANY_RUNTIME_TMPDIR: /app/outputs/system/tmp + RMBG_MODEL_PATH: ${RMBG_MODEL_PATH:-/app/models/RMBG-2.0} + SAM3_SERVER_URLS: ${SAM3_SERVER_URLS:-} + SAM3_HOME: ${SAM3_HOME:-} + SAM3_CHECKPOINT_PATH: ${SAM3_CHECKPOINT_PATH:-/app/models/sam3/sam3.pt} + SAM3_BPE_PATH: ${SAM3_BPE_PATH:-/app/models/sam3/bpe_simple_vocab_16e6.txt.gz} + PAPER2DRAWIO_SAM3_CHECKPOINT_PATH: ${SAM3_CHECKPOINT_PATH:-/app/models/sam3/sam3.pt} + PAPER2DRAWIO_SAM3_BPE_PATH: ${SAM3_BPE_PATH:-/app/models/sam3/bpe_simple_vocab_16e6.txt.gz} + command: + - python + - -m + - uvicorn + - fastapi_app.main:app + - --host + - 0.0.0.0 + - --port + - "${DOCKER_BACKEND_BIND_PORT:-8000}" + - --workers + - "${DOCKER_APP_WORKERS:-1}" + - --log-level + - info volumes: - ./outputs:/app/outputs - ./models:/app/models - - ./fastapi_app/.env:/app/fastapi_app/.env + - ./data:/app/data + - ./database:/app/database + - ./logs:/app/logs + - ./raw_data_store:/app/raw_data_store + - ./rebuttal_sessions:/app/rebuttal_sessions + - ./fastapi_app/.env:/app/fastapi_app/.env:ro + ports: + - "${BACKEND_PORT:-8000}:${DOCKER_BACKEND_BIND_PORT:-8000}" + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:${DOCKER_BACKEND_BIND_PORT:-8000}/health"] + interval: 20s + timeout: 5s + retries: 10 + start_period: 30s restart: unless-stopped paper2any-frontend: @@ -21,13 +57,71 @@ services: context: . dockerfile: frontend-workflow/Dockerfile args: + NODE_BASE_IMAGE: ${NODE_BASE_IMAGE:-node:20-alpine} + NGINX_BASE_IMAGE: ${NGINX_BASE_IMAGE:-nginx:alpine} VITE_API_KEY: ${VITE_API_KEY:-} - VITE_DEFAULT_LLM_API_URL: ${VITE_DEFAULT_LLM_API_URL:-https://api.apiyi.com/v1} - VITE_LLM_API_URLS: ${VITE_LLM_API_URLS:-https://api.apiyi.com/v1,http://b.apiyi.com:16888/v1,http://123.129.219.111:3000/v1} VITE_API_BASE_URL: ${VITE_API_BASE_URL:-} + VITE_DEFAULT_LLM_API_URL: ${VITE_DEFAULT_LLM_API_URL:-} + VITE_LLM_API_URLS: ${VITE_LLM_API_URLS:-} + VITE_SUPABASE_URL: ${VITE_SUPABASE_URL:-} + VITE_SUPABASE_ANON_KEY: ${VITE_SUPABASE_ANON_KEY:-} + image: paper2any-frontend:local container_name: paper2any-frontend - ports: - - "3000:80" depends_on: - - paper2any-backend + paper2any-backend: + condition: service_healthy + environment: + NGINX_LISTEN_PORT: "${DOCKER_FRONTEND_BIND_PORT:-80}" + BACKEND_UPSTREAM_URL: "${BACKEND_UPSTREAM_URL:-http://paper2any-backend:${DOCKER_BACKEND_BIND_PORT:-8000}}" + ports: + - "${FRONTEND_PORT:-3000}:${DOCKER_FRONTEND_BIND_PORT:-80}" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:${DOCKER_FRONTEND_BIND_PORT:-80}/"] + interval: 20s + timeout: 5s + retries: 10 + start_period: 10s + restart: unless-stopped + + paper2any-sam3: + profiles: ["sam3"] + build: + context: . + dockerfile: Dockerfile + args: + INSTALL_CUDA: 1 + PYTHON_BASE_IMAGE: ${PYTHON_BASE_IMAGE:-python:3.11-slim} + image: paper2any-backend:local-gpu + container_name: paper2any-sam3 + environment: + PYTHONUNBUFFERED: "1" + SAM3_HOME: ${SAM3_HOME:-/app/models/sam3-official/sam3} + SAM3_CHECKPOINT_PATH: ${SAM3_CHECKPOINT_PATH:-/app/models/sam3/sam3.pt} + SAM3_BPE_PATH: ${SAM3_BPE_PATH:-/app/models/sam3/bpe_simple_vocab_16e6.txt.gz} + command: + - python + - -m + - dataflow_agent.toolkits.model_servers.sam3_server + - --host + - 0.0.0.0 + - --port + - "${DOCKER_SAM3_BIND_PORT:-8021}" + - --checkpoint + - "${SAM3_CHECKPOINT_PATH:-/app/models/sam3/sam3.pt}" + - --bpe + - "${SAM3_BPE_PATH:-/app/models/sam3/bpe_simple_vocab_16e6.txt.gz}" + - --device + - cuda + volumes: + - ./models:/app/models + - ./logs:/app/logs + ports: + - "${SAM3_PORT:-8021}:${DOCKER_SAM3_BIND_PORT:-8021}" + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:${DOCKER_SAM3_BIND_PORT:-8021}/health"] + interval: 20s + timeout: 5s + retries: 10 + start_period: 30s + gpus: all restart: unless-stopped diff --git a/docs/contributing.md b/docs/contributing.md index 2a99cc24..3e2230ea 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -26,7 +26,10 @@ conda create -n paper2any python=3.11 -y conda activate paper2any pip install --upgrade pip -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +pip install -r requirements-paper.txt + +# NVIDIA GPU 机器再额外安装 +pip install -r requirements-cu12.txt ``` ### 前端 diff --git a/docs/guides/open_source_deployment.md b/docs/guides/open_source_deployment.md index 47b4d796..75cc2af9 100644 --- a/docs/guides/open_source_deployment.md +++ b/docs/guides/open_source_deployment.md @@ -210,7 +210,10 @@ conda create -n paper2any python=3.11 -y conda activate paper2any pip install --upgrade pip -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +pip install -r requirements-paper.txt + +# NVIDIA GPU 机器再额外安装 +pip install -r requirements-cu12.txt ``` 如果你需要本地包开发模式: @@ -222,6 +225,8 @@ pip install -e . 说明: - `requirements-paper.txt` 已经包含 `requirements-base.txt` +- `requirements-cu12.txt` 只用于 NVIDIA Linux + CUDA 12 +- `requirements-system-ubuntu.txt` 列的是系统包,不是 Python 包 - 当前部署脚本会检查后端环境里是否至少有这些运行时依赖:`cv2`、`cairosvg`、`fastapi`、`moviepy`、`supabase`、`torch`、`uvicorn` ## 6.2 前端 Node 环境 diff --git a/docs/installation.md b/docs/installation.md index 884b0d94..6ddaf1c4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -44,6 +44,7 @@ sudo apt-get update sudo apt-get install -y \ build-essential \ curl \ + ffmpeg \ git \ inkscape \ libreoffice \ @@ -61,7 +62,8 @@ sudo apt-get install -y \ 说明: - `Inkscape`、`LibreOffice`、`poppler-utils` 对图形导出和文档转换链路很常见 -- 部分功能还会用到 `wkhtmltopdf` +- 部分功能还会用到 `ffmpeg`、`wkhtmltopdf`、`tectonic` +- `requirements-system-ubuntu.txt` 里列的是系统包名,不是 Python 包 ## 4. 后端安装 @@ -72,7 +74,10 @@ conda create -n paper2any python=3.11 -y conda activate paper2any pip install --upgrade pip -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +pip install -r requirements-paper.txt + +# NVIDIA GPU 机器再额外安装 +pip install -r requirements-cu12.txt ``` 可选: diff --git a/docs/quickstart.md b/docs/quickstart.md index 42a67a66..b72b2718 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -25,7 +25,10 @@ conda create -n paper2any python=3.11 -y conda activate paper2any pip install --upgrade pip -pip install -r requirements-paper.txt || pip install -r requirements-paper-backup.txt +pip install -r requirements-paper.txt + +# NVIDIA GPU 机器再额外安装 +pip install -r requirements-cu12.txt ``` ### 3. 准备前端 Node 环境 diff --git a/frontend-workflow/Dockerfile b/frontend-workflow/Dockerfile index 49acbd98..e1f2236c 100644 --- a/frontend-workflow/Dockerfile +++ b/frontend-workflow/Dockerfile @@ -1,18 +1,40 @@ -FROM node:20-alpine AS build +ARG NODE_BASE_IMAGE=node:20-alpine +FROM ${NODE_BASE_IMAGE} AS build + +ARG VITE_API_KEY= +ARG VITE_API_BASE_URL= +ARG VITE_DEFAULT_LLM_API_URL= +ARG VITE_LLM_API_URLS= +ARG VITE_SUPABASE_URL= +ARG VITE_SUPABASE_ANON_KEY= WORKDIR /app COPY frontend-workflow/package.json frontend-workflow/package-lock.json ./ RUN npm ci -# 复制前端源码(包含 .env 文件,Vite build 时会自动读取) +# 复制前端源码;若存在 frontend-workflow/.env,Vite build 也会读取。 COPY frontend-workflow/ ./ +RUN cp -f .env.example .env.production || true && \ + rm -f .env.production.local && \ + touch .env.production.local && \ + if [ -n "$VITE_API_KEY" ]; then echo "VITE_API_KEY=$VITE_API_KEY" >> .env.production.local; fi && \ + if [ -n "$VITE_API_BASE_URL" ]; then echo "VITE_API_BASE_URL=$VITE_API_BASE_URL" >> .env.production.local; fi && \ + if [ -n "$VITE_DEFAULT_LLM_API_URL" ]; then echo "VITE_DEFAULT_LLM_API_URL=$VITE_DEFAULT_LLM_API_URL" >> .env.production.local; fi && \ + if [ -n "$VITE_LLM_API_URLS" ]; then echo "VITE_LLM_API_URLS=$VITE_LLM_API_URLS" >> .env.production.local; fi && \ + if [ -n "$VITE_SUPABASE_URL" ]; then echo "VITE_SUPABASE_URL=$VITE_SUPABASE_URL" >> .env.production.local; fi && \ + if [ -n "$VITE_SUPABASE_ANON_KEY" ]; then echo "VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY" >> .env.production.local; fi + RUN npm run build -FROM nginx:alpine +ARG NGINX_BASE_IMAGE=nginx:alpine +FROM ${NGINX_BASE_IMAGE} -COPY frontend-workflow/nginx.conf /etc/nginx/conf.d/default.conf +COPY frontend-workflow/nginx.conf.template /etc/nginx/templates/default.conf.template +COPY frontend-workflow/docker-nginx-start.sh /docker-nginx-start.sh +RUN chmod +x /docker-nginx-start.sh COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80 +CMD ["/docker-nginx-start.sh"] diff --git a/frontend-workflow/docker-nginx-start.sh b/frontend-workflow/docker-nginx-start.sh new file mode 100644 index 00000000..9b845d09 --- /dev/null +++ b/frontend-workflow/docker-nginx-start.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu + +LISTEN_PORT="${NGINX_LISTEN_PORT:-80}" +BACKEND_URL="${BACKEND_UPSTREAM_URL:-http://paper2any-backend:8000}" + +sed \ + -e "s|__NGINX_LISTEN_PORT__|${LISTEN_PORT}|g" \ + -e "s|__BACKEND_UPSTREAM_URL__|${BACKEND_URL}|g" \ + /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf + +exec nginx -g 'daemon off;' diff --git a/frontend-workflow/nginx.conf b/frontend-workflow/nginx.conf.template similarity index 87% rename from frontend-workflow/nginx.conf rename to frontend-workflow/nginx.conf.template index 86646a2e..c95d7e3f 100644 --- a/frontend-workflow/nginx.conf +++ b/frontend-workflow/nginx.conf.template @@ -1,10 +1,10 @@ server { - listen 80; + listen __NGINX_LISTEN_PORT__; server_name _; client_max_body_size 200m; location /api/ { - proxy_pass http://paper2any-backend:8000; + proxy_pass __BACKEND_UPSTREAM_URL__; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -16,7 +16,7 @@ server { } location /paper2video/ { - proxy_pass http://paper2any-backend:8000; + proxy_pass __BACKEND_UPSTREAM_URL__; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -25,7 +25,7 @@ server { } location /outputs/ { - proxy_pass http://paper2any-backend:8000; + proxy_pass __BACKEND_UPSTREAM_URL__; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/requirements-base.txt b/requirements-base.txt index 442ef692..17d6f993 100644 --- a/requirements-base.txt +++ b/requirements-base.txt @@ -1,3 +1,9 @@ +# Cross-platform Python runtime dependencies. +# Paper-specific extras live in requirements-paper.txt. +# NVIDIA CUDA 12 runtime extras live in requirements-cu12.txt. +# OS-level packages such as ffmpeg / libreoffice / inkscape are not pip packages; +# see requirements-system-ubuntu.txt and DEPLOY.md. + open-dataflow==1.0.6 numpy==1.26.4 diff --git a/requirements-cu12.txt b/requirements-cu12.txt new file mode 100644 index 00000000..ba062881 --- /dev/null +++ b/requirements-cu12.txt @@ -0,0 +1,21 @@ +# NVIDIA CUDA 12 runtime extras for Linux GPU deployment. +# Install after requirements-base.txt and requirements-paper.txt. +# Do not mix with legacy cu11 packages in the same environment. +# This file is not intended for Windows or macOS. + +nvidia-cublas-cu12==12.8.4.1 +nvidia-cuda-cupti-cu12==12.8.90 +nvidia-cuda-nvrtc-cu12==12.8.93 +nvidia-cuda-runtime-cu12==12.8.90 +nvidia-cudnn-cu12==9.10.2.21 +nvidia-cufft-cu12==11.3.3.83 +nvidia-cufile-cu12==1.13.1.3 +nvidia-curand-cu12==10.3.9.90 +nvidia-cusolver-cu12==11.7.3.90 +nvidia-cusparse-cu12==12.5.8.93 +nvidia-cusparselt-cu12==0.7.1 +nvidia-nccl-cu12==2.27.5 +nvidia-nvjitlink-cu12==12.8.93 +nvidia-nvshmem-cu12==3.3.20 +nvidia-nvtx-cu12==12.8.90 +triton==3.6.0 diff --git a/requirements-paper-backup.txt b/requirements-paper-backup.txt index 2df579f4..0ac49e95 100644 --- a/requirements-paper-backup.txt +++ b/requirements-paper-backup.txt @@ -1,445 +1,8 @@ --r requirements-base.txt +# Deprecated compatibility file. +# Historical one-shot installer for Linux + Paper workflows + NVIDIA CUDA 12. +# New installs should use: +# pip install -r requirements-paper.txt +# pip install -r requirements-cu12.txt -accelerate==1.12.0 -addict==2.4.0 -aiofiles==24.1.0 -aiohappyeyeballs==2.6.1 -aiohttp==3.13.2 -aiosignal==1.4.0 -aistudio-sdk==0.3.8 -aisuite==0.1.14 -albucore==0.0.13 -# albumentations==2.0.8 -albumentations==1.4.10 -annotated-doc==0.0.4 -annotated-types==0.7.0 -anthropic==0.71.0 -antlr4-python3-runtime==4.9.3 -anyascii==0.3.3 -anyio==4.12.0 -apache-tvm-ffi==0.1.5 -appdirs==1.4.4 -astor==0.8.1 -asttokens==3.0.1 -attrs==25.4.0 -audioread==3.1.0 -av==16.0.1 -babel==2.17.0 -backoff==2.2.1 -backrefs==6.1 -bce-python-sdk==0.9.56 -beautifulsoup4==4.14.3 -blake3==1.0.8 -blis==1.3.3 -boto3==1.42.4 -botocore==1.42.4 -brotli==1.2.0 -cachetools==6.2.2 -cairocffi==1.7.1 -CairoSVG==2.8.2 -catalogue==2.0.10 -cbor2==5.7.1 -certifi==2025.11.12 -cffi==2.0.0 -chardet==5.2.0 -charset-normalizer==3.4.4 -chonkie==1.3.1 -click==8.2.1 -cloudpathlib==0.23.0 -cloudpickle==3.1.2 -colorama==0.4.6 -coloredlogs==15.0.1 -colorlog==6.10.1 -compressed-tensors==0.12.2 -confection==0.1.5 -contourpy==1.3.3 -contractions==0.1.73 -courlan==1.3.2 -cryptography==44.0.3 -cssselect==1.3.0 -cssselect2==0.8.0 -cssutils==2.11.1 -cuda-bindings==13.1.0 -cuda-pathfinder==1.3.3 -cuda-python==13.1.0 -cupy-cuda12x==13.6.0 -cycler==0.12.1 -cymem==2.0.13 -Cython==3.2.3 -dataclasses-json==0.6.7 -datasets==3.2.0 -datasketch==1.8.0 -dateparser==1.2.2 -decorator==5.2.1 -defusedxml==0.7.1 -Deprecated==1.3.1 -depyf==0.20.0 -dill==0.3.8 -diskcache==5.6.3 -distro==1.9.0 -dnspython==2.8.0 -# doclayout_yolo==0.0.4 # pip install doclayout_yolo --no-deps -docstring-parser==0.15 -editdistpy==0.1.6 -einops==0.8.1 -email-validator==2.3.0 -et_xmlfile==2.0.0 -evaluate==0.4.6 -executing==2.2.1 -faiss-cpu==1.12.0 -fast-langdetect==0.2.5 -fastapi==0.124.0 -fastapi-cli==0.0.16 -fastapi-cloud-cli==0.6.0 -fastar==0.8.0 -fastrlock==0.8.3 -fasttext-predict==0.9.2.4 -fasttext-wheel==0.9.2 -ffmpy==1.0.0 -filelock==3.20.0 -fire==0.7.1 -flashinfer-python==0.5.3 -flatbuffers==25.9.23 -fonttools==4.61.0 -frozenlist==1.8.0 -fsspec==2024.9.0 -ftfy==6.3.1 -func_timeout==4.3.5 -future==1.0.0 -gguf==0.17.1 -ghp-import==2.1.0 -google-api-core==2.28.1 -google-api-python-client==2.187.0 -google-auth==2.43.0 -google-auth-httplib2==0.2.1 -googleapis-common-protos==1.72.0 -gradio -gradio_client -gradio_pdf -greenlet==3.3.0 -groovy==0.1.2 -h11==0.16.0 -hf-xet==1.2.0 -htmldate==1.9.4 -httpcore==1.0.9 -httplib2==0.31.0 -httptools==0.7.1 -httpx==0.27.2 -httpx-retries==0.4.5 -httpx-sse==0.4.3 -huggingface-hub==0.36.0 -humanfriendly==10.0 -idna==3.11 -ImageIO==2.37.2 -imagesize==1.4.1 -img2pdf==0.6.3 -imgaug==0.4.0 -imgkit==1.2.3 -iniconfig==2.3.0 -interegular==0.3.3 -ipython==8.18.1 -jedi==0.19.2 -Jinja2==3.1.6 -jiter==0.12.0 -jmespath==1.0.1 -joblib==1.5.2 -json_repair==0.54.2 -jsonpatch==1.33 -jsonpointer==3.0.0 -jsonschema==4.25.1 -jsonschema-specifications==2025.9.1 -jusText==3.0.2 -kiwisolver==1.4.9 -kornia==0.8.2 -kornia_rs==0.1.10 -langchain==0.3.27 -langchain-community==0.3.29 -langchain-core==0.3.76 -langchain-openai==0.3.33 -langchain-text-splitters==0.3.11 -langgraph==0.6.7 -langgraph-checkpoint==2.1.1 -langgraph-prebuilt==0.6.4 -langgraph-sdk==0.2.6 -langkit==0.0.35 -langsmith==0.4.56 -lark==1.2.2 -latex2sympy2_extended==1.10.2 -lazy_loader==0.4 -librosa==0.11.0 -llguidance==1.3.0 -llvmlite==0.44.0 -lm-format-enforcer==0.11.3 -lmdb==1.7.5 -loguru==0.7.3 -lxml==6.0.2 -lxml_html_clean==0.4.3 -magika==1.0.1 -Markdown==3.10 -markdown-it-py==4.0.0 -MarkupSafe==3.0.3 -marshmallow==3.26.1 -math-verify==0.8.0 -matplotlib==3.10.7 -matplotlib-inline==0.2.1 -mdurl==0.1.2 -mergedeep==1.3.4 -mineru==2.6.6 -mineru_vl_utils==0.1.17 -mistral_common==1.8.6 -mkdocs==1.6.1 -mkdocs-get-deps==0.2.0 -mkdocs-material==9.7.0 -mkdocs-material-extensions==1.3.1 -model-hosting-container-standards==0.1.9 -modelscope==1.33.0 -more-itertools==10.8.0 -mpmath==1.3.0 -msgpack==1.1.2 -msgspec==0.20.0 -multidict==6.7.0 -multiprocess==0.70.16 -murmurhash==1.0.15 -mypy_extensions==1.1.0 -networkx==3.6 -ninja==1.13.0 -nltk==3.9.2 -numba==0.61.2 -numpy==1.26.4 -nvidia-cublas-cu11==11.11.3.6 -nvidia-cublas-cu12==12.8.4.1 -nvidia-cuda-cccl-cu12==12.6.77 -nvidia-cuda-cupti-cu11==11.8.87 -nvidia-cuda-cupti-cu12==12.8.90 -nvidia-cuda-nvrtc-cu11==11.8.89 -nvidia-cuda-nvrtc-cu12==12.8.93 -nvidia-cuda-runtime-cu11==11.8.89 -nvidia-cuda-runtime-cu12==12.8.90 -nvidia-cudnn-cu12==9.10.2.21 -nvidia-cudnn-frontend==1.16.0 -nvidia-cufft-cu11==10.9.0.58 -nvidia-cufft-cu12==11.3.3.83 -nvidia-cufile-cu12==1.13.1.3 -nvidia-curand-cu11==10.3.0.86 -nvidia-curand-cu12==10.3.9.90 -nvidia-cusolver-cu11==11.4.1.48 -nvidia-cusolver-cu12==11.7.3.90 -nvidia-cusparse-cu11==11.7.5.86 -nvidia-cusparse-cu12==12.5.8.93 -nvidia-cusparselt-cu12==0.7.1 -nvidia-cutlass-dsl==4.3.2 -nvidia-ml-py==13.590.44 -nvidia-nccl-cu11==2.19.3 -nvidia-nccl-cu12==2.27.5 -nvidia-nvjitlink-cu12==12.8.93 -nvidia-nvshmem-cu12==3.3.20 -nvidia-nvtx-cu11==11.8.86 -nvidia-nvtx-cu12==12.8.90 -omegaconf==2.3.0 -onnxruntime==1.23.2 -open-dataflow==1.0.6 -openai==1.109.1 -openai-harmony==0.0.8 -opencv-contrib-python==4.10.0.84 -opencv-python==4.11.0.86 -opencv-python-headless==4.11.0.86 -openpyxl==3.1.5 -opt-einsum==3.3.0 -orjson==3.11.5 -ormsgpack==1.12.0 -outlines_core==0.2.11 -packaging==25.0 -# paddleocr==2.9.1 # 懒加载,仅旧版 workflow 需要 -# paddlepaddle==3.2.0 # 懒加载,仅旧版 workflow 需要 -paginate==0.5.7 -pandas==2.3.3 -parso==0.8.5 -partial-json-parser==0.2.1.1.post7 -pathspec==0.12.1 -pdf2image==1.17.0 -pdfminer.six==20250506 -pdfplumber==0.11.7 -pdftext==0.6.3 -pexpect==4.9.0 -phonenumbers==9.0.20 -pikepdf==10.0.3 -pillow==11.3.0 -platformdirs==3.11.0 -playwright==1.56.0 -pluggy==1.6.0 -polars==1.35.2 -polars-runtime-32==1.35.2 -pooch==1.8.2 -premailer==3.10.0 -preshed==3.0.12 -presidio_analyzer==2.2.360 -presidio_anonymizer==2.2.360 -prettytable==3.17.0 -prometheus-fastapi-instrumentator==7.1.0 -prometheus_client==0.23.1 -prompt_toolkit==3.0.52 -propcache==0.4.1 -proto-plus==1.26.1 -protobuf==6.33.2 -psutil==7.1.3 -ptyprocess==0.7.0 -pure_eval==0.2.3 -py-cpuinfo==9.0.0 -pyahocorasick==2.2.0 -pyarrow==22.0.0 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pybase64==1.4.3 -pybind11==3.0.1 -pyclipper==1.4.0 -pycountry==24.6.1 -pycparser==2.23 -pycryptodome==3.23.0 -pydantic -pydantic-extra-types -pydantic-settings -pydantic_core -pydub==0.25.1 -pydyf==0.12.1 -pyee==13.0.0 -pyfiglet==1.0.4 -Pygments==2.19.2 -pymdown-extensions==10.18 -PyMuPDF==1.26.6 -PyMySQL==1.1.2 -pyparsing==3.2.5 -pypdf==6.4.1 -PyPDF2==3.0.1 -pypdfium2==4.30.0 -pyphen==0.17.2 -pytesseract==0.3.13 -pytest==9.0.2 -python-bidi==0.6.7 -python-dateutil==2.9.0.post0 -python-docx==1.2.0 -python-dotenv==1.2.1 -python-json-logger==4.0.0 -python-multipart==0.0.20 -python-pptx==1.0.2 -pytz==2025.2 -PyYAML==6.0.2 -pyyaml_env_tag==1.1 -pyzmq==27.1.0 -qwen-vl-utils==0.0.14 -RapidFuzz==3.14.3 -ray==2.52.1 -referencing==0.37.0 -regex==2025.11.3 -reportlab==4.4.5 -requests==2.32.5 -requests-file==3.0.1 -requests-toolbelt==1.0.0 -rich==14.2.0 -rich-toolkit==0.17.0 -rignore==0.7.6 -robust-downloader==0.0.2 -rpds-py==0.30.0 -rsa==4.9.1 -ruamel.yaml==0.18.16 -ruamel.yaml.clib==0.2.15 -ruff==0.14.8 -s3transfer==0.16.0 -safehttpx==0.1.7 -safetensors==0.7.0 -scikit-image==0.25.2 -scikit-learn==1.7.2 -scipy==1.16.3 -seaborn==0.13.2 -semantic-version==2.10.0 -sentencepiece==0.2.1 -sentry-sdk==2.47.0 -setproctitle==1.3.7 -shapely==2.1.2 -shellingham==1.5.4 -simhash==2.1.2 -simsimd==6.5.3 -six==1.17.0 -smart_open==7.5.0 -sniffio==1.3.1 -socksio==1.0.0 -soundfile==0.13.1 -soupsieve==2.8 -soxr==1.0.0 -spacy==3.8.11 -spacy-huggingface-pipelines==0.0.4 -spacy-legacy==3.0.12 -spacy-loggers==1.0.5 -SQLAlchemy==2.0.44 -sqlglot==28.1.0 -srsly==2.5.2 -sseclient-py==1.8.0 -stack-data==0.6.3 -starlette==0.50.0 -stringzilla==4.4.0 -supervisor==4.3.0 -syllapy==0.7.2 -sympy==1.14.0 -symspellpy==6.9.0 -tabulate==0.9.0 -tenacity==9.1.2 -termcolor==3.2.0 -textsearch==0.0.24 -thinc==8.3.10 -thop==0.1.1.post2209072238 -threadpoolctl==3.6.0 -tifffile==2025.10.16 -tiktoken==0.12.0 -timm==1.0.22 -tinycss2==1.5.1 -tinyhtml5==2.0.0 -tld==0.13.1 -tldextract==5.3.0 -tokenizers==0.22.1 -tomli==2.3.0 -tomlkit==0.13.3 -torch -torchaudio -torchvision -tqdm==4.67.1 -trafilatura==2.0.0 -traitlets==5.14.3 -transformers==4.57.3 -triton==3.5.0 -typer==0.20.0 -typer-slim==0.20.0 -typing-inspect==0.9.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -tzdata==2025.2 -tzlocal==5.3.1 -ujson==5.11.0 -ultralytics==8.3.235 -ultralytics-thop==2.0.18 -uritemplate==4.2.0 -urllib3==2.6.0 -uuid_utils==0.12.0 -uvicorn==0.38.0 -uvloop==0.22.1 -vendi-score==0.0.3 -vllm -vtracer==0.6.11 -wasabi==1.1.3 -watchdog==6.0.0 -watchfiles==1.1.1 -wcwidth==0.2.14 -weasel==0.4.3 -weasyprint==67.0 -webencodings==0.5.1 -websockets==15.0.1 -whylabs-client==0.6.16 -whylabs-textstat==0.7.4 -whylogs==1.6.4 -whylogs-sketching==3.4.1.dev3 -word2number==1.1 -wrapt==2.0.1 -xgrammar==0.1.27 -xlsxwriter==3.2.9 -xxhash==3.6.0 -yarl==1.22.0 -zopfli==0.4.0 -zstandard==0.25.0 +-r requirements-paper.txt +-r requirements-cu12.txt diff --git a/requirements-paper.txt b/requirements-paper.txt index fc9b37a6..e36a82c2 100644 --- a/requirements-paper.txt +++ b/requirements-paper.txt @@ -1,31 +1,23 @@ -r requirements-base.txt -# paper 系(NLP / 文本 / LLM 实验)特有或易变更依赖,请在这里追加或覆盖版本 -# transformers==4.46.0 -# datasets==3.2.0 -# evaluate==0.4.3 -# vendi-score==0.0.3 - - +# Paper / figure / PDF workflow extras beyond the shared base runtime. +# CUDA-specific GPU runtime packages are intentionally split into +# requirements-cu12.txt. +# Linux system packages are intentionally split into +# requirements-system-ubuntu.txt and DEPLOY.md. +# # system dependency: -# tectonic (TeX engine) must be installed via conda-forge/apt/cargo - -#sudo apt-get update -# sudo apt-get install -y inkscape -# sudo apt-get install -y tectonic +# tectonic (TeX engine) should be installed via conda-forge or another +# OS/package-manager channel, not via pip. # Paper2Figure pdfplumber -transformers==4.57.3 -# mineru[core] mineru-vl-utils -python-pptx PyPDF2 -kornia +kornia timm lmdb>=1.4.1 -cairosvg vtracer ultralytics scikit-image @@ -35,14 +27,10 @@ imgaug>=0.4.0 pillow # pytesseract # 已改用 PaddleOCR -# Paddle OCR (CPU) — 懒加载,仅旧版 workflow 需要,Docker 构建可跳过 +# Paddle OCR (CPU) — 懒加载,仅旧版 workflow 需要,通常不必装进默认环境 # paddlepaddle==3.2.0 # paddleocr==2.9.1 -PyMuPDF -img2pdf -reportlab imgkit==1.2.3 seaborn==0.13.2 -json_repair==0.54.2 diff --git a/requirements-system-ubuntu.txt b/requirements-system-ubuntu.txt new file mode 100644 index 00000000..41ded2d8 --- /dev/null +++ b/requirements-system-ubuntu.txt @@ -0,0 +1,26 @@ +# Ubuntu/Debian system packages required for the full Linux feature set. +# These are OS packages, not Python packages. +# Install with apt manually, for example: +# sudo apt-get update +# sudo apt-get install -y $(grep -v '^#' requirements-system-ubuntu.txt | tr '\n' ' ') +# +# Note: +# - tectonic is intentionally not listed here; install it via conda-forge or your preferred OS channel. +# - Node.js / npm are intentionally not listed here; frontend setup is managed separately. + +build-essential +curl +ffmpeg +git +inkscape +libgl1 +libglib2.0-0 +libgomp1 +libsndfile1 +libsm6 +libxext6 +libxrender1 +libreoffice +poppler-utils +wget +wkhtmltopdf From 9583d39968d1559b19478b89f99fc2158228b1c2 Mon Sep 17 00:00:00 2001 From: DeepThinkingLiuZhou Date: Mon, 6 Apr 2026 09:11:27 +0000 Subject: [PATCH 2/9] feat: add ikuncode image provider support --- .../toolkits/multimodaltool/providers.py | 133 +++++++++++++++++- .../toolkits/multimodaltool/req_img.py | 2 + .../toolkits/multimodaltool/utils.py | 3 + tests/test_ikuncode_multimodal_provider.py | 79 +++++++++++ 4 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 tests/test_ikuncode_multimodal_provider.py diff --git a/dataflow_agent/toolkits/multimodaltool/providers.py b/dataflow_agent/toolkits/multimodaltool/providers.py index 73a619c7..aa253f3a 100644 --- a/dataflow_agent/toolkits/multimodaltool/providers.py +++ b/dataflow_agent/toolkits/multimodaltool/providers.py @@ -303,13 +303,141 @@ def parse_generation_response(self, data: Dict[str, Any]) -> str: raise RuntimeError("candidates is empty") content = candidates[0].get("content", {}) parts = content.get("parts", []) - inline_data = parts[0].get("inlineData", {}) - return inline_data.get("data") + if not parts: + raise RuntimeError("parts is empty") + for part in parts: + inline_data = part.get("inlineData", {}) + b64 = inline_data.get("data") + if b64: + return b64 + raise RuntimeError("inlineData.data is empty") except Exception as e: log.error(f"Failed to parse APIYI Gemini response: {e}") log.error(f"Response preview: {str(data)[:500]}") raise + +class IkunCodeGeminiProvider(AIProviderStrategy): + """ + IKunCode 上的 Gemini 图像生成接口。 + 与 APIYI 同为 Google Native 风格,但字段命名遵循 IKunCode 文档: + - image_size + - inlineData / mimeType + """ + + def match(self, api_url: str, model: str) -> bool: + return ( + detect_provider(api_url) is Provider.IKUNCODE + and (is_gemini_3_pro(model) or is_gemini_31_flash(model)) + ) + + def _get_base_url(self, api_url: str) -> str: + base = api_url.rstrip("/") + if base.endswith("/v1beta"): + return base + if base.endswith("/v1"): + return f"{base[:-3]}/v1beta" + return f"{base}/v1beta" + + def _image_config(self, aspect_ratio: str, resolution: str) -> Dict[str, Any]: + return { + "aspectRatio": aspect_ratio, + "image_size": resolution, + } + + def build_generation_request(self, api_url: str, model: str, prompt: str, **kwargs) -> Tuple[str, Dict[str, Any], bool]: + base = self._get_base_url(api_url) + aspect_ratio = kwargs.get("aspect_ratio", "16:9") + resolution = kwargs.get("resolution", "2K") + url = f"{base}/models/{model}:generateContent" + payload = { + "contents": [{"parts": [{"text": prompt}]}], + "generationConfig": { + "responseModalities": ["IMAGE"], + "imageConfig": self._image_config(aspect_ratio, resolution), + }, + } + return url, payload, False + + def build_edit_request(self, api_url: str, model: str, prompt: str, image_b64: str, **kwargs) -> Tuple[str, Dict[str, Any], bool]: + base = self._get_base_url(api_url) + aspect_ratio = kwargs.get("aspect_ratio", "16:9") + resolution = kwargs.get("resolution", "2K") + fmt = kwargs.get("image_fmt", "png") + url = f"{base}/models/{model}:generateContent" + payload = { + "contents": [ + { + "parts": [ + { + "inlineData": { + "mimeType": f"image/{fmt}", + "data": image_b64, + } + }, + {"text": prompt}, + ] + } + ], + "generationConfig": { + "responseModalities": ["IMAGE"], + "imageConfig": self._image_config(aspect_ratio, resolution), + }, + } + return url, payload, False + + def build_multi_image_edit_request( + self, + api_url: str, + model: str, + prompt: str, + image_b64_list: List[Tuple[str, str]], + **kwargs + ) -> Tuple[str, Dict[str, Any], bool]: + base = self._get_base_url(api_url) + aspect_ratio = kwargs.get("aspect_ratio", "16:9") + resolution = kwargs.get("resolution", "2K") + parts: List[Dict[str, Any]] = [] + for b64, fmt in image_b64_list: + parts.append( + { + "inlineData": { + "mimeType": f"image/{fmt}", + "data": b64, + } + } + ) + parts.append({"text": prompt}) + url = f"{base}/models/{model}:generateContent" + payload = { + "contents": [{"parts": parts}], + "generationConfig": { + "responseModalities": ["IMAGE"], + "imageConfig": self._image_config(aspect_ratio, resolution), + }, + } + return url, payload, False + + def parse_generation_response(self, data: Dict[str, Any]) -> str: + try: + candidates = data.get("candidates", []) + if not candidates: + raise RuntimeError("candidates is empty") + content = candidates[0].get("content", {}) + parts = content.get("parts", []) + if not parts: + raise RuntimeError("parts is empty") + for part in parts: + inline_data = part.get("inlineData", {}) + b64 = inline_data.get("data") + if b64: + return b64 + raise RuntimeError("inlineData.data is empty") + except Exception as e: + log.error(f"Failed to parse IKunCode Gemini response: {e}") + log.error(f"Response preview: {str(data)[:500]}") + raise + # Gemini TTS 无 speakingRate 参数,通过文本前加 Pacing 指令控制语速 # steady:不论长短都保持稳定、自然的语速(避免短句偏慢、fast 偏快) _TTS_PACE_PREFIX = { @@ -1226,6 +1354,7 @@ def parse_generation_response(self, data: Dict[str, Any]) -> str: # 注册顺序 STRATEGIES = [ + IkunCodeGeminiProvider(), ApiYiGeminiProvider(), ApiYiSeeDreamProvider(), ApiYiGPTImageProvider(), diff --git a/dataflow_agent/toolkits/multimodaltool/req_img.py b/dataflow_agent/toolkits/multimodaltool/req_img.py index e7415708..5b209e85 100644 --- a/dataflow_agent/toolkits/multimodaltool/req_img.py +++ b/dataflow_agent/toolkits/multimodaltool/req_img.py @@ -111,6 +111,8 @@ async def _post_raw( for part in content.get("parts", []): if "inline_data" in part: part["inline_data"]["data"] = " ...[base64]... " + if "inlineData" in part: + part["inlineData"]["data"] = " ...[base64]... " log.info(f"Payload Preview: {json.dumps(debug_payload, ensure_ascii=False)}") except Exception: diff --git a/dataflow_agent/toolkits/multimodaltool/utils.py b/dataflow_agent/toolkits/multimodaltool/utils.py index 51f9923a..f49fc1da 100644 --- a/dataflow_agent/toolkits/multimodaltool/utils.py +++ b/dataflow_agent/toolkits/multimodaltool/utils.py @@ -11,6 +11,7 @@ class Provider(str, Enum): APIYI = "apiyi" + IKUNCODE = "ikuncode" LOCAL_123 = "local_123" OTHER = "other" @@ -20,6 +21,8 @@ def detect_provider(api_url: str) -> Provider: """ 根据 api_url 粗略识别服务商 """ + if "ikuncode" in api_url: + return Provider.IKUNCODE if "apiyi" in api_url: return Provider.APIYI if "123.129.219.111" in api_url: diff --git a/tests/test_ikuncode_multimodal_provider.py b/tests/test_ikuncode_multimodal_provider.py new file mode 100644 index 00000000..39e829ce --- /dev/null +++ b/tests/test_ikuncode_multimodal_provider.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +if str(PROJECT_ROOT) not in sys.path: + sys.path.insert(0, str(PROJECT_ROOT)) + +from dataflow_agent.toolkits.multimodaltool.providers import ( # noqa: E402 + IkunCodeGeminiProvider, + get_provider, +) +from dataflow_agent.toolkits.multimodaltool.utils import Provider, detect_provider # noqa: E402 + + +def test_detect_provider_identifies_ikuncode() -> None: + assert detect_provider("https://api.ikuncode.cc") is Provider.IKUNCODE + assert detect_provider("https://api.ikuncode.cc/v1beta") is Provider.IKUNCODE + + +def test_get_provider_returns_ikuncode_strategy_for_supported_image_models() -> None: + provider = get_provider("https://api.ikuncode.cc", "gemini-3-pro-image-preview") + assert isinstance(provider, IkunCodeGeminiProvider) + + +def test_ikuncode_generation_payload_uses_documented_fields() -> None: + provider = IkunCodeGeminiProvider() + url, payload, is_stream = provider.build_generation_request( + api_url="https://api.ikuncode.cc", + model="gemini-3-pro-image-preview", + prompt="test prompt", + aspect_ratio="21:9", + resolution="4K", + ) + assert not is_stream + assert url == "https://api.ikuncode.cc/v1beta/models/gemini-3-pro-image-preview:generateContent" + image_config = payload["generationConfig"]["imageConfig"] + assert image_config["aspectRatio"] == "21:9" + assert image_config["image_size"] == "4K" + assert "imageSize" not in image_config + + +def test_ikuncode_edit_payload_uses_inline_data_schema() -> None: + provider = IkunCodeGeminiProvider() + _, payload, _ = provider.build_edit_request( + api_url="https://api.ikuncode.cc/v1beta", + model="gemini-3.1-flash-image-preview", + prompt="edit prompt", + image_b64="ZmFrZQ==", + image_fmt="png", + aspect_ratio="4:5", + resolution="2K", + ) + parts = payload["contents"][0]["parts"] + assert "inlineData" in parts[0] + assert parts[0]["inlineData"]["mimeType"] == "image/png" + assert parts[0]["inlineData"]["data"] == "ZmFrZQ==" + assert parts[1]["text"] == "edit prompt" + assert payload["generationConfig"]["imageConfig"]["aspectRatio"] == "4:5" + assert payload["generationConfig"]["imageConfig"]["image_size"] == "2K" + + +def test_ikuncode_parse_generation_response_handles_text_then_image_parts() -> None: + provider = IkunCodeGeminiProvider() + data = { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + {"text": "Here is your edited slide."}, + {"inlineData": {"mimeType": "image/png", "data": "ZmFrZS1pbWFnZQ=="}}, + ], + } + } + ] + } + assert provider.parse_generation_response(data) == "ZmFrZS1pbWFnZQ==" From 1ee20e32f2e6358f99d3b3a2feac0bd1503199a8 Mon Sep 17 00:00:00 2001 From: DeepThinkingLiuZhou Date: Tue, 7 Apr 2026 08:20:31 +0800 Subject: [PATCH 3/9] Fix paper2poster output validation and backend watchdog --- .../postertool/src/agents/renderer.py | 113 ++++-- deploy/backend_watchdog.sh | 333 ++++++++++++++++++ fastapi_app/services/paper2poster_service.py | 27 +- script/paper2poster_worker.py | 45 ++- tests/test_paper2poster_outputs.py | 42 +++ tests/test_paper2poster_renderer.py | 61 ++++ 6 files changed, 570 insertions(+), 51 deletions(-) create mode 100755 deploy/backend_watchdog.sh create mode 100644 tests/test_paper2poster_outputs.py create mode 100644 tests/test_paper2poster_renderer.py diff --git a/dataflow_agent/toolkits/postertool/src/agents/renderer.py b/dataflow_agent/toolkits/postertool/src/agents/renderer.py index 3259809c..5f019f04 100644 --- a/dataflow_agent/toolkits/postertool/src/agents/renderer.py +++ b/dataflow_agent/toolkits/postertool/src/agents/renderer.py @@ -424,6 +424,36 @@ def _parse_and_add_runs(self, paragraph, text: str, font_family: str, if segment['italic']: run.font.italic = True + + def _append_format_segment( + self, + segments: list, + text: str, + *, + bold: bool = False, + italic: bool = False, + color: Optional[str] = None, + ) -> None: + """Append a formatting segment while coalescing adjacent identical styles.""" + if not text: + return + + if segments: + last = segments[-1] + if ( + last["bold"] == bold + and last["italic"] == italic + and last["color"] == color + ): + last["text"] += text + return + + segments.append({ + "text": text, + "bold": bold, + "italic": italic, + "color": color, + }) def _tokenize_formatting(self, text: str) -> list: """tokenize text into formatting segments with precise position tracking""" @@ -452,73 +482,80 @@ def _tokenize_formatting(self, text: str) -> list: # process colored text with automatic bold if colored_text.strip(): # only process non-empty content - segments.append({ - 'text': colored_text, - 'bold': True, # all colored text is bold - 'italic': False, - 'color': color_hex - }) + self._append_format_segment( + segments, + colored_text, + bold=True, # all colored text is bold + italic=False, + color=color_hex, + ) # move past the entire color block i = closing_tag_end continue else: # malformed color tag, treat as regular text - segments.append({ - 'text': text[i], - 'bold': False, - 'italic': False, - 'color': None - }) + self._append_format_segment(segments, text[i]) i += 1 continue + + # check for bold italic: ***text*** + bold_italic_match = re.match(r'\*\*\*(.+?)\*\*\*', text[i:]) + if bold_italic_match: + self._append_format_segment( + segments, + bold_italic_match.group(1), + bold=True, + italic=True, + ) + i += bold_italic_match.end() + continue # check for bold: **text** - bold_match = re.match(r'\*\*(.*?)\*\*', text[i:]) + bold_match = re.match(r'\*\*(.+?)\*\*', text[i:]) if bold_match: - bold_text = bold_match.group(1) - segments.append({ - 'text': bold_text, - 'bold': True, - 'italic': False, - 'color': None - }) + self._append_format_segment( + segments, + bold_match.group(1), + bold=True, + italic=False, + ) i += bold_match.end() continue # check for italic: *text* - italic_match = re.match(r'\*(.*?)\*', text[i:]) + italic_match = re.match(r'\*(.+?)\*', text[i:]) if italic_match: - italic_text = italic_match.group(1) - segments.append({ - 'text': italic_text, - 'bold': False, - 'italic': True, - 'color': None - }) + self._append_format_segment( + segments, + italic_match.group(1), + bold=False, + italic=True, + ) i += italic_match.end() continue # regular text - find next formatting marker next_format = re.search(r'(\*\*|\*| list: diff --git a/deploy/backend_watchdog.sh b/deploy/backend_watchdog.sh new file mode 100755 index 00000000..7d48b7eb --- /dev/null +++ b/deploy/backend_watchdog.sh @@ -0,0 +1,333 @@ +#!/bin/bash +# FastAPI backend watchdog for port 8000. + +set -u + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" || exit 1 + +source "$PROJECT_ROOT/deploy/app_config.sh" + +LOG_DIR="$PROJECT_ROOT/logs" +UVICORN_PID_FILE="$LOG_DIR/uvicorn.pid" +WATCHDOG_PID_FILE="$LOG_DIR/backend_watchdog.pid" +WATCHDOG_LOG_FILE="$LOG_DIR/backend_watchdog.log" +WATCHDOG_LOCK_DIR="$LOG_DIR/backend_watchdog.lock" + +WATCHDOG_INTERVAL_SECONDS="${WATCHDOG_INTERVAL_SECONDS:-15}" +WATCHDOG_FAIL_THRESHOLD="${WATCHDOG_FAIL_THRESHOLD:-2}" +WATCHDOG_RESTART_COOLDOWN_SECONDS="${WATCHDOG_RESTART_COOLDOWN_SECONDS:-30}" +WATCHDOG_HEALTH_TIMEOUT_SECONDS="${WATCHDOG_HEALTH_TIMEOUT_SECONDS:-5}" +WATCHDOG_START_WAIT_SECONDS="${WATCHDOG_START_WAIT_SECONDS:-25}" + +mkdir -p "$LOG_DIR" + +timestamp() { + date '+%Y-%m-%d %H:%M:%S' +} + +log() { + printf '%s | %s\n' "$(timestamp)" "$*" +} + +health_url() { + printf 'http://127.0.0.1:%s/health' "$APP_PORT" +} + +find_port_listener_pids() { + local port="$1" + + if command -v lsof >/dev/null 2>&1; then + lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | sort -u + return 0 + fi + + if command -v ss >/dev/null 2>&1; then + ss -ltnp 2>/dev/null \ + | awk -v port=":$port" '$4 ~ port { print $NF }' \ + | grep -oE 'pid=[0-9]+' \ + | cut -d= -f2 \ + | sort -u + return 0 + fi + + if command -v netstat >/dev/null 2>&1; then + netstat -ltnp 2>/dev/null \ + | awk -v port=":$port" '$4 ~ port { split($7, parts, "/"); if (parts[1] ~ /^[0-9]+$/) print parts[1] }' \ + | sort -u + return 0 + fi + + return 1 +} + +backend_port_listening() { + [[ -n "$(find_port_listener_pids "$APP_PORT" || true)" ]] +} + +backend_health_ok() { + local response + response="$( + curl -fsS \ + --max-time "$WATCHDOG_HEALTH_TIMEOUT_SECONDS" \ + "$(health_url)" \ + 2>/dev/null || true + )" + [[ "$response" == *'"status":"ok"'* ]] +} + +current_watchdog_pid() { + if [[ -f "$WATCHDOG_PID_FILE" ]]; then + cat "$WATCHDOG_PID_FILE" 2>/dev/null || true + fi +} + +backend_pid_from_pidfile() { + if [[ -f "$UVICORN_PID_FILE" ]]; then + cat "$UVICORN_PID_FILE" 2>/dev/null || true + fi +} + +backend_process_running() { + local pid + pid="$(backend_pid_from_pidfile)" + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + return 0 + fi + + pgrep -f "uvicorn fastapi_app.main:app" >/dev/null 2>&1 +} + +watchdog_running() { + local pid + pid="$(current_watchdog_pid)" + [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null +} + +write_pid() { + echo "$1" > "$WATCHDOG_PID_FILE" +} + +cleanup_pid_file() { + if ! watchdog_running; then + rm -f "$WATCHDOG_PID_FILE" + fi +} + +acquire_lock() { + mkdir "$WATCHDOG_LOCK_DIR" 2>/dev/null +} + +release_lock() { + rmdir "$WATCHDOG_LOCK_DIR" 2>/dev/null || true +} + +wait_for_backend() { + local attempt + for attempt in $(seq 1 "$WATCHDOG_START_WAIT_SECONDS"); do + if backend_port_listening; then + return 0 + fi + sleep 1 + done + return 1 +} + +restart_backend() { + if ! acquire_lock; then + log "[watchdog] another restart is already in progress" + return 1 + fi + + log "[watchdog] backend unhealthy, running deploy/start.sh" + if bash "$PROJECT_ROOT/deploy/start.sh" >> "$WATCHDOG_LOG_FILE" 2>&1; then + if wait_for_backend; then + log "[watchdog] backend recovered successfully" + release_lock + return 0 + fi + log "[watchdog] deploy/start.sh returned success, but port $APP_PORT did not recover in time" + else + log "[watchdog] deploy/start.sh failed" + fi + + release_lock + return 1 +} + +run_once() { + if backend_port_listening; then + log "[watchdog] backend port $APP_PORT is listening" + return 0 + fi + + if backend_process_running; then + log "[watchdog] port $APP_PORT is down, but backend process is still alive" + return 0 + fi + + restart_backend +} + +run_loop() { + local consecutive_failures=0 + + trap 'rm -f "$WATCHDOG_PID_FILE"; release_lock; exit 0' INT TERM EXIT + write_pid "$$" + log "[watchdog] started, interval=${WATCHDOG_INTERVAL_SECONDS}s fail_threshold=${WATCHDOG_FAIL_THRESHOLD}" + + while true; do + if backend_port_listening; then + consecutive_failures=0 + else + consecutive_failures=$((consecutive_failures + 1)) + if backend_process_running; then + log "[watchdog] port $APP_PORT is down, but backend process is still alive (${consecutive_failures}/${WATCHDOG_FAIL_THRESHOLD})" + else + log "[watchdog] port $APP_PORT is down (${consecutive_failures}/${WATCHDOG_FAIL_THRESHOLD})" + fi + if (( consecutive_failures >= WATCHDOG_FAIL_THRESHOLD )); then + restart_backend || true + consecutive_failures=0 + sleep "$WATCHDOG_RESTART_COOLDOWN_SECONDS" + continue + fi + fi + + sleep "$WATCHDOG_INTERVAL_SECONDS" + done +} + +start_watchdog() { + cleanup_pid_file + if watchdog_running; then + log "[watchdog] already running with PID $(current_watchdog_pid)" + exit 0 + fi + + if command -v setsid >/dev/null 2>&1; then + nohup setsid bash "$0" run >> "$WATCHDOG_LOG_FILE" 2>&1 < /dev/null & + else + nohup bash "$0" run >> "$WATCHDOG_LOG_FILE" 2>&1 < /dev/null & + fi + sleep 1 + + if watchdog_running; then + log "[watchdog] started with PID $(current_watchdog_pid)" + exit 0 + fi + + log "[watchdog] failed to start" + exit 1 +} + +stop_watchdog() { + local pid + local waited=0 + pid="$(current_watchdog_pid)" + if [[ -z "$pid" ]]; then + log "[watchdog] not running" + rm -f "$WATCHDOG_PID_FILE" + exit 0 + fi + + if kill "$pid" 2>/dev/null; then + while kill -0 "$pid" 2>/dev/null && (( waited < 5 )); do + sleep 1 + waited=$((waited + 1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -KILL "$pid" 2>/dev/null || true + sleep 1 + fi + cleanup_pid_file + release_lock + if kill -0 "$pid" 2>/dev/null; then + log "[watchdog] failed to stop PID $pid" + exit 1 + fi + log "[watchdog] stopped" + exit 0 + fi + + log "[watchdog] failed to stop PID $pid" + exit 1 +} + +status_watchdog() { + cleanup_pid_file + if watchdog_running; then + log "[watchdog] running with PID $(current_watchdog_pid)" + else + log "[watchdog] not running" + fi + + if backend_port_listening; then + log "[watchdog] backend port $APP_PORT is listening" + else + log "[watchdog] backend port $APP_PORT is NOT listening" + fi + + if backend_health_ok; then + log "[watchdog] backend health OK on $(health_url)" + elif backend_process_running; then + log "[watchdog] backend health FAILED, but uvicorn process is still alive" + else + log "[watchdog] backend health FAILED on $(health_url)" + fi + + log "[watchdog] log file: $WATCHDOG_LOG_FILE" +} + +show_logs() { + touch "$WATCHDOG_LOG_FILE" + tail -n 80 "$WATCHDOG_LOG_FILE" +} + +usage() { + cat <<'EOF' +Usage: + bash deploy/backend_watchdog.sh start + bash deploy/backend_watchdog.sh stop + bash deploy/backend_watchdog.sh status + bash deploy/backend_watchdog.sh logs + bash deploy/backend_watchdog.sh run + bash deploy/backend_watchdog.sh run-once + +Optional env vars: + WATCHDOG_INTERVAL_SECONDS + WATCHDOG_FAIL_THRESHOLD + WATCHDOG_RESTART_COOLDOWN_SECONDS + WATCHDOG_HEALTH_TIMEOUT_SECONDS + WATCHDOG_START_WAIT_SECONDS +EOF +} + +case "${1:-status}" in + start) + start_watchdog + ;; + stop) + stop_watchdog + ;; + status) + status_watchdog + ;; + logs) + show_logs + ;; + run) + run_loop + ;; + run-once) + run_once + ;; + -h|--help|help) + usage + ;; + *) + echo "Unknown command: ${1:-}" >&2 + usage + exit 1 + ;; +esac diff --git a/fastapi_app/services/paper2poster_service.py b/fastapi_app/services/paper2poster_service.py index d688d54b..a2027d00 100644 --- a/fastapi_app/services/paper2poster_service.py +++ b/fastapi_app/services/paper2poster_service.py @@ -22,6 +22,22 @@ class Paper2PosterService: """paper2poster 请求编排与文件落盘。""" + @staticmethod + def _resolve_existing_output_file(path_value: str) -> Optional[Path]: + raw = (path_value or "").strip() + if not raw: + return None + + candidate = Path(raw).expanduser() + if not candidate.is_absolute(): + candidate = (PROJECT_ROOT / candidate).resolve() + + if candidate.is_file(): + return candidate + + log.warning("[paper2poster] output file missing on disk: %s", raw) + return None + @staticmethod def _validate_poster_dimensions(width: float, height: float) -> None: if width <= 0 or height <= 0: @@ -138,13 +154,16 @@ async def generate( raise HTTPException(status_code=500, detail=result.get("message") or "Failed to generate poster") pptx_path = (result.get("output_pptx_path") or "").strip() - if not pptx_path: - raise HTTPException(status_code=500, detail="Poster workflow finished without a PPTX output") + pptx_file = self._resolve_existing_output_file(pptx_path) + if pptx_file is None: + detail = result.get("message") or "Poster workflow finished without a valid PPTX output" + raise HTTPException(status_code=500, detail=detail) png_path = (result.get("output_png_path") or "").strip() + png_file = self._resolve_existing_output_file(png_path) if png_path else None return { "success": True, - "pptx_url": _to_outputs_url(pptx_path), - "png_url": _to_outputs_url(png_path) if png_path else None, + "pptx_url": _to_outputs_url(str(pptx_file)), + "png_url": _to_outputs_url(str(png_file)) if png_file else None, "message": "Poster generated successfully", } diff --git a/script/paper2poster_worker.py b/script/paper2poster_worker.py index 2fe0953e..3d3db2ca 100644 --- a/script/paper2poster_worker.py +++ b/script/paper2poster_worker.py @@ -17,6 +17,41 @@ log = get_logger(__name__) +def _resolve_existing_output_path(path_value: str) -> str: + raw = (path_value or "").strip() + if not raw: + return "" + + candidate = Path(raw).expanduser() + if not candidate.is_absolute(): + candidate = (Path(__file__).resolve().parent.parent / candidate).resolve() + + return str(candidate) if candidate.is_file() else "" + + +def _build_worker_result(output_pptx_path: str, output_png_path: str, errors: list[str]) -> dict: + resolved_pptx_path = _resolve_existing_output_path(output_pptx_path) + resolved_png_path = _resolve_existing_output_path(output_png_path) + + normalized_errors = [str(err).strip() for err in (errors or []) if str(err).strip()] + if output_pptx_path and not resolved_pptx_path: + normalized_errors.append(f"Poster PPTX output missing on disk: {output_pptx_path}") + if output_png_path and not resolved_png_path: + normalized_errors.append(f"Poster PNG output missing on disk: {output_png_path}") + + success = bool(resolved_pptx_path) + message = "Poster generated successfully" if success else ( + "; ".join(normalized_errors) if normalized_errors else "Poster generation failed" + ) + return { + "success": success, + "message": message, + "output_pptx_path": resolved_pptx_path, + "output_png_path": resolved_png_path, + "errors": normalized_errors, + } + + async def _run_workflow(in_data: dict) -> dict: api_key = (in_data.get("api_key") or "").strip() api_url = (in_data.get("chat_api_url") or "").strip() @@ -59,15 +94,7 @@ async def _run_workflow(in_data: dict) -> dict: output_png_path = getattr(final_state, "output_png_path", "") or "" errors = getattr(final_state, "errors", []) or [] - success = bool(output_pptx_path) - message = "Poster generated successfully" if success else ("; ".join(errors) if errors else "Poster generation failed") - return { - "success": success, - "message": message, - "output_pptx_path": output_pptx_path, - "output_png_path": output_png_path, - "errors": errors, - } + return _build_worker_result(output_pptx_path, output_png_path, errors) def main() -> int: diff --git a/tests/test_paper2poster_outputs.py b/tests/test_paper2poster_outputs.py new file mode 100644 index 00000000..3cee59a6 --- /dev/null +++ b/tests/test_paper2poster_outputs.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +if str(PROJECT_ROOT) not in sys.path: + sys.path.insert(0, str(PROJECT_ROOT)) + +from fastapi_app.services.paper2poster_service import Paper2PosterService +from script.paper2poster_worker import _build_worker_result + + +def test_worker_result_requires_existing_pptx_file(tmp_path: Path) -> None: + missing_pptx = tmp_path / "poster.pptx" + + result = _build_worker_result(str(missing_pptx), "", []) + + assert result["success"] is False + assert result["output_pptx_path"] == "" + assert "Poster PPTX output missing on disk" in result["message"] + + +def test_worker_result_drops_missing_png_file(tmp_path: Path) -> None: + pptx_path = tmp_path / "poster.pptx" + pptx_path.write_bytes(b"pptx") + missing_png = tmp_path / "poster.png" + + result = _build_worker_result(str(pptx_path), str(missing_png), []) + + assert result["success"] is True + assert result["output_pptx_path"] == str(pptx_path) + assert result["output_png_path"] == "" + assert any("Poster PNG output missing on disk" in err for err in result["errors"]) + + +def test_service_output_resolver_only_accepts_existing_files(tmp_path: Path) -> None: + pptx_path = tmp_path / "poster.pptx" + pptx_path.write_bytes(b"pptx") + + assert Paper2PosterService._resolve_existing_output_file(str(pptx_path)) == pptx_path + assert Paper2PosterService._resolve_existing_output_file(str(tmp_path / "missing.png")) is None diff --git a/tests/test_paper2poster_renderer.py b/tests/test_paper2poster_renderer.py new file mode 100644 index 00000000..148ece60 --- /dev/null +++ b/tests/test_paper2poster_renderer.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import multiprocessing as mp +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +POSTERTOOL_ROOT = PROJECT_ROOT / "dataflow_agent" / "toolkits" / "postertool" +if str(PROJECT_ROOT) not in sys.path: + sys.path.insert(0, str(PROJECT_ROOT)) +if str(POSTERTOOL_ROOT) not in sys.path: + sys.path.insert(0, str(POSTERTOOL_ROOT)) + +from src.agents.renderer import Renderer + + +def _tokenize_in_subprocess(text: str, queue: mp.Queue) -> None: + renderer = Renderer() + queue.put(renderer._tokenize_formatting(text)) + + +def _tokenize_with_timeout(text: str, timeout: float = 2.0) -> list[dict]: + queue: mp.Queue = mp.Queue() + proc = mp.Process(target=_tokenize_in_subprocess, args=(text, queue)) + proc.start() + proc.join(timeout) + + if proc.is_alive(): + proc.terminate() + proc.join() + raise AssertionError("Renderer tokenizer hung on malformed markdown input") + + assert proc.exitcode == 0 + return queue.get(timeout=1) + + +def test_renderer_tokenizer_supports_bold_italic_markdown() -> None: + renderer = Renderer() + segments = renderer._tokenize_formatting( + "• ***Guarantees:*** parameter safety, bounded recovery, and **termination (Tmax=10)**" + ) + + assert [segment["text"] for segment in segments] == [ + "• ", + "Guarantees:", + " parameter safety, bounded recovery, and ", + "termination (Tmax=10)", + ] + assert segments[1]["bold"] is True and segments[1]["italic"] is True + assert segments[3]["bold"] is True and segments[3]["italic"] is False + + +def test_renderer_tokenizer_does_not_hang_on_malformed_markdown() -> None: + segments = _tokenize_with_timeout( + "• ***Ablation* headline:** removing logic rules causes large drops" + ) + + joined = "".join(segment["text"] for segment in segments) + assert "Ablation" in joined + assert "headline:" in joined + assert "removing logic rules causes large drops" in joined From 08707817824b51a407d8e03164957d29d3379221 Mon Sep 17 00:00:00 2001 From: DeepThinkingLiuZhou Date: Thu, 9 Apr 2026 16:32:41 +0000 Subject: [PATCH 4/9] fix: point pdf2ppt CLI to supported workflow --- script/run_pdf2ppt_cli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/run_pdf2ppt_cli.py b/script/run_pdf2ppt_cli.py index 6d81818a..064e0708 100644 --- a/script/run_pdf2ppt_cli.py +++ b/script/run_pdf2ppt_cli.py @@ -185,8 +185,10 @@ async def run_pdf2ppt_workflow(args, input_path: Path, output_dir: Path): # Set PDF file path state.pdf_file = str(input_path) - # Select workflow based on use_ai_edit flag - workflow_name = "pdf2ppt_qwenvl" if args.use_ai_edit else "pdf2ppt_parallel" + # The legacy `pdf2ppt_parallel` workflow now lives under deprecated/ and is + # no longer registered by the lazy workflow loader. Keep a single supported + # CLI path and use `--use-ai-edit` only as a feature flag inside the request. + workflow_name = "pdf2ppt_qwenvl" log.info("%s", "=" * 60) log.info("PDF2PPT Workflow Starting") From 224bfbacb8c53392f0be9a5a48ac1985a7197dbd Mon Sep 17 00:00:00 2001 From: DeepThinkingLiuZhou Date: Sat, 11 Apr 2026 15:37:25 +0000 Subject: [PATCH 5/9] feat: restructure ppt workflows and backend-managed free mode --- .../content_expander_agent.py | 21 +- .../long_paper_outline_agent.py | 13 +- .../paper2any_agents/outline_agent.py | 15 +- .../paper2any_agents/outline_refine_agent.py | 4 +- .../paper2any_agents/websearch_curator.py | 2 +- .../websearch_initial_analyzer.py | 2 - .../paper2any_agents/websearch_planner.py | 2 - .../paper2any_agents/websearch_researcher.py | 2 +- .../resources/pt_long_paper_repo.py | 19 +- .../resources/pt_paper2ppt_outline_repo.py | 74 ++ .../workflow/wf_paper2page_content.py | 20 + .../wf_paper2page_content_for_long_paper.py | 271 +++++- fastapi_app/.env.example | 5 +- fastapi_app/.env.simple.example | 51 ++ fastapi_app/config/settings.py | 161 +++- fastapi_app/main.py | 6 +- fastapi_app/routers/__init__.py | 4 + fastapi_app/routers/image2drawio.py | 19 +- fastapi_app/routers/image2ppt.py | 14 +- fastapi_app/routers/kb.py | 490 +--------- fastapi_app/routers/kb_workflows.py | 512 +++++++++++ fastapi_app/routers/mindmap.py | 12 +- fastapi_app/routers/paper2any.py | 112 +-- fastapi_app/routers/paper2drawio.py | 18 +- fastapi_app/routers/paper2figure.py | 132 +++ fastapi_app/routers/paper2poster.py | 15 +- fastapi_app/routers/paper2ppt.py | 391 +++++++- fastapi_app/routers/pdf2ppt.py | 11 +- fastapi_app/schemas.py | 5 +- fastapi_app/services/image2ppt_service.py | 10 + fastapi_app/services/managed_api_service.py | 26 + fastapi_app/services/paper2any_service.py | 49 +- fastapi_app/services/paper2drawio_service.py | 14 +- fastapi_app/services/paper2poster_service.py | 13 +- .../services/paper2ppt_frontend_service.py | 861 +++++++++--------- fastapi_app/services/paper2ppt_service.py | 118 ++- fastapi_app/services/paper2video_service.py | 18 +- fastapi_app/services/pdf2ppt_service.py | 10 + fastapi_app/services/rebuttal_service.py | 8 +- fastapi_app/workflow_adapters/wa_paper2ppt.py | 10 + frontend-workflow/.env.example | 5 +- frontend-workflow/.env.simple.example | 40 + frontend-workflow/index.html | 4 +- frontend-workflow/package-lock.json | 134 ++- frontend-workflow/package.json | 1 + .../public/paper2any-favicon.png | Bin 0 -> 367682 bytes .../src/components/Image2DrawioPage.tsx | 10 +- .../src/components/Image2PptPage.tsx | 14 +- .../src/components/MindMapPage.tsx | 12 +- .../src/components/Pdf2PptPage.tsx | 22 +- .../src/components/Ppt2PolishPage.tsx | 64 +- .../knowledge-base/tools/ChatTool.tsx | 10 +- .../knowledge-base/tools/DeepResearchTool.tsx | 118 +-- .../knowledge-base/tools/MindMapTool.tsx | 74 +- .../knowledge-base/tools/PodcastTool.tsx | 81 +- .../knowledge-base/tools/PptTool.tsx | 92 +- .../knowledge-base/tools/ReportTool.tsx | 86 +- .../knowledge-base/tools/SearchTool.tsx | 71 +- .../src/components/paper2drawio/index.tsx | 53 +- .../components/paper2graph/PreviewSection.tsx | 12 +- .../components/paper2graph/SettingsCard.tsx | 5 +- .../src/components/paper2graph/index.tsx | 16 +- .../components/paper2poster/UploadStep.tsx | 9 +- .../src/components/paper2poster/index.tsx | 28 +- .../paper2ppt/FrontendCompleteStep.tsx | 14 +- .../paper2ppt/FrontendGenerateStep.tsx | 222 ++--- .../paper2ppt/FrontendSlidePreview.tsx | 102 +-- .../paper2ppt/StructuredSlideCanvas.tsx | 447 +++++++++ .../src/components/paper2ppt/UploadStep.tsx | 41 +- .../paper2ppt/exportStructuredSlides.ts | 624 +++++++++++++ .../src/components/paper2ppt/index.tsx | 721 ++++++--------- .../paper2ppt/structuredSlideModel.ts | 205 +++++ .../src/components/paper2ppt/types.ts | 116 ++- .../src/components/paper2rebuttal/index.tsx | 12 +- .../src/components/paper2video/UploadStep.tsx | 4 +- .../src/components/paper2video/index.tsx | 12 +- .../src/services/runtimeConfigService.ts | 2 + .../src/utils/runtimeBillingForm.ts | 36 + script/run_paper2ppt_frontend_cli.py | 215 +++++ script/run_pdf2ppt_cli.py | 6 +- 80 files changed, 5077 insertions(+), 2203 deletions(-) create mode 100644 dataflow_agent/promptstemplates/resources/pt_paper2ppt_outline_repo.py create mode 100644 fastapi_app/.env.simple.example create mode 100644 fastapi_app/routers/kb_workflows.py create mode 100644 fastapi_app/routers/paper2figure.py create mode 100644 frontend-workflow/.env.simple.example create mode 100644 frontend-workflow/public/paper2any-favicon.png create mode 100644 frontend-workflow/src/components/paper2ppt/StructuredSlideCanvas.tsx create mode 100644 frontend-workflow/src/components/paper2ppt/exportStructuredSlides.ts create mode 100644 frontend-workflow/src/components/paper2ppt/structuredSlideModel.ts create mode 100644 frontend-workflow/src/utils/runtimeBillingForm.ts create mode 100644 script/run_paper2ppt_frontend_cli.py diff --git a/dataflow_agent/agentroles/paper2any_agents/content_expander_agent.py b/dataflow_agent/agentroles/paper2any_agents/content_expander_agent.py index 45c4c4c4..708740e6 100644 --- a/dataflow_agent/agentroles/paper2any_agents/content_expander_agent.py +++ b/dataflow_agent/agentroles/paper2any_agents/content_expander_agent.py @@ -51,15 +51,21 @@ def get_task_prompt_params(self, pre_tool_results: Dict[str, Any]) -> Dict[str, - text_content: 待扩写的文本 - expansion_round: 当前扩写轮次 """ + language = "zh" + request = getattr(self.state, "request", None) + if request is not None: + language = str(getattr(request, "language", None) or language).strip() or language return { "text_content": self.state.text_content, - "expansion_round": 0, + "expansion_round": int(getattr(self.state, "expansion_round", 0) or 0), + "language": language, } def get_default_pre_tool_results(self) -> Dict[str, Any]: return { "text_content": "", - "expansion_round": 0 + "expansion_round": 0, + "language": "zh", } # ---------- 结果写回 ---------- @@ -72,7 +78,16 @@ def update_state_result( """ 将扩写后的文本(字符串)写回 State。 """ - state.text_content = result + if isinstance(result, dict): + text_value = result.get("text") + if isinstance(text_value, str): + state.text_content = text_value + else: + state.text_content = str(text_value or "") + elif isinstance(result, str): + state.text_content = result + else: + state.text_content = str(result or "") super().update_state_result(state, result, pre_tool_results) diff --git a/dataflow_agent/agentroles/paper2any_agents/long_paper_outline_agent.py b/dataflow_agent/agentroles/paper2any_agents/long_paper_outline_agent.py index 4ac0b83c..cc7ef880 100644 --- a/dataflow_agent/agentroles/paper2any_agents/long_paper_outline_agent.py +++ b/dataflow_agent/agentroles/paper2any_agents/long_paper_outline_agent.py @@ -37,7 +37,7 @@ def role_name(self) -> str: @property def system_prompt_template_name(self) -> str: - return "system_prompt_for_outline_agent" + return "system_prompt_for_long_paper_outline_agent" @property def task_prompt_template_name(self) -> str: @@ -87,9 +87,16 @@ def update_state_result( 注意:在 Workflow 的 generate_outline_for_batch 中, 会从返回的 State 中读取 pagecontent。 """ - # 结果预期是一个 List[Dict] (页面列表) + if not isinstance(result, list): + log.warning("[long_paper_outline_agent] Invalid result, discard invalid payload and mark pagecontent empty.") + state.pagecontent = [] + setattr(state, "outline_generation_error", "long_paper_outline_agent did not return a valid JSON array") + super().update_state_result(state, [], pre_tool_results) + return + state.pagecontent = result - log.info(f"[long_paper_outline_agent] 生成了 {len(result) if isinstance(result, list) else 0} 页内容") + setattr(state, "outline_generation_error", "") + log.info(f"[long_paper_outline_agent] 生成了 {len(result)} 页内容") super().update_state_result(state, result, pre_tool_results) diff --git a/dataflow_agent/agentroles/paper2any_agents/outline_agent.py b/dataflow_agent/agentroles/paper2any_agents/outline_agent.py index 5068f0bc..7b152f50 100644 --- a/dataflow_agent/agentroles/paper2any_agents/outline_agent.py +++ b/dataflow_agent/agentroles/paper2any_agents/outline_agent.py @@ -40,13 +40,11 @@ def role_name(self) -> str: # noqa: D401 @property def system_prompt_template_name(self) -> str: - # TODO: 修改为真实的模板 id - return "system_prompt_for_outline_agent" + return "system_prompt_for_paper2ppt_outline_agent" @property def task_prompt_template_name(self) -> str: - # TODO: 修改为真实的模板 id - return "task_prompt_for_outline_agent" + return "task_prompt_for_paper2ppt_outline_agent" # ---------- Prompt 参数 ---------- def get_task_prompt_params(self, pre_tool_results: Dict[str, Any]) -> Dict[str, Any]: @@ -78,8 +76,15 @@ def update_state_result( pre_tool_results: Dict[str, Any], ): """将推理结果写回 MainState,可按需重写""" + if not isinstance(result, list): + log.warning("[outline_agent] Invalid result, discard invalid payload and mark pagecontent empty.") + state.pagecontent = [] + setattr(state, "outline_generation_error", "outline_agent did not return a valid JSON array") + super().update_state_result(state, [], pre_tool_results) + return state.pagecontent = result + setattr(state, "outline_generation_error", "") log.info(f"[outline_agent]: outline_agent 生成了 {len(result)} 页内容") super().update_state_result(state, result, pre_tool_results) @@ -167,4 +172,4 @@ def create_outline_agent( use_vlm=use_vlm, vlm_config=vlm_config, **kwargs, - ) \ No newline at end of file + ) diff --git a/dataflow_agent/agentroles/paper2any_agents/outline_refine_agent.py b/dataflow_agent/agentroles/paper2any_agents/outline_refine_agent.py index 02170686..cd629d18 100644 --- a/dataflow_agent/agentroles/paper2any_agents/outline_refine_agent.py +++ b/dataflow_agent/agentroles/paper2any_agents/outline_refine_agent.py @@ -31,11 +31,11 @@ def role_name(self) -> str: # noqa: D401 @property def system_prompt_template_name(self) -> str: - return "system_prompt_for_outline_refine_agent" + return "system_prompt_for_paper2ppt_outline_refine_agent" @property def task_prompt_template_name(self) -> str: - return "task_prompt_for_outline_refine_agent" + return "task_prompt_for_paper2ppt_outline_refine_agent" def get_task_prompt_params(self, pre_tool_results: Dict[str, Any]) -> Dict[str, Any]: return { diff --git a/dataflow_agent/agentroles/paper2any_agents/websearch_curator.py b/dataflow_agent/agentroles/paper2any_agents/websearch_curator.py index 2e0ff7e8..b8211915 100644 --- a/dataflow_agent/agentroles/paper2any_agents/websearch_curator.py +++ b/dataflow_agent/agentroles/paper2any_agents/websearch_curator.py @@ -672,4 +672,4 @@ def create_websearch_curator_agent( """ 便捷创建函数。 """ - return WebsearchChiefCuratorAgent.create(tool_manager=tool_manager, **kwargs) \ No newline at end of file + return WebsearchChiefCuratorAgent.create(tool_manager=tool_manager, **kwargs) diff --git a/dataflow_agent/agentroles/paper2any_agents/websearch_initial_analyzer.py b/dataflow_agent/agentroles/paper2any_agents/websearch_initial_analyzer.py index 0c164650..0b4f645e 100644 --- a/dataflow_agent/agentroles/paper2any_agents/websearch_initial_analyzer.py +++ b/dataflow_agent/agentroles/paper2any_agents/websearch_initial_analyzer.py @@ -553,5 +553,3 @@ def create_websearch_initial_analyzer_agent( - - diff --git a/dataflow_agent/agentroles/paper2any_agents/websearch_planner.py b/dataflow_agent/agentroles/paper2any_agents/websearch_planner.py index 7a858620..70e811ba 100644 --- a/dataflow_agent/agentroles/paper2any_agents/websearch_planner.py +++ b/dataflow_agent/agentroles/paper2any_agents/websearch_planner.py @@ -218,5 +218,3 @@ def create_websearch_planner_agent( 便捷创建函数。 """ return WebsearchPlannerAgent.create(tool_manager=tool_manager, **kwargs) - - diff --git a/dataflow_agent/agentroles/paper2any_agents/websearch_researcher.py b/dataflow_agent/agentroles/paper2any_agents/websearch_researcher.py index 5c17dba9..e6e4fbf7 100644 --- a/dataflow_agent/agentroles/paper2any_agents/websearch_researcher.py +++ b/dataflow_agent/agentroles/paper2any_agents/websearch_researcher.py @@ -877,4 +877,4 @@ async def run(self, state: MainState, **kwargs) -> Dict[str, Any]: return result_payload def create_websearch_researcher_agent(tool_manager: Optional[ToolManager] = None, **kwargs) -> WebsearchResearcherAgent: - return WebsearchResearcherAgent.create(tool_manager=tool_manager, **kwargs) \ No newline at end of file + return WebsearchResearcherAgent.create(tool_manager=tool_manager, **kwargs) diff --git a/dataflow_agent/promptstemplates/resources/pt_long_paper_repo.py b/dataflow_agent/promptstemplates/resources/pt_long_paper_repo.py index 147451a8..aa844e55 100644 --- a/dataflow_agent/promptstemplates/resources/pt_long_paper_repo.py +++ b/dataflow_agent/promptstemplates/resources/pt_long_paper_repo.py @@ -12,7 +12,7 @@ class LongPaperOutlineAgent: """ # 系统提示词:与普通 outline_agent 共享或专用 - system_prompt_for_outline_agent = """ + system_prompt_for_long_paper_outline_agent = """ 你是一位拥有丰富学术汇报经验的PPT设计专家及大纲生成助手。你的核心任务是将一篇学术论文(或长文档的一部分)转化为一份逻辑清晰、视觉布局合理的PPT演示大纲。 请遵循以下严格规则: @@ -20,6 +20,8 @@ class LongPaperOutlineAgent: 2. **视觉导向**:在规划每一页PPT时,不仅要生成文字内容,必须明确指出该页是否需要展示特定的插图(Images)或表格(Tables)。 3. **布局建议**:为每一页提供具体的布局指导(例如:左文右图、上标题下表格、两栏对比等)。 4. **格式严格**:输出必须且只能是标准的 JSON 格式数组。严禁包含 markdown 标记(如 ```json)、前言、后语或任何非 JSON 字符。 +5. **语言绝对一致**:如果 `language=en`,则 `title`、`layout_description`、`key_points` 中禁止出现中文;如果 `language=zh`,则这些字段必须全部使用中文。严禁中英混用。 +6. **key_points 只能是字符串数组**:`key_points` 中每个元素必须是纯字符串,绝对不能输出对象、嵌套数组或带 `text/value/content` 字段的结构。 """ # 1. 首页 Prompt (Is First Batch) @@ -38,6 +40,9 @@ class LongPaperOutlineAgent: 2. 后续页面开始进入正文介绍(如背景、引言、核心问题等)。 3.输出内容的语言为 **{language}**。 4. 不需要致谢页(除非文本很短,这是唯一一批)。 +5. **必须严格返回恰好 {pages_to_generate} 个 JSON 数组元素,不能少也不能多。** +6. `key_points` 必须是 `List`,每个元素都是一句简洁要点,不允许对象。 +7. 如果 `{language}` 为 `en`,输出中不得包含中文字符。 **输出格式要求(JSON Array):** 请返回一个 JSON 数组,数组中每个对象代表一页PPT,结构如下: @@ -78,6 +83,9 @@ class LongPaperOutlineAgent: 2. 承接上一批次的内容,继续展开当前的章节。 3. 如果文本包含新的章节标题,请作为新的一页或新章节的开始。 4. 输出内容的语言为 **{language}**。 +5. **必须严格返回恰好 {pages_to_generate} 个 JSON 数组元素,不能少也不能多。** +6. `key_points` 必须是 `List`,每个元素都是一句简洁要点,不允许对象。 +7. 如果 `{language}` 为 `en`,输出中不得包含中文字符。 **输出格式要求(JSON Array):** JSON 数组,每个对象代表一页PPT。 @@ -115,6 +123,9 @@ class LongPaperOutlineAgent: 1. 生成剩余的正文内容(结论、未来展望等)。 2. **最后一页必须是致谢(Thank You)**:简短的结束语。 3.输出内容的语言为 **{language}**。 +4. **必须严格返回恰好 {pages_to_generate} 个 JSON 数组元素,不能少也不能多。** +5. `key_points` 必须是 `List`,每个元素都是一句简洁要点,不允许对象。 +6. 如果 `{language}` 为 `en`,输出中不得包含中文字符。 **输出格式要求(JSON Array):** JSON 数组,每个对象代表一页PPT。 @@ -152,6 +163,7 @@ class ContentExpander: system_prompt_for_content_expander = """ 你是一个专业的学术写作助手和内容扩写专家。你的任务是将输入的简短文本或草稿,扩写成篇幅更长、细节更丰富、逻辑更严密的文章或报告。 你的扩写应保持专业性,增加必要的背景介绍、详细的解释、具体的例子或论证,以满足生成长篇 PPT 的内容需求。 +请严格遵守目标输出语言要求:如果 `language=en`,整个输出必须完全使用英文;如果 `language=zh`,整个输出必须完全使用中文。严禁中英混写。 """ task_prompt_for_content_expander = """ @@ -165,8 +177,9 @@ class ContentExpander: 1. **大幅增加篇幅**:在保持原意的前提下,通过增加细节、举例、背景分析、优缺点对比等方式,显著增加字数。 2. **结构完整**:如果输入是片段,请将其补全为完整的章节;如果输入是提纲,请将其展开为全文。 3. **保持连贯**:确保扩写后的内容逻辑通顺,段落过渡自然。 -4. **输出限制**:直接输出扩写后的完整文本,不要包含任何类似于“好的,这是扩写后的内容”的废话。不要使用 Markdown 代码块包裹。 -5. 如果需要表格,必须输出md表格内容,Table_1, xxx +4. **输出语言**:本轮扩写后的全文必须严格使用 **{language}**。如果 `{language}` 为 `en`,输出中不得包含中文字符;如果 `{language}` 为 `zh`,输出必须全部使用中文。 +5. **输出限制**:直接输出扩写后的完整文本,不要包含任何类似于“好的,这是扩写后的内容”的废话。不要使用 Markdown 代码块包裹。 +6. 如果需要表格,必须输出md表格内容,Table_1, xxx 请开始扩写: """ diff --git a/dataflow_agent/promptstemplates/resources/pt_paper2ppt_outline_repo.py b/dataflow_agent/promptstemplates/resources/pt_paper2ppt_outline_repo.py new file mode 100644 index 00000000..53e3b47b --- /dev/null +++ b/dataflow_agent/promptstemplates/resources/pt_paper2ppt_outline_repo.py @@ -0,0 +1,74 @@ +""" +Prompt templates dedicated to paper2ppt outline generation/refinement. +""" + + +class Paper2PPTOutline: + system_prompt_for_paper2ppt_outline_agent = """ +你是一位拥有丰富学术汇报经验的 PPT 设计专家及大纲生成助手。你的核心任务是将一篇学术论文或一段研究正文转化为逻辑清晰、视觉布局合理的 PPT 演示大纲。 + +请遵循以下严格规则: +1. 深度理解:仔细阅读输入内容,提取核心论点、方法、实验结果和结论。 +2. 视觉导向:在规划每一页 PPT 时,明确指出该页适合的布局,并仅在确有必要时引用一个原图或表格。 +3. 格式严格:输出必须且只能是标准 JSON 数组。严禁包含 markdown 标记、前言、后语或任何非 JSON 字符。 +4. 语言绝对一致:如果 `language=en`,则 `title`、`layout_description`、`key_points` 中禁止出现中文;如果 `language=zh`,则这些字段必须全部使用中文。严禁中英混用。 +5. key_points 只能是字符串数组:`key_points` 中每个元素必须是纯字符串,绝对不能输出对象、嵌套数组或带 `text/value/content` 字段的结构。 +6. 页面粒度:每个数组元素必须只对应一页 PPT,不能把整篇论文原文直接塞进单页。 +7. 要点长度:每个 `key_points` 元素必须是面向 PPT 的短句;不要输出大段原文摘抄。 +""" + + task_prompt_for_paper2ppt_outline_agent = """ +请根据以下提供的论文全文内容,生成一份详细的 PPT 演示文稿大纲。 + +输入论文内容: +{text_content} +{minueru_output} + +约束条件: +1. 目标 PPT 页数:{page_count} 页。 +2. 第一页必须是封面,只保留主题和汇报人,不要额外正文。 +3. 最后一页必须是致谢 / Thank You。 +4. 输出语言必须严格使用 {language}。 +5. 每一页只能给出该页需要的摘要和要点,禁止把长段论文原文复制进单页。 +6. `key_points` 必须是 `List`,每个元素都是一句简洁要点。 + +输出格式要求(JSON Array): +[ + {{ + "title": "Slide title", + "layout_description": "具体版式说明", + "key_points": ["要点1", "要点2"], + "asset_ref": null + }} +] +""" + + system_prompt_for_paper2ppt_outline_refine_agent = """ +你是一位拥有丰富学术汇报经验的 PPT 设计专家及大纲编辑助手。你的核心任务是:在不改变页数与顺序的前提下,基于用户反馈与论文内容,对已有 PPT 大纲进行更精准、更完善的改写与补充。 + +请遵循以下严格规则: +1. 仅允许修改每页内容字段:`title` / `layout_description` / `key_points`。 +2. 默认保留 `asset_ref`,除非用户反馈明确要求修改。 +3. 禁止编造论文中不存在的具体事实、数值、指标或结论。 +4. 输出必须且只能是标准 JSON 数组。 +5. `key_points` 必须保持为纯字符串数组,且每个元素为适合 PPT 的简洁短句。 +""" + + task_prompt_for_paper2ppt_outline_refine_agent = """ +请根据以下提供的论文内容、当前大纲以及用户反馈,对大纲进行“只改内容”的修订与完善。 + +论文内容: +{text_content} +{minueru_output} + +当前大纲(JSON Array): +{pagecontent} + +用户反馈: +{outline_feedback} + +约束: +1. 页数必须保持不变,总页数仍为 {page_count}。 +2. 输出语言必须严格使用 {language}。 +3. 只返回合法 JSON 数组,不要返回任何解释性文字。 +""" diff --git a/dataflow_agent/workflow/wf_paper2page_content.py b/dataflow_agent/workflow/wf_paper2page_content.py index b5aab416..ae88df16 100644 --- a/dataflow_agent/workflow/wf_paper2page_content.py +++ b/dataflow_agent/workflow/wf_paper2page_content.py @@ -20,6 +20,23 @@ log = get_logger(__name__) + +def _resolve_outline_model(state: Paper2FigureState) -> str | None: + request = getattr(state, "request", None) + request_model = str(getattr(request, "model", "") or "").strip() + if request_model: + return request_model + + explicit_outline_model = str(getattr(request, "outline_model", "") or "").strip() + if explicit_outline_model: + return explicit_outline_model + + configured_outline_model = os.getenv("PAPER2PPT_OUTLINE_MODEL", "").strip() + if configured_outline_model: + return configured_outline_model + + return None + def _ensure_result_path(state: Paper2FigureState) -> str: """ 参考 wf_paper2figure_with_sam.py 的做法: @@ -256,6 +273,7 @@ async def outline_agent(state: Paper2FigureState) -> Paper2FigureState: """ agent = create_react_agent( name="outline_agent", + model_name=_resolve_outline_model(state), temperature=0.1, max_retries=5, parser_type="json", @@ -269,6 +287,7 @@ async def outline_refine_agent(state: Paper2FigureState) -> Paper2FigureState: """ agent = create_react_agent( name="outline_refine_agent", + model_name=_resolve_outline_model(state), parser_type="json", max_retries=5 ) @@ -282,6 +301,7 @@ async def deep_research_agent(state: Paper2FigureState) -> Paper2FigureState: log.info("[paper2page_content] Entering deep_research_agent...") agent = create_simple_agent( name="deep_research_agent", + model_name=_resolve_outline_model(state), temperature=0.7, parser_type="text", # 直接输出长文本 ) diff --git a/dataflow_agent/workflow/wf_paper2page_content_for_long_paper.py b/dataflow_agent/workflow/wf_paper2page_content_for_long_paper.py index 01896afd..3d3cef2f 100644 --- a/dataflow_agent/workflow/wf_paper2page_content_for_long_paper.py +++ b/dataflow_agent/workflow/wf_paper2page_content_for_long_paper.py @@ -1,9 +1,10 @@ from __future__ import annotations +import copy import json import os +import re import time -import copy from pathlib import Path from typing import List, Dict, Any @@ -25,6 +26,23 @@ log = get_logger(__name__) + +def _resolve_outline_model(state: Paper2FigureState) -> str | None: + request = getattr(state, "request", None) + request_model = str(getattr(request, "model", "") or "").strip() + if request_model: + return request_model + + explicit_outline_model = str(getattr(request, "outline_model", "") or "").strip() + if explicit_outline_model: + return explicit_outline_model + + configured_outline_model = os.getenv("PAPER2PPT_OUTLINE_MODEL", "").strip() + if configured_outline_model: + return configured_outline_model + + return None + """ Workflow: paper2page_content_for_long_paper Description: 专门用于处理长文档(如书籍、长论文、长篇报告)生成大量 PPT 页面的工作流。 @@ -93,6 +111,186 @@ def _calculate_target_chars(target_pages: int, text: str = "") -> int: return target +def _extract_plain_text(value: Any) -> str: + if value is None: + return "" + if isinstance(value, str): + return value.strip() + if isinstance(value, (int, float, bool)): + return str(value) + if isinstance(value, dict): + preferred_keys = ( + "text", + "value", + "content", + "summary", + "title", + "label", + "body", + "description", + "reason", + "point", + "raw", + ) + for key in preferred_keys: + extracted = _extract_plain_text(value.get(key)) + if extracted: + return extracted + for item in value.values(): + extracted = _extract_plain_text(item) + if extracted: + return extracted + return "" + if isinstance(value, (list, tuple, set)): + parts = [_extract_plain_text(item) for item in value] + return "\n\n".join(part for part in parts if part) + return str(value).strip() + + +def _normalize_outline_points(value: Any, *, limit: int = 5) -> List[str]: + if isinstance(value, list): + items = [_extract_plain_text(item) for item in value] + else: + items = [_extract_plain_text(value)] + cleaned = [item for item in items if item] + return cleaned[:limit] + + +def _clip_outline_point(text: str, *, limit: int = 220) -> str: + text = re.sub(r"\s+", " ", str(text or "")).strip() + if len(text) <= limit: + return text + clipped = text[:limit].rsplit(" ", 1)[0].strip() + return (clipped or text[:limit]).rstrip(",;:.- ") + "..." + + +def _normalize_outline_page_item(raw: Any) -> Dict[str, Any] | None: + if not isinstance(raw, dict): + return None + + title = _extract_plain_text(raw.get("title")) + layout_description = _extract_plain_text(raw.get("layout_description")) + key_points = _normalize_outline_points(raw.get("key_points"), limit=6) + asset_ref_text = _extract_plain_text(raw.get("asset_ref")) + + # ReAct 失败或空对象时,不要把错误占位直接透传给前端。 + if raw.get("error") and not title and not key_points: + return None + if not title and not layout_description and not key_points and not asset_ref_text: + return None + + normalized = dict(raw) + normalized["title"] = title + normalized["layout_description"] = layout_description + normalized["key_points"] = key_points + normalized["asset_ref"] = asset_ref_text or None + return normalized + + +def _normalize_outline_pages(items: List[Any]) -> List[Dict[str, Any]]: + normalized: List[Dict[str, Any]] = [] + for raw in items: + item = _normalize_outline_page_item(raw) + if item is not None: + normalized.append(item) + return normalized + + +def _split_batch_text_into_units(content: str) -> List[str]: + content = _extract_plain_text(content) + paragraphs = [part.strip() for part in re.split(r"\n\s*\n", content or "") if part.strip()] + if paragraphs: + return paragraphs + + lines = [line.strip() for line in (content or "").splitlines() if line.strip()] + if lines: + return lines + + collapsed = re.sub(r"\s+", " ", str(content or "")).strip() + if not collapsed: + return [] + + sentence_parts = [ + part.strip() + for part in re.split(r"(?<=[。!?!?\.])\s+", collapsed) + if part.strip() + ] + if len(sentence_parts) > 1: + return sentence_parts + + chunk_size = 220 + return [collapsed[i:i + chunk_size].strip() for i in range(0, len(collapsed), chunk_size) if collapsed[i:i + chunk_size].strip()] + + +def _build_fallback_pages_for_batch( + *, + batch: Dict[str, Any], + existing_pages: List[Dict[str, Any]], + page_budget: int, + language: str, +) -> List[Dict[str, Any]]: + if page_budget <= 0: + return [] + + batch_titles = [str(title).strip() for title in (batch.get("section_titles") or []) if str(title).strip()] + units = _split_batch_text_into_units(str(batch.get("content") or "")) + if not units: + units = ["Content summary pending refinement."] + + missing = max(0, page_budget - len(existing_pages)) + if missing <= 0: + return [] + + fallback_pages: List[Dict[str, Any]] = [] + unit_count = len(units) + chunk_size = max(1, (unit_count + missing - 1) // missing) + use_chinese = str(language or "").strip().lower().startswith("zh") + default_heading_prefix = "章节" if use_chinese else "Section" + closing_title = "感谢聆听" if use_chinese else "Thank You" + closing_points = ["感谢聆听", "欢迎交流与提问"] if use_chinese else ["Thank you for your attention.", "Questions & Discussion"] + fallback_layout = ( + "结构化学术内容页,包含一个简洁摘要和若干支持要点,延续前后页叙事。" + if use_chinese else + "Structured academic content slide with one concise summary paragraph and supporting bullet points. Preserve narrative continuity with neighboring slides." + ) + + for fallback_idx in range(missing): + if fallback_idx == missing - 1 and batch.get("is_last"): + fallback_pages.append({ + "title": closing_title, + "layout_description": ( + "结束页,包含简短致谢与答疑提示。" + if use_chinese else + "Closing page with a concise thank-you message and optional Q&A prompt." + ), + "key_points": closing_points, + "asset_ref": None, + }) + continue + + start = fallback_idx * chunk_size + end = min(unit_count, start + chunk_size) + excerpt_units = units[start:end] or units[-1:] + excerpt = " ".join(excerpt_units) + heading = batch_titles[min(fallback_idx, len(batch_titles) - 1)] if batch_titles else f"{default_heading_prefix} {fallback_idx + 1}" + points = [ + _clip_outline_point(text.strip()) + for text in excerpt_units[:4] + if text.strip() + ] + if not points: + points = [_clip_outline_point(excerpt[:220].strip())] if excerpt.strip() else ["Expand this section in the editor."] + + fallback_pages.append({ + "title": heading if fallback_idx == 0 else f"{heading} ({fallback_idx + 1})", + "layout_description": fallback_layout, + "key_points": points[:5], + "asset_ref": None, + }) + + return fallback_pages + + # ============================================================ # Workflow 工厂函数 # ============================================================ @@ -251,7 +449,7 @@ async def expand_text_iteratively(state: Paper2FigureState) -> Paper2FigureState TEXT 循环扩写:扩写到足够长度 """ target_pages = getattr(state, "target_pages", 60) - current_text = state.text_content or "" + current_text = _extract_plain_text(state.text_content) # 动态计算目标 target_chars = _calculate_target_chars(target_pages, current_text) @@ -266,6 +464,7 @@ async def expand_text_iteratively(state: Paper2FigureState) -> Paper2FigureState agent = create_simple_agent( name = "content_expander", + model_name=_resolve_outline_model(state), temperature=0.7, parser_type="text", ) @@ -276,9 +475,11 @@ async def expand_text_iteratively(state: Paper2FigureState) -> Paper2FigureState state = await agent.execute(state=state) - # 增加类型检查,防止 agent 返回 dict 导致后续切片报错 - # 用户要求:直接把字典当字符串 - current_text = str(state.text_content) if state.text_content else "" + expanded_text = _extract_plain_text(state.text_content) + if expanded_text: + current_text = expanded_text + else: + log.warning("[long_paper] 扩写结果为空,保留上一轮文本内容") # 重新计算目标(以防语言变化) target_chars = _calculate_target_chars(target_pages, current_text) @@ -299,12 +500,13 @@ async def generate_long_content_from_topic(state: Paper2FigureState) -> Paper2Fi target_pages = getattr(state, "target_pages", 60) max_rounds = state.max_rounds - current_text = state.text_content or "" + current_text = _extract_plain_text(state.text_content) target_chars = target_pages * 800 log.info(f"[long_paper] 从 TOPIC 生成长文,当前: {len(current_text)} 字符") agent = create_simple_agent( name="topic_writer", + model_name=_resolve_outline_model(state), parser_type="text", ) for round_num in range(max_rounds): @@ -313,7 +515,11 @@ async def generate_long_content_from_topic(state: Paper2FigureState) -> Paper2Fi state = await agent.execute(state=state) - current_text = str(state.text_content) if state.text_content else "" + generated_text = _extract_plain_text(state.text_content) + if generated_text: + current_text = generated_text + else: + log.warning("[long_paper] topic_writer 返回空内容,保留上一轮文本") # 动态更新目标 target_chars = _calculate_target_chars(target_pages, current_text) @@ -330,6 +536,7 @@ async def outline_refine_agent(state: Paper2FigureState) -> Paper2FigureState: """ agent = create_react_agent( name="outline_refine_agent", + model_name=_resolve_outline_model(state), parser_type="json", max_retries=5 ) @@ -345,7 +552,7 @@ async def consolidate_long_text(state: Paper2FigureState) -> Paper2FigureState: log.info(f"[long_paper] 使用 PDF markdown: {len(state.long_text)} 字符") elif state.text_content: # TEXT/TOPIC 路径使用 text_content - state.long_text = state.text_content + state.long_text = _extract_plain_text(state.text_content) log.info(f"[long_paper] 使用 text_content: {len(state.long_text)} 字符") else: state.long_text = "" @@ -372,6 +579,7 @@ async def ensure_sufficient_content(state: Paper2FigureState) -> Paper2FigureSta log.info(f"[long_paper] 内容不足({len(long_text)} < {target_chars} chars),开始补充扩写") agent = create_content_expander( + model_name=_resolve_outline_model(state), temperature=0.7, parser_type="text", ) @@ -385,9 +593,11 @@ async def ensure_sufficient_content(state: Paper2FigureState) -> Paper2FigureSta state = await agent.execute(state=state) - # 增加类型检查 - # 用户要求:直接把字典当字符串 - current_text = str(state.text_content) if state.text_content else "" + expanded_text = _extract_plain_text(state.text_content) + if expanded_text: + current_text = expanded_text + else: + log.warning("[long_paper] 补充扩写结果为空,继续使用已有正文") # 重新计算目标 target_chars = _calculate_target_chars(target_pages, current_text) @@ -434,6 +644,7 @@ async def generate_outline_for_batch( # 调用 long_paper_outline_agent agent = create_react_agent( name = "long_paper_outline_agent", + model_name=_resolve_outline_model(state), temperature=0.1, max_retries=5, parser_type="json", @@ -445,6 +656,7 @@ async def generate_outline_for_batch( pages = result_state.pagecontent or [] if not isinstance(pages, list): pages = [pages] + pages = _normalize_outline_pages(pages) log.info(f"[long_paper] 批次 {batch_idx + 1}/{total_batches} 生成了 {len(pages)} 页") return pages @@ -514,23 +726,46 @@ async def outline_for_long_text(state: Paper2FigureState) -> Paper2FigureState: results = await asyncio.gather(*tasks) log.info(f"[long_paper] 并行执行完成,收到 {len(results)} 个结果") + normalized_batches: List[tuple[List[Dict[str, Any]], Dict[str, Any]]] = [] + for chunk_pages, batch in zip(results, batch_info): + page_budget = int(batch.get("pages_to_generate", 1) or 1) + selected = list(_normalize_outline_pages(chunk_pages)[:page_budget]) + normalized_batches.append((selected, batch)) + + if normalized_batches and all(len(selected) == 0 for selected, _ in normalized_batches): + log.error("[long_paper] 所有批次均未生成有效 outline,拒绝使用全量 fallback 伪造大纲") + state.pagecontent = [] + setattr(state, "outline_generation_error", "long_paper_outline_agent returned no valid pages for every batch") + return state + # 5. 按顺序处理结果 all_pages = [] - for chunk_pages, batch in zip(results, batch_info): + for selected, batch in normalized_batches: batch_idx = int(batch.get("batch_index", 0)) page_budget = int(batch.get("pages_to_generate", 1) or 1) - selected = list(chunk_pages[:page_budget]) - if len(chunk_pages) > page_budget: + raw_count = len(selected) + if raw_count > page_budget: log.warning( - f"[long_paper] 批次 {batch_idx + 1}: 生成 {len(chunk_pages)} 页," + f"[long_paper] 批次 {batch_idx + 1}: 生成 {raw_count} 页," f"按预算保留 {page_budget} 页" ) - elif len(chunk_pages) < page_budget: + elif raw_count < page_budget: log.warning( - f"[long_paper] 批次 {batch_idx + 1}: 生成页数不足 {len(chunk_pages)}/{page_budget}" + f"[long_paper] 批次 {batch_idx + 1}: 生成页数不足 {raw_count}/{page_budget}" + ) + fallback_pages = _build_fallback_pages_for_batch( + batch=batch, + existing_pages=selected, + page_budget=page_budget, + language=getattr(getattr(state, "request", None), "language", "en"), ) + if fallback_pages: + log.warning( + f"[long_paper] 批次 {batch_idx + 1}: 使用 {len(fallback_pages)} 页 fallback 补齐到 {page_budget} 页" + ) + selected.extend(fallback_pages) else: - log.info(f"[long_paper] 批次 {batch_idx + 1}: 生成 {len(chunk_pages)} 页,符合预算") + log.info(f"[long_paper] 批次 {batch_idx + 1}: 生成 {raw_count} 页,符合预算") all_pages.extend(selected) if len(all_pages) != target_pages: diff --git a/fastapi_app/.env.example b/fastapi_app/.env.example index 404fb4ed..18468434 100644 --- a/fastapi_app/.env.example +++ b/fastapi_app/.env.example @@ -1,6 +1,9 @@ # ============================================ -# 使用说明 +# 使用说明(Advanced / Fine-grained) # ============================================ +# 如果你只想填很少的 URL / Key,请优先使用: +# fastapi_app/.env.simple.example +# 当前这个 .env.example 是“细粒度 / 高级模式”示例,保留所有 workflow / model 级别开关。 # 1. 复制本文件为 .env:cp .env.example .env # 2. 在 .env 中填入真实 key(不要提交 .env 到 git) # 3. 启动后端后会自动加载 fastapi_app/.env,无需在终端 export diff --git a/fastapi_app/.env.simple.example b/fastapi_app/.env.simple.example new file mode 100644 index 00000000..1f45daa8 --- /dev/null +++ b/fastapi_app/.env.simple.example @@ -0,0 +1,51 @@ +# ============================================ +# Paper2Any Backend - Simple Mode Example +# ============================================ +# 目标: +# - 用户只填少量 URL / Key +# - 不再逐个 workflow 配模型 +# - 所有 workflow 文本模型统一走 SIMPLE_TEXT_* +# - 所有生图 workflow 统一走 SIMPLE_IMAGE_* +# - OCR / VLM 统一走 SIMPLE_OCR_* + +BACKEND_API_KEY=your-backend-api-key +APP_BILLING_MODE=free +PAPER2ANY_CONFIG_MODE=simple + +# 文本模型入口(统一给 paper2ppt / kb / drawio / report / rebuttal 等使用) +SIMPLE_TEXT_API_URL=http://123.129.219.111:3000/v1 +SIMPLE_TEXT_API_KEY=sk-your-text-key + +# 生图 / 改图入口(统一给 paper2ppt 图片版 / ppt2polish / paper2figure / poster 等使用) +SIMPLE_IMAGE_API_URL=https://api.ikuncode.cc +SIMPLE_IMAGE_API_KEY=sk-your-image-key + +# OCR / VLM 入口(统一给 pdf2ppt / drawio / 视觉理解类流程使用) +SIMPLE_OCR_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +SIMPLE_OCR_API_KEY=your-ocr-key + +# 下面这些模型一般不需要改;simple 模式下如果不填,就自动使用默认推荐值 +SIMPLE_TEXT_MODEL=gpt-4o +SIMPLE_IMAGE_MODEL=gemini-3-pro-image-preview +SIMPLE_VLM_MODEL=qwen-vl-ocr-2025-11-20 +SIMPLE_EMBEDDING_MODEL=text-embedding-3-small + +# 其他基础运行配置 +PAPER2FIGURE_TO_PPT_FORCE_AI_EDIT=true +PAPER2DRAWIO_ENABLE_VLM_VALIDATION=false +SAM3_SERVER_URLS=http://127.0.0.1:8021 +MINERU_API_BASE_URL=https://mineru.net/api/v4 +MINERU_API_KEY=your_mineru_api_key +PAPER2DRAWIO_OCR_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +PAPER2DRAWIO_OCR_API_KEY=your-ocr-key + +# 登录 / 历史 / 配额(可选) +SUPABASE_URL=https://your-project-id.supabase.co +SUPABASE_ANON_KEY=your_supabase_anon_key +SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key + +# 购买 / 兑换(可选) +POINTS_PURCHASE_URL= +POINTS_REDEEM_CODE_FILE_10=./data/redeem_codes/points_10.txt +POINTS_REDEEM_CODE_FILE_50=./data/redeem_codes/points_50.txt +POINTS_REDEEM_CODE_FILE_100=./data/redeem_codes/points_100.txt diff --git a/fastapi_app/config/settings.py b/fastapi_app/config/settings.py index 41793320..53d2852c 100644 --- a/fastapi_app/config/settings.py +++ b/fastapi_app/config/settings.py @@ -40,6 +40,18 @@ class AppSettings(BaseSettings): MODEL_QWEN_VL_OCR: str = "qwen-vl-ocr-2025-11-20" # API Configuration + PAPER2ANY_CONFIG_MODE: str = "advanced" + SIMPLE_TEXT_API_URL: str = "" + SIMPLE_TEXT_API_KEY: str = "" + SIMPLE_IMAGE_API_URL: str = "" + SIMPLE_IMAGE_API_KEY: str = "" + SIMPLE_OCR_API_URL: str = "" + SIMPLE_OCR_API_KEY: str = "" + SIMPLE_TEXT_MODEL: str = "gpt-4o" + SIMPLE_IMAGE_MODEL: str = "gemini-3-pro-image-preview" + SIMPLE_VLM_MODEL: str = "qwen-vl-ocr-2025-11-20" + SIMPLE_EMBEDDING_MODEL: str = "text-embedding-3-small" + DEFAULT_LLM_API_URL: str = "http://123.129.219.111:3000/v1/" DF_API_URL: str = "http://123.129.219.111:3000/v1" DF_API_KEY: str = "" @@ -123,6 +135,14 @@ class AppSettings(BaseSettings): PAPER2DRAWIO_SAM3_BPE_PATH: str = str(_project_root() / "models" / "sam3" / "bpe_simple_vocab_16e6.txt.gz") PAPER2DRAWIO_OCR_API_URL: str = "https://dashscope.aliyuncs.com/compatible-mode/v1" PAPER2DRAWIO_OCR_API_KEY: str = "" + PAPER2DRAWIO_SEGMENT_HINT_API_URL: str = "" + PAPER2DRAWIO_SEGMENT_HINT_API_KEY: str = "" + PAPER2DRAWIO_SEGMENT_HINT_VLM_MODEL: str = "gpt-4o" + PAPER2DRAWIO_SEGMENT_HINT_TIMEOUT: int = 120 + PAPER2PPT_SEGMENT_HINT_API_URL: str = "" + PAPER2PPT_SEGMENT_HINT_API_KEY: str = "" + PAPER2PPT_SEGMENT_HINT_VLM_MODEL: str = "gpt-4o" + PAPER2PPT_SEGMENT_HINT_TIMEOUT: int = 120 MINERU_API_BASE_URL: str = "https://mineru.net/api/v4" MINERU_API_KEY: str = "" MINERU_API_MODEL_VERSION: str = "vlm" @@ -151,22 +171,39 @@ class AppSettings(BaseSettings): PDF2PPT_DEFAULT_MODEL: str = "gpt-4o" PDF2PPT_DEFAULT_IMAGE_MODEL: str = "gemini-2.5-flash-image" + # Image2PPT Workflow + IMAGE2PPT_DEFAULT_MODEL: str = "gpt-4o" + IMAGE2PPT_DEFAULT_IMAGE_MODEL: str = "gemini-2.5-flash-image" + # Paper2Figure Workflow PAPER2FIGURE_DEFAULT_MODEL: str = "gpt-4o" PAPER2FIGURE_DEFAULT_IMAGE_MODEL: str = "gemini-3-pro-image-preview" # Paper2Video Workflow PAPER2VIDEO_DEFAULT_MODEL: str = "gpt-4o" + PAPER2VIDEO_TTS_MODEL: str = "cosyvoice-v3-flash" + PAPER2VIDEO_TALKING_MODEL: str = "liveportrait" # Paper2Drawio Workflow PAPER2DRAWIO_DEFAULT_MODEL: str = "gpt-5.4" PAPER2DRAWIO_VLM_MODEL: str = "gpt-4o" PAPER2DRAWIO_ENABLE_VLM_VALIDATION: bool = False + # Image2Drawio Workflow + IMAGE2DRAWIO_DEFAULT_MODEL: str = "gpt-4o" + IMAGE2DRAWIO_DEFAULT_IMAGE_MODEL: str = "gemini-3-pro-image-preview" + IMAGE2DRAWIO_VLM_MODEL: str = "qwen-vl-ocr-2025-11-20" + # Knowledge Base KB_EMBEDDING_MODEL: str = "gemini-2.5-flash" KB_CHAT_MODEL: str = "gpt-4o" + # MindMap / Poster / Rebuttal + MINDMAP_DEFAULT_MODEL: str = "gpt-4o" + PAPER2POSTER_DEFAULT_MODEL: str = "gpt-4o" + PAPER2POSTER_VISION_MODEL: str = "gpt-4o" + PAPER2REBUTTAL_DEFAULT_MODEL: str = "gpt-4o" + # ============================================ # Layer 3: Role-level Model Configuration # ============================================ @@ -196,5 +233,127 @@ class Config: extra = "ignore" +def _first_non_empty(*values: Optional[str]) -> str: + for value in values: + text = str(value or "").strip() + if text: + return text + return "" + + +def _apply_simple_mode(settings_obj: AppSettings) -> AppSettings: + mode = str(getattr(settings_obj, "PAPER2ANY_CONFIG_MODE", "") or "").strip().lower() + if mode != "simple": + return settings_obj + + text_api_url = _first_non_empty( + settings_obj.SIMPLE_TEXT_API_URL, + settings_obj.DF_API_URL, + settings_obj.DEFAULT_LLM_API_URL, + ) + text_api_key = _first_non_empty( + settings_obj.SIMPLE_TEXT_API_KEY, + settings_obj.DF_API_KEY, + ) + image_api_url = _first_non_empty( + settings_obj.SIMPLE_IMAGE_API_URL, + settings_obj.DF_IMAGE_API_URL, + text_api_url, + ) + image_api_key = _first_non_empty( + settings_obj.SIMPLE_IMAGE_API_KEY, + settings_obj.DF_IMAGE_API_KEY, + text_api_key, + ) + ocr_api_url = _first_non_empty( + settings_obj.SIMPLE_OCR_API_URL, + settings_obj.PAPER2DRAWIO_OCR_API_URL, + text_api_url, + ) + ocr_api_key = _first_non_empty( + settings_obj.SIMPLE_OCR_API_KEY, + settings_obj.PAPER2DRAWIO_OCR_API_KEY, + text_api_key, + ) + + text_model = _first_non_empty(settings_obj.SIMPLE_TEXT_MODEL, "gpt-4o") + image_model = _first_non_empty(settings_obj.SIMPLE_IMAGE_MODEL, "gemini-3-pro-image-preview") + vlm_model = _first_non_empty(settings_obj.SIMPLE_VLM_MODEL, "qwen-vl-ocr-2025-11-20") + embedding_model = _first_non_empty(settings_obj.SIMPLE_EMBEDDING_MODEL, "text-embedding-3-small") + + settings_obj.DEFAULT_LLM_API_URL = text_api_url or settings_obj.DEFAULT_LLM_API_URL + settings_obj.DF_API_URL = text_api_url or settings_obj.DF_API_URL + settings_obj.DF_API_KEY = text_api_key or settings_obj.DF_API_KEY + settings_obj.DF_IMAGE_API_URL = image_api_url or settings_obj.DF_IMAGE_API_URL + settings_obj.DF_IMAGE_API_KEY = image_api_key or settings_obj.DF_IMAGE_API_KEY + settings_obj.PAPER2DRAWIO_OCR_API_URL = ocr_api_url or settings_obj.PAPER2DRAWIO_OCR_API_URL + settings_obj.PAPER2DRAWIO_OCR_API_KEY = ocr_api_key or settings_obj.PAPER2DRAWIO_OCR_API_KEY + + for scope in ( + "PAPER2ANY", + "PAPER2PPT", + "PPT2POLISH", + "PDF2PPT", + "IMAGE2PPT", + "PAPER2DRAWIO", + "PAPER2POSTER", + "PAPER2VIDEO", + "KB", + "KB_DEEPRESEARCH", + "PAPER2REBUTTAL", + ): + setattr(settings_obj, f"{scope}_MANAGED_API_URL", text_api_url) + setattr(settings_obj, f"{scope}_MANAGED_API_KEY", text_api_key) + setattr(settings_obj, f"{scope}_MANAGED_IMAGE_API_URL", image_api_url) + setattr(settings_obj, f"{scope}_MANAGED_IMAGE_API_KEY", image_api_key) + + settings_obj.PAPER2DRAWIO_SEGMENT_HINT_API_URL = text_api_url + settings_obj.PAPER2DRAWIO_SEGMENT_HINT_API_KEY = text_api_key + settings_obj.PAPER2PPT_SEGMENT_HINT_API_URL = text_api_url + settings_obj.PAPER2PPT_SEGMENT_HINT_API_KEY = text_api_key + + settings_obj.PAPER2PPT_DEFAULT_MODEL = text_model + settings_obj.PAPER2PPT_DEFAULT_IMAGE_MODEL = image_model + settings_obj.PDF2PPT_DEFAULT_MODEL = text_model + settings_obj.PDF2PPT_DEFAULT_IMAGE_MODEL = image_model + settings_obj.IMAGE2PPT_DEFAULT_MODEL = text_model + settings_obj.IMAGE2PPT_DEFAULT_IMAGE_MODEL = image_model + settings_obj.PAPER2FIGURE_DEFAULT_MODEL = text_model + settings_obj.PAPER2FIGURE_DEFAULT_IMAGE_MODEL = image_model + settings_obj.PAPER2VIDEO_DEFAULT_MODEL = text_model + settings_obj.PAPER2VIDEO_TTS_MODEL = settings_obj.PAPER2VIDEO_TTS_MODEL or "cosyvoice-v3-flash" + settings_obj.PAPER2VIDEO_TALKING_MODEL = settings_obj.PAPER2VIDEO_TALKING_MODEL or "liveportrait" + settings_obj.PAPER2DRAWIO_DEFAULT_MODEL = text_model + settings_obj.PAPER2DRAWIO_VLM_MODEL = vlm_model + settings_obj.IMAGE2DRAWIO_DEFAULT_MODEL = text_model + settings_obj.IMAGE2DRAWIO_DEFAULT_IMAGE_MODEL = image_model + settings_obj.IMAGE2DRAWIO_VLM_MODEL = vlm_model + settings_obj.KB_CHAT_MODEL = text_model + settings_obj.KB_EMBEDDING_MODEL = embedding_model + settings_obj.MINDMAP_DEFAULT_MODEL = text_model + settings_obj.PAPER2POSTER_DEFAULT_MODEL = text_model + settings_obj.PAPER2POSTER_VISION_MODEL = text_model + settings_obj.PAPER2REBUTTAL_DEFAULT_MODEL = text_model + + settings_obj.PAPER2PPT_OUTLINE_MODEL = text_model + settings_obj.PAPER2PPT_CONTENT_MODEL = text_model + settings_obj.PAPER2PPT_IMAGE_GEN_MODEL = image_model + settings_obj.PAPER2PPT_VLM_MODEL = vlm_model + settings_obj.PAPER2PPT_CHART_MODEL = text_model + settings_obj.PAPER2PPT_DESC_MODEL = text_model + settings_obj.PAPER2PPT_TECHNICAL_MODEL = text_model + + settings_obj.PAPER2FIGURE_TEXT_MODEL = text_model + settings_obj.PAPER2FIGURE_IMAGE_MODEL = image_model + settings_obj.PAPER2FIGURE_VLM_MODEL = vlm_model + settings_obj.PAPER2FIGURE_CHART_MODEL = text_model + settings_obj.PAPER2FIGURE_DESC_MODEL = text_model + settings_obj.PAPER2FIGURE_REF_IMG_DESC_MODEL = text_model + settings_obj.PAPER2FIGURE_TECHNICAL_MODEL = text_model + + settings_obj.PAPER2CITATION_WEBSEARCH_MODEL = settings_obj.PAPER2CITATION_WEBSEARCH_MODEL or text_model + return settings_obj + + # Global configuration instance -settings = AppSettings() +settings = _apply_simple_mode(AppSettings()) diff --git a/fastapi_app/main.py b/fastapi_app/main.py index d48a4229..4fa17f6d 100644 --- a/fastapi_app/main.py +++ b/fastapi_app/main.py @@ -34,8 +34,8 @@ def _configure_runtime_tempdir() -> None: from fastapi_app.config import settings from fastapi_app.routers import account from fastapi_app.routers import paper2video -from fastapi_app.routers import paper2any, paper2citation, paper2ppt, paper2poster -from fastapi_app.routers import pdf2ppt, image2ppt, kb, kb_embedding, files +from fastapi_app.routers import paper2any, paper2citation, paper2figure, paper2ppt, paper2poster +from fastapi_app.routers import pdf2ppt, image2ppt, kb, kb_workflows, kb_embedding, files from fastapi_app.routers import image2drawio from fastapi_app.routers import mindmap from fastapi_app.routers import paper2drawio @@ -86,6 +86,7 @@ def create_app() -> FastAPI: # 路由挂载 # Paper2Graph / System app.include_router(paper2any.router, prefix="/api/v1", tags=["paper2any"]) + app.include_router(paper2figure.router, prefix="/api/v1", tags=["paper2figure"]) app.include_router(account.router, prefix="/api/v1", tags=["account"]) # Paper2PPT app.include_router(paper2ppt.router, prefix="/api/v1", tags=["paper2ppt"]) @@ -105,6 +106,7 @@ def create_app() -> FastAPI: app.include_router(mindmap.router, prefix="/api/v1", tags=["mindmap"]) # 知识库接口 app.include_router(kb.router, prefix="/api/v1", tags=["Knowledge Base"]) + app.include_router(kb_workflows.router, prefix="/api/v1", tags=["Knowledge Base Workflows"]) app.include_router(kb_embedding.router, prefix="/api/v1", tags=["Knowledge Base Embedding"]) # 文件管理接口 app.include_router(files.router, prefix="/api/v1", tags=["Files"]) diff --git a/fastapi_app/routers/__init__.py b/fastapi_app/routers/__init__.py index 3068c824..ef3eb627 100644 --- a/fastapi_app/routers/__init__.py +++ b/fastapi_app/routers/__init__.py @@ -10,10 +10,12 @@ paper2citation, paper2video, paper2any, + paper2figure, paper2ppt, pdf2ppt, image2ppt, kb, + kb_workflows, kb_embedding, files, image2drawio, @@ -27,10 +29,12 @@ "paper2citation", "paper2video", "paper2any", + "paper2figure", "paper2ppt", "pdf2ppt", "image2ppt", "kb", + "kb_workflows", "kb_embedding", "files", "image2drawio", diff --git a/fastapi_app/routers/image2drawio.py b/fastapi_app/routers/image2drawio.py index dd63d7b6..f2ce8b9f 100644 --- a/fastapi_app/routers/image2drawio.py +++ b/fastapi_app/routers/image2drawio.py @@ -6,6 +6,7 @@ from pydantic import BaseModel from fastapi_app.config.settings import settings +from fastapi_app.services.managed_api_service import resolve_model_name router = APIRouter(prefix="/image2drawio", tags=["image2drawio"]) @@ -37,9 +38,21 @@ async def generate_image2drawio( chat_api_url=chat_api_url, api_key=api_key, email=email, - model=model, - gen_fig_model=gen_fig_model, - vlm_model=vlm_model, + model=resolve_model_name( + model, + managed_default=settings.IMAGE2DRAWIO_DEFAULT_MODEL, + fallback_default="gpt-4o", + ), + gen_fig_model=resolve_model_name( + gen_fig_model, + managed_default=settings.IMAGE2DRAWIO_DEFAULT_IMAGE_MODEL, + fallback_default="gemini-3-pro-image-preview", + ), + vlm_model=resolve_model_name( + vlm_model, + managed_default=settings.IMAGE2DRAWIO_VLM_MODEL, + fallback_default="qwen-vl-ocr-2025-11-20", + ), language=language, ) return Image2DrawioResponse(**result) diff --git a/fastapi_app/routers/image2ppt.py b/fastapi_app/routers/image2ppt.py index 1e1bbd4b..1449fff5 100644 --- a/fastapi_app/routers/image2ppt.py +++ b/fastapi_app/routers/image2ppt.py @@ -6,6 +6,8 @@ from fastapi.responses import FileResponse from dataflow_agent.logger import get_logger +from fastapi_app.config import settings +from fastapi_app.services.managed_api_service import resolve_model_name log = get_logger(__name__) @@ -51,8 +53,16 @@ async def generate_image2ppt( api_key=api_key, email=email, use_ai_edit=use_ai_edit, - model=model, - gen_fig_model=gen_fig_model, + model=resolve_model_name( + model, + managed_default=settings.IMAGE2PPT_DEFAULT_MODEL, + fallback_default="gpt-4o", + ), + gen_fig_model=resolve_model_name( + gen_fig_model, + managed_default=settings.IMAGE2PPT_DEFAULT_IMAGE_MODEL, + fallback_default="gemini-2.5-flash-image", + ), language=language, style=style, page_count=page_count, diff --git a/fastapi_app/routers/kb.py b/fastapi_app/routers/kb.py index aefd6ea0..336e3780 100644 --- a/fastapi_app/routers/kb.py +++ b/fastapi_app/routers/kb.py @@ -672,492 +672,10 @@ async def chat_with_kb( traceback.print_exc() raise HTTPException(status_code=500, detail=str(e)) -@router.post("/generate-ppt") -async def generate_ppt_from_kb( - file_path: Optional[str] = Body(None, embed=True), - file_paths: Optional[List[str]] = Body(None, embed=True), - image_paths: Optional[List[str]] = Body(None, embed=True), - image_items: Optional[List[Dict[str, Any]]] = Body(None, embed=True), - query: Optional[str] = Body("", embed=True), - need_embedding: bool = Body(False, embed=True), - search_top_k: int = Body(8, embed=True), - user_id: Optional[str] = Body(None, embed=True), - email: Optional[str] = Body(None, embed=True), - notebook_id: Optional[str] = Body(None, embed=True), - api_url: str = Body(..., embed=True), - api_key: str = Body(..., embed=True), - style: str = Body("modern", embed=True), - language: str = Body("zh", embed=True), - page_count: int = Body(10, embed=True), - model: str = Body("gpt-4o", embed=True), - gen_fig_model: str = Body("gemini-2.5-flash-image", embed=True), - user: AuthUser = Depends(get_current_user), -): - """ - Generate PPT from knowledge base file (non-interactive) - """ - try: - api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") - email, user_id = _resolve_kb_identity(user) - # Normalize and validate input files (PDF/PPT/DOC/IMG) - input_paths = file_paths or ([file_path] if file_path else []) - if not input_paths: - raise HTTPException(status_code=400, detail="No input files provided") - - # Create output directory - project_root = get_project_root() - if notebook_id: - output_dir = _generated_dir(email, notebook_id, "ppt", user_id) - else: - ts = int(time.time()) - output_dir = project_root / "outputs" / "kb_outputs" / email / f"{ts}_ppt" - output_dir.mkdir(parents=True, exist_ok=True) - - # Split docs/images - doc_paths: List[Path] = [] - user_image_items: List[Dict[str, Any]] = [] - for p in input_paths: - local_path = _resolve_user_owned_output_path(p, user) - if not local_path.exists(): - raise HTTPException(status_code=404, detail=f"File not found: {p}") - ext = local_path.suffix.lower() - if ext in IMAGE_EXTENSIONS: - user_image_items.append({"path": str(local_path), "description": ""}) - elif ext in {".pdf", ".pptx", ".ppt", ".docx", ".doc"}: - doc_paths.append(local_path) - else: - raise HTTPException(status_code=400, detail=f"Unsupported file type for PPT: {local_path.name}") - - if not doc_paths: - raise HTTPException(status_code=400, detail="At least one document file is required for PPT generation") - - # Convert docs to PDF for MinerU merge - local_pdf_paths: List[Path] = [] - convert_dir = output_dir / "input" - convert_dir.mkdir(parents=True, exist_ok=True) - for p in doc_paths: - ext = p.suffix.lower() - if ext == ".pdf": - local_pdf_paths.append(p) - elif ext in {".pptx", ".ppt", ".docx", ".doc"}: - local_pdf_paths.append(_convert_to_pdf(p, convert_dir)) - else: - raise HTTPException(status_code=400, detail=f"Unsupported file type for PPT: {p.name}") - - # Merge PDFs if multiple - if len(local_pdf_paths) > 1: - merge_dir = output_dir / "input" - merged_pdf = merge_dir / "merged.pdf" - local_file_path = _merge_pdfs(local_pdf_paths, merged_pdf) - else: - local_file_path = local_pdf_paths[0] - - # Normalize image items (optional) - resolved_image_items: List[Dict[str, Any]] = [] - for item in image_items or []: - raw_path = item.get("path") or item.get("url") or "" - if not raw_path: - continue - img_path = _resolve_user_owned_output_path(str(raw_path), user) - if img_path.exists() and img_path.suffix.lower() in IMAGE_EXTENSIONS: - resolved_image_items.append({ - "path": str(img_path), - "description": item.get("description") or item.get("desc") or "" - }) - - for img in image_paths or []: - img_path = _resolve_user_owned_output_path(img, user) - if img_path.exists() and img_path.suffix.lower() in IMAGE_EXTENSIONS: - resolved_image_items.append({ - "path": str(img_path), - "description": "" - }) - - resolved_image_items.extend(user_image_items) - - # Embedding + retrieval (optional) - retrieval_text = "" - if need_embedding: - if notebook_id: - base_dir = _vector_store_dir(email, notebook_id, user_id) - else: - base_dir = project_root / "outputs" / "kb_data" / email / "vector_store" - embed_api_url = api_url - if "/embeddings" not in embed_api_url: - embed_api_url = embed_api_url.rstrip("/") + "/embeddings" - - files_for_embed = [{"path": str(p), "description": ""} for p in doc_paths] - from dataflow_agent.toolkits.ragtool.vector_store_tool import process_knowledge_base_files - - manifest = await process_knowledge_base_files( - files_for_embed, - base_dir=str(base_dir), - api_url=embed_api_url, - api_key=api_key, - model_name=None, - multimodal_model=None, - ) - - from dataflow_agent.toolkits.ragtool.vector_store_tool import VectorStoreManager - - manager = VectorStoreManager( - base_dir=str(base_dir), - embedding_api_url=embed_api_url, - api_key=api_key, - ) - - def _match_file_ids(m: Dict[str, Any], paths: List[Path]) -> List[str]: - ids: List[str] = [] - target = {str(p.resolve()) for p in paths} - for f in m.get("files", []): - try: - if str(Path(f.get("original_path", "")).resolve()) in target: - if f.get("id"): - ids.append(f["id"]) - except Exception: - continue - return ids - - file_ids = _match_file_ids(manifest or manager.manifest or {}, doc_paths) - if query and file_ids: - results = manager.search(query=query, top_k=search_top_k, file_ids=file_ids) - retrieval_text = "\n\n".join([r.get("content", "") for r in results if r.get("content")]) - - # Prepare request - ppt_req = Paper2PPTRequest( - input_type="PDF", - input_content=str(local_file_path), - email=email, - chat_api_url=api_url, - chat_api_key=api_key, - api_key=api_key, - style=style, - language=language, - page_count=page_count, - model=model, - gen_fig_model=gen_fig_model, - aspect_ratio="16:9", - use_long_paper=False - ) - - # Run KB pagecontent workflow - from fastapi_app.workflow_adapters.wa_paper2ppt import _init_state_from_request - - state_pc = _init_state_from_request(ppt_req, result_path=output_dir) - state_pc.kb_query = query or "" - state_pc.kb_retrieval_text = retrieval_text - state_pc.kb_user_images = resolved_image_items - state_pc = await run_workflow("kb_page_content", state_pc) - pagecontent = getattr(state_pc, "pagecontent", []) or [] - - # Run PPT generation with injected pagecontent - state_pc.pagecontent = pagecontent - state_pp = await run_workflow("paper2ppt_parallel_consistent_style", state_pc) - - # Extract output paths - pdf_path = "" - pptx_path = "" - if hasattr(state_pp, 'ppt_pdf_path'): - pdf_path = state_pp.ppt_pdf_path - if hasattr(state_pp, 'ppt_pptx_path'): - pptx_path = state_pp.ppt_pptx_path - - return { - "success": True, - "result_path": str(output_dir), - "pdf_path": _to_outputs_url(pdf_path) if pdf_path else "", - "pptx_path": _to_outputs_url(pptx_path) if pptx_path else "", - "output_file_id": f"kb_ppt_{ts}" - } - - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/generate-podcast") -async def generate_podcast_from_kb( - file_paths: List[str] = Body(..., embed=True), - user_id: Optional[str] = Body(None, embed=True), - email: Optional[str] = Body(None, embed=True), - notebook_id: Optional[str] = Body(None, embed=True), - api_url: str = Body(..., embed=True), - api_key: str = Body(..., embed=True), - model: str = Body("gpt-4o", embed=True), - tts_model: str = Body("cosyvoice-v3-flash", embed=True), - voice_name: str = Body("", embed=True), - voice_name_b: str = Body("Puck", embed=True), - podcast_mode: str = Body("monologue", embed=True), - podcast_length: str = Body("standard", embed=True), - language: str = Body("zh", embed=True), - user: AuthUser = Depends(get_current_user), -): - """ - Generate podcast from knowledge base files - """ - try: - api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") - email, user_id = _resolve_kb_identity(user) - project_root = get_project_root() - if notebook_id: - output_dir = _generated_dir(email, notebook_id, "podcast", user_id) - else: - ts = int(time.time()) - output_dir = project_root / "outputs" / "kb_outputs" / email / f"{ts}_podcast" - output_dir.mkdir(parents=True, exist_ok=True) - - # Normalize file paths - if not file_paths: - raise HTTPException(status_code=400, detail="No valid files provided") - - local_paths: List[Path] = [] - for f in file_paths: - local_path = _resolve_user_owned_output_path(f, user) - if not local_path.exists(): - raise HTTPException(status_code=404, detail=f"File not found: {f}") - local_paths.append(local_path) - - # If multiple files, merge into a single PDF (doc/ppt will be converted) - if len(local_paths) > 1: - merge_dir = output_dir / "input" - merge_dir.mkdir(parents=True, exist_ok=True) - - pdf_paths: List[Path] = [] - for p in local_paths: - ext = p.suffix.lower() - if ext == ".pdf": - pdf_paths.append(p) - elif ext in {".docx", ".doc", ".pptx", ".ppt"}: - pdf_paths.append(_convert_to_pdf(p, merge_dir)) - else: - raise HTTPException(status_code=400, detail=f"Unsupported file type for podcast: {p.name}") - - merged_pdf = merge_dir / "merged.pdf" - local_file_paths = [str(_merge_pdfs(pdf_paths, merged_pdf))] - else: - local_file_paths = [str(local_paths[0])] - - # Prepare request - podcast_req = KBPodcastRequest( - files=local_file_paths, - chat_api_url=api_url, - api_key=api_key, - model=model, - tts_model=tts_model, - voice_name=voice_name, - voice_name_b=voice_name_b, - podcast_mode=podcast_mode, - podcast_length=podcast_length, - language=language - ) - podcast_req.email = email - - state = KBPodcastState(request=podcast_req, result_path=str(output_dir)) - - # Run workflow via registry (统一使用 run_workflow) - result_state = await run_workflow("kb_podcast", state) - - # Extract results - audio_path = "" - script_path = "" - result_path = "" - - if isinstance(result_state, dict): - audio_path = result_state.get("audio_path", "") - result_path = result_state.get("result_path", "") - else: - audio_path = getattr(result_state, "audio_path", "") - result_path = getattr(result_state, "result_path", "") - - if result_path: - script_path = str(Path(result_path) / "script.txt") - - audio_error = "" - if not audio_path: - audio_error = "No audio path returned from workflow" - elif isinstance(audio_path, str) and audio_path.startswith("["): - audio_error = audio_path - else: - audio_file = Path(audio_path) - if not audio_file.is_absolute(): - audio_file = (get_project_root() / audio_file).resolve() - if not audio_file.exists(): - audio_error = f"Audio file not found: {audio_file}" - - if audio_error: - raise HTTPException(status_code=500, detail=audio_error) - - audio_url = _to_outputs_url(audio_path) if audio_path else "" - script_url = _to_outputs_url(script_path) if script_path else "" - result_url = _to_outputs_url(result_path) if result_path else "" - - return { - "success": True, - "result_path": result_url, - "audio_path": audio_url, - "script_path": script_url, - "output_file_id": f"kb_podcast_{int(time.time())}" - } - - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/generate-mindmap") -async def generate_mindmap_from_kb( - file_paths: List[str] = Body(..., embed=True), - user_id: Optional[str] = Body(None, embed=True), - email: Optional[str] = Body(None, embed=True), - notebook_id: Optional[str] = Body(None, embed=True), - api_url: str = Body(..., embed=True), - api_key: str = Body(..., embed=True), - model: str = Body("gpt-4o", embed=True), - mindmap_style: str = Body("default", embed=True), - max_depth: int = Body(3, embed=True), - language: str = Body("zh", embed=True), - user: AuthUser = Depends(get_current_user), -): - """ - Generate mindmap from knowledge base files - """ - try: - api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") - email, user_id = _resolve_kb_identity(user) - # Normalize file paths - local_file_paths = [] - - for f in file_paths: - local_path = _resolve_user_owned_output_path(f, user) - if not local_path.exists(): - raise HTTPException(status_code=404, detail=f"File not found: {f}") - local_file_paths.append(str(local_path)) - - if not local_file_paths: - raise HTTPException(status_code=400, detail="No valid files provided") - - # Prepare request - mindmap_req = KBMindMapRequest( - files=local_file_paths, - chat_api_url=api_url, - api_key=api_key, - model=model, - mindmap_style=mindmap_style, - max_depth=max_depth, - language=language - ) - mindmap_req.email = email - - if notebook_id: - nb_output_dir = _generated_dir(email, notebook_id, "mindmap", user_id) - state = KBMindMapState(request=mindmap_req, result_path=str(nb_output_dir)) - else: - state = KBMindMapState(request=mindmap_req) - - # Run workflow via registry (统一使用 run_workflow) - result_state = await run_workflow("kb_mindmap", state) - - # Extract results - mermaid_code = "" - result_path = "" - - if isinstance(result_state, dict): - mermaid_code = result_state.get("mermaid_code", "") - result_path = result_state.get("result_path", "") - else: - mermaid_code = getattr(result_state, "mermaid_code", "") - result_path = getattr(result_state, "result_path", "") - - mindmap_path = "" - if result_path: - mmd_path = Path(result_path) / "mindmap.mmd" - if (not mmd_path.exists()) and mermaid_code: - try: - mmd_path.write_text(mermaid_code, encoding="utf-8") - except Exception: - pass - if mmd_path.exists(): - mindmap_path = _to_outputs_url(str(mmd_path)) - - return { - "success": True, - "result_path": _to_outputs_url(result_path) if result_path else "", - "mermaid_code": mermaid_code, - "mindmap_path": mindmap_path, - "output_file_id": f"kb_mindmap_{int(time.time())}" - } - - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/deep-research", response_model=DeepResearchResponse) -async def deep_research_from_kb( - req: DeepResearchRequest, - user: AuthUser = Depends(get_current_user), -): - """ - Deep research workflow入口(router -> service -> wa -> wf) - """ - if req.mode == "web" and not (req.search_api_key or (is_free_billing_mode() and settings.DEFAULT_SEARCH_API_KEY)): - raise HTTPException(status_code=400, detail="Search API key required") - if req.mode == "web" and req.search_provider == "google_cse" and not (req.google_cse_id or (is_free_billing_mode() and settings.DEFAULT_GOOGLE_CSE_ID)): - raise HTTPException(status_code=400, detail="google_cse_id required") - if not req.topic and not req.file_paths: - raise HTTPException(status_code=400, detail="Topic or files required") - req.email = _canonical_user_email(user) - req.user_id = _canonical_user_id(user) - if req.file_paths: - req.file_paths = [str(_resolve_user_owned_output_path(path, user)) for path in req.file_paths] - service = _get_deepresearch_service() - return await service.run(req) - - -@router.post("/generate-report", response_model=KBReportResponse) -async def generate_report_from_kb( - req: KBReportRequest, - user: AuthUser = Depends(get_current_user), -): - """ - Generate a report with insights/analysis from KB documents (workflow). - """ - if not req.file_paths: - raise HTTPException(status_code=400, detail="No valid files provided") - req.email = _canonical_user_email(user) - req.user_id = _canonical_user_id(user) - req.file_paths = [str(_resolve_user_owned_output_path(path, user)) for path in req.file_paths] - service = _get_report_service() - return await service.run(req) - - -@router.post("/save-mindmap") -async def save_mindmap_to_file( - file_url: str = Body(..., embed=True), - content: str = Body(..., embed=True), - user: AuthUser = Depends(get_current_user), -): - """ - Save edited Mermaid mindmap code back to the output file. - """ - try: - if not file_url: - raise HTTPException(status_code=400, detail="File URL is required") - - local_path = _resolve_user_owned_output_path(file_url, user) - - if local_path.suffix.lower() not in {".mmd", ".mermaid", ".md"}: - raise HTTPException(status_code=400, detail="Invalid mindmap file type") - - local_path.parent.mkdir(parents=True, exist_ok=True) - local_path.write_text(content or "", encoding="utf-8") - - return { - "success": True, - "mindmap_path": _to_outputs_url(str(local_path)) - } - except HTTPException: - raise +# +# Knowledge-base workflow endpoints were moved to: +# fastapi_app/routers/kb_workflows.py +# This file now focuses on notebook / file-management / chat responsibilities. except Exception as e: import traceback traceback.print_exc() diff --git a/fastapi_app/routers/kb_workflows.py b/fastapi_app/routers/kb_workflows.py new file mode 100644 index 00000000..af80ad68 --- /dev/null +++ b/fastapi_app/routers/kb_workflows.py @@ -0,0 +1,512 @@ +from __future__ import annotations + +import time +from pathlib import Path +from typing import Any, Dict, List, Optional + +from fastapi import APIRouter, Body, Depends, HTTPException + +from dataflow_agent.state import ( + IntelligentQARequest, + IntelligentQAState, + KBMindMapRequest, + KBMindMapState, + KBPodcastRequest, + KBPodcastState, +) +from dataflow_agent.utils import get_project_root +from dataflow_agent.workflow import run_workflow +from fastapi_app.config import settings +from fastapi_app.dependencies import AuthUser, get_current_user +from fastapi_app.schemas import ( + DeepResearchRequest, + DeepResearchResponse, + KBReportRequest, + KBReportResponse, + Paper2PPTRequest, +) +from fastapi_app.services.managed_api_service import ( + is_free_billing_mode, + resolve_llm_credentials, + resolve_model_name, +) +from fastapi_app.utils import _to_outputs_url +from fastapi_app.routers.kb import ( + IMAGE_EXTENSIONS, + _canonical_user_email, + _canonical_user_id, + _convert_to_pdf, + _generated_dir, + _get_deepresearch_service, + _get_report_service, + _merge_pdfs, + _resolve_kb_identity, + _resolve_user_owned_output_path, + _vector_store_dir, +) + + +router = APIRouter(prefix="/kb", tags=["Knowledge Base Workflows"]) + + +@router.post("/generate-ppt") +async def generate_ppt_from_kb( + file_path: Optional[str] = Body(None, embed=True), + file_paths: Optional[List[str]] = Body(None, embed=True), + image_paths: Optional[List[str]] = Body(None, embed=True), + image_items: Optional[List[Dict[str, Any]]] = Body(None, embed=True), + query: Optional[str] = Body("", embed=True), + need_embedding: bool = Body(False, embed=True), + search_top_k: int = Body(8, embed=True), + user_id: Optional[str] = Body(None, embed=True), + email: Optional[str] = Body(None, embed=True), + notebook_id: Optional[str] = Body(None, embed=True), + api_url: Optional[str] = Body(None, embed=True), + api_key: Optional[str] = Body(None, embed=True), + style: str = Body("modern", embed=True), + language: str = Body("zh", embed=True), + page_count: int = Body(10, embed=True), + model: Optional[str] = Body(None, embed=True), + gen_fig_model: Optional[str] = Body(None, embed=True), + user: AuthUser = Depends(get_current_user), +): + """ + Generate PPT from KB documents. + """ + try: + api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") + resolved_model = resolve_model_name( + model, + managed_default=settings.KB_CHAT_MODEL, + fallback_default=settings.KB_CHAT_MODEL, + ) + resolved_image_model = resolve_model_name( + gen_fig_model, + managed_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ) + email, user_id = _resolve_kb_identity(user) + input_paths = file_paths or ([file_path] if file_path else []) + if not input_paths: + raise HTTPException(status_code=400, detail="No input files provided") + + project_root = get_project_root() + ts = int(time.time()) + if notebook_id: + output_dir = _generated_dir(email, notebook_id, "ppt", user_id) + else: + output_dir = project_root / "outputs" / "kb_outputs" / email / f"{ts}_ppt" + output_dir.mkdir(parents=True, exist_ok=True) + + doc_paths: List[Path] = [] + user_image_items: List[Dict[str, Any]] = [] + for p in input_paths: + local_path = _resolve_user_owned_output_path(p, user) + if not local_path.exists(): + raise HTTPException(status_code=404, detail=f"File not found: {p}") + ext = local_path.suffix.lower() + if ext in IMAGE_EXTENSIONS: + user_image_items.append({"path": str(local_path), "description": ""}) + elif ext in {".pdf", ".pptx", ".ppt", ".docx", ".doc"}: + doc_paths.append(local_path) + else: + raise HTTPException(status_code=400, detail=f"Unsupported file type for PPT: {local_path.name}") + + if not doc_paths: + raise HTTPException(status_code=400, detail="At least one document file is required for PPT generation") + + local_pdf_paths: List[Path] = [] + convert_dir = output_dir / "input" + convert_dir.mkdir(parents=True, exist_ok=True) + for p in doc_paths: + ext = p.suffix.lower() + if ext == ".pdf": + local_pdf_paths.append(p) + elif ext in {".pptx", ".ppt", ".docx", ".doc"}: + local_pdf_paths.append(_convert_to_pdf(p, convert_dir)) + else: + raise HTTPException(status_code=400, detail=f"Unsupported file type for PPT: {p.name}") + + if len(local_pdf_paths) > 1: + merged_pdf = convert_dir / "merged.pdf" + local_file_path = _merge_pdfs(local_pdf_paths, merged_pdf) + else: + local_file_path = local_pdf_paths[0] + + resolved_image_items: List[Dict[str, Any]] = [] + for item in image_items or []: + raw_path = item.get("path") or item.get("url") or "" + if not raw_path: + continue + img_path = _resolve_user_owned_output_path(str(raw_path), user) + if img_path.exists() and img_path.suffix.lower() in IMAGE_EXTENSIONS: + resolved_image_items.append( + { + "path": str(img_path), + "description": item.get("description") or item.get("desc") or "", + } + ) + + for img in image_paths or []: + img_path = _resolve_user_owned_output_path(img, user) + if img_path.exists() and img_path.suffix.lower() in IMAGE_EXTENSIONS: + resolved_image_items.append({"path": str(img_path), "description": ""}) + + resolved_image_items.extend(user_image_items) + + retrieval_text = "" + if need_embedding: + if notebook_id: + base_dir = _vector_store_dir(email, notebook_id, user_id) + else: + base_dir = project_root / "outputs" / "kb_data" / email / "vector_store" + embed_api_url = api_url + if "/embeddings" not in embed_api_url: + embed_api_url = embed_api_url.rstrip("/") + "/embeddings" + + files_for_embed = [{"path": str(p), "description": ""} for p in doc_paths] + from dataflow_agent.toolkits.ragtool.vector_store_tool import ( + VectorStoreManager, + process_knowledge_base_files, + ) + + manifest = await process_knowledge_base_files( + files_for_embed, + base_dir=str(base_dir), + api_url=embed_api_url, + api_key=api_key, + model_name=None, + multimodal_model=None, + ) + + manager = VectorStoreManager( + base_dir=str(base_dir), + embedding_api_url=embed_api_url, + api_key=api_key, + ) + + def _match_file_ids(m: Dict[str, Any], paths: List[Path]) -> List[str]: + ids: List[str] = [] + target = {str(p.resolve()) for p in paths} + for f in m.get("files", []): + try: + if str(Path(f.get("original_path", "")).resolve()) in target and f.get("id"): + ids.append(f["id"]) + except Exception: + continue + return ids + + file_ids = _match_file_ids(manifest or manager.manifest or {}, doc_paths) + if query and file_ids: + results = manager.search(query=query, top_k=search_top_k, file_ids=file_ids) + retrieval_text = "\n\n".join([r.get("content", "") for r in results if r.get("content")]) + + ppt_req = Paper2PPTRequest( + input_type="PDF", + input_content=str(local_file_path), + email=email, + chat_api_url=api_url, + chat_api_key=api_key, + api_key=api_key, + style=style, + language=language, + page_count=page_count, + model=resolved_model, + gen_fig_model=resolved_image_model, + aspect_ratio="16:9", + use_long_paper=False, + ) + + from fastapi_app.workflow_adapters.wa_paper2ppt import _init_state_from_request + + state_pc = _init_state_from_request(ppt_req, result_path=output_dir) + state_pc.kb_query = query or "" + state_pc.kb_retrieval_text = retrieval_text + state_pc.kb_user_images = resolved_image_items + state_pc = await run_workflow("kb_page_content", state_pc) + pagecontent = getattr(state_pc, "pagecontent", []) or [] + + state_pc.pagecontent = pagecontent + state_pp = await run_workflow("paper2ppt_parallel_consistent_style", state_pc) + + pdf_path = getattr(state_pp, "ppt_pdf_path", "") + pptx_path = getattr(state_pp, "ppt_pptx_path", "") + + return { + "success": True, + "result_path": str(output_dir), + "pdf_path": _to_outputs_url(pdf_path) if pdf_path else "", + "pptx_path": _to_outputs_url(pptx_path) if pptx_path else "", + "output_file_id": f"kb_ppt_{ts}", + } + except Exception as e: + import traceback + + traceback.print_exc() + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/generate-podcast") +async def generate_podcast_from_kb( + file_paths: List[str] = Body(..., embed=True), + user_id: Optional[str] = Body(None, embed=True), + email: Optional[str] = Body(None, embed=True), + notebook_id: Optional[str] = Body(None, embed=True), + api_url: Optional[str] = Body(None, embed=True), + api_key: Optional[str] = Body(None, embed=True), + model: Optional[str] = Body(None, embed=True), + tts_model: Optional[str] = Body(None, embed=True), + voice_name: str = Body("", embed=True), + voice_name_b: str = Body("Puck", embed=True), + podcast_mode: str = Body("monologue", embed=True), + podcast_length: str = Body("standard", embed=True), + language: str = Body("zh", embed=True), + user: AuthUser = Depends(get_current_user), +): + try: + api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") + resolved_model = resolve_model_name( + model, + managed_default=settings.KB_CHAT_MODEL, + fallback_default=settings.KB_CHAT_MODEL, + ) + resolved_tts_model = resolve_model_name( + tts_model, + managed_default=settings.PAPER2VIDEO_TTS_MODEL, + fallback_default=settings.PAPER2VIDEO_TTS_MODEL, + ) + email, user_id = _resolve_kb_identity(user) + project_root = get_project_root() + if notebook_id: + output_dir = _generated_dir(email, notebook_id, "podcast", user_id) + else: + ts = int(time.time()) + output_dir = project_root / "outputs" / "kb_outputs" / email / f"{ts}_podcast" + output_dir.mkdir(parents=True, exist_ok=True) + + if not file_paths: + raise HTTPException(status_code=400, detail="No valid files provided") + + local_paths: List[Path] = [] + for f in file_paths: + local_path = _resolve_user_owned_output_path(f, user) + if not local_path.exists(): + raise HTTPException(status_code=404, detail=f"File not found: {f}") + local_paths.append(local_path) + + if len(local_paths) > 1: + merge_dir = output_dir / "input" + merge_dir.mkdir(parents=True, exist_ok=True) + + pdf_paths: List[Path] = [] + for p in local_paths: + ext = p.suffix.lower() + if ext == ".pdf": + pdf_paths.append(p) + elif ext in {".docx", ".doc", ".pptx", ".ppt"}: + pdf_paths.append(_convert_to_pdf(p, merge_dir)) + else: + raise HTTPException(status_code=400, detail=f"Unsupported file type for podcast: {p.name}") + + merged_pdf = merge_dir / "merged.pdf" + local_file_paths = [str(_merge_pdfs(pdf_paths, merged_pdf))] + else: + local_file_paths = [str(local_paths[0])] + + podcast_req = KBPodcastRequest( + files=local_file_paths, + chat_api_url=api_url, + api_key=api_key, + model=resolved_model, + tts_model=resolved_tts_model, + voice_name=voice_name, + voice_name_b=voice_name_b, + podcast_mode=podcast_mode, + podcast_length=podcast_length, + language=language, + ) + podcast_req.email = email + + state = KBPodcastState(request=podcast_req, result_path=str(output_dir)) + result_state = await run_workflow("kb_podcast", state) + + audio_path = "" + result_path = "" + if isinstance(result_state, dict): + audio_path = result_state.get("audio_path", "") + result_path = result_state.get("result_path", "") + else: + audio_path = getattr(result_state, "audio_path", "") + result_path = getattr(result_state, "result_path", "") + + script_path = str(Path(result_path) / "script.txt") if result_path else "" + audio_error = "" + if not audio_path: + audio_error = "No audio path returned from workflow" + elif isinstance(audio_path, str) and audio_path.startswith("["): + audio_error = audio_path + else: + audio_file = Path(audio_path) + if not audio_file.is_absolute(): + audio_file = (get_project_root() / audio_file).resolve() + if not audio_file.exists(): + audio_error = f"Audio file not found: {audio_file}" + if audio_error: + raise HTTPException(status_code=500, detail=audio_error) + + return { + "success": True, + "result_path": _to_outputs_url(result_path) if result_path else "", + "audio_path": _to_outputs_url(audio_path) if audio_path else "", + "script_path": _to_outputs_url(script_path) if script_path else "", + "output_file_id": f"kb_podcast_{int(time.time())}", + } + except Exception as e: + import traceback + + traceback.print_exc() + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/generate-mindmap") +async def generate_mindmap_from_kb( + file_paths: List[str] = Body(..., embed=True), + user_id: Optional[str] = Body(None, embed=True), + email: Optional[str] = Body(None, embed=True), + notebook_id: Optional[str] = Body(None, embed=True), + api_url: Optional[str] = Body(None, embed=True), + api_key: Optional[str] = Body(None, embed=True), + model: Optional[str] = Body(None, embed=True), + mindmap_style: str = Body("default", embed=True), + max_depth: int = Body(3, embed=True), + language: str = Body("zh", embed=True), + user: AuthUser = Depends(get_current_user), +): + try: + api_url, api_key = resolve_llm_credentials(api_url, api_key, scope="kb") + resolved_model = resolve_model_name( + model, + managed_default=settings.MINDMAP_DEFAULT_MODEL, + fallback_default=settings.MINDMAP_DEFAULT_MODEL, + ) + email, user_id = _resolve_kb_identity(user) + + local_file_paths = [] + for f in file_paths: + local_path = _resolve_user_owned_output_path(f, user) + if not local_path.exists(): + raise HTTPException(status_code=404, detail=f"File not found: {f}") + local_file_paths.append(str(local_path)) + if not local_file_paths: + raise HTTPException(status_code=400, detail="No valid files provided") + + mindmap_req = KBMindMapRequest( + files=local_file_paths, + chat_api_url=api_url, + api_key=api_key, + model=resolved_model, + mindmap_style=mindmap_style, + max_depth=max_depth, + language=language, + ) + mindmap_req.email = email + + if notebook_id: + nb_output_dir = _generated_dir(email, notebook_id, "mindmap", user_id) + state = KBMindMapState(request=mindmap_req, result_path=str(nb_output_dir)) + else: + state = KBMindMapState(request=mindmap_req) + + result_state = await run_workflow("kb_mindmap", state) + mermaid_code = "" + result_path = "" + if isinstance(result_state, dict): + mermaid_code = result_state.get("mermaid_code", "") + result_path = result_state.get("result_path", "") + else: + mermaid_code = getattr(result_state, "mermaid_code", "") + result_path = getattr(result_state, "result_path", "") + + mindmap_path = "" + if result_path: + mmd_path = Path(result_path) / "mindmap.mmd" + if (not mmd_path.exists()) and mermaid_code: + try: + mmd_path.write_text(mermaid_code, encoding="utf-8") + except Exception: + pass + if mmd_path.exists(): + mindmap_path = _to_outputs_url(str(mmd_path)) + + return { + "success": True, + "result_path": _to_outputs_url(result_path) if result_path else "", + "mermaid_code": mermaid_code, + "mindmap_path": mindmap_path, + "output_file_id": f"kb_mindmap_{int(time.time())}", + } + except Exception as e: + import traceback + + traceback.print_exc() + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/deep-research", response_model=DeepResearchResponse) +async def deep_research_from_kb( + req: DeepResearchRequest, + user: AuthUser = Depends(get_current_user), +): + if req.mode == "web" and not (req.search_api_key or (is_free_billing_mode() and settings.DEFAULT_SEARCH_API_KEY)): + raise HTTPException(status_code=400, detail="Search API key required") + if req.mode == "web" and req.search_provider == "google_cse" and not ( + req.google_cse_id or (is_free_billing_mode() and settings.DEFAULT_GOOGLE_CSE_ID) + ): + raise HTTPException(status_code=400, detail="google_cse_id required") + if not req.topic and not req.file_paths: + raise HTTPException(status_code=400, detail="Topic or files required") + req.email = _canonical_user_email(user) + req.user_id = _canonical_user_id(user) + if req.file_paths: + req.file_paths = [str(_resolve_user_owned_output_path(path, user)) for path in req.file_paths] + service = _get_deepresearch_service() + return await service.run(req) + + +@router.post("/generate-report", response_model=KBReportResponse) +async def generate_report_from_kb( + req: KBReportRequest, + user: AuthUser = Depends(get_current_user), +): + if not req.file_paths: + raise HTTPException(status_code=400, detail="No valid files provided") + req.email = _canonical_user_email(user) + req.user_id = _canonical_user_id(user) + req.file_paths = [str(_resolve_user_owned_output_path(path, user)) for path in req.file_paths] + service = _get_report_service() + return await service.run(req) + + +@router.post("/save-mindmap") +async def save_mindmap_to_file( + file_url: str = Body(..., embed=True), + content: str = Body(..., embed=True), + user: AuthUser = Depends(get_current_user), +): + try: + if not file_url: + raise HTTPException(status_code=400, detail="File URL is required") + + local_path = _resolve_user_owned_output_path(file_url, user) + if local_path.suffix.lower() not in {".mmd", ".mermaid", ".md"}: + raise HTTPException(status_code=400, detail="Invalid mindmap file type") + + local_path.parent.mkdir(parents=True, exist_ok=True) + local_path.write_text(content or "", encoding="utf-8") + + return { + "success": True, + "mindmap_path": _to_outputs_url(str(local_path)), + } + except HTTPException: + raise diff --git a/fastapi_app/routers/mindmap.py b/fastapi_app/routers/mindmap.py index 45c20a9b..bf2547d7 100644 --- a/fastapi_app/routers/mindmap.py +++ b/fastapi_app/routers/mindmap.py @@ -15,10 +15,11 @@ from dataflow_agent.agentroles import create_agent from dataflow_agent.logger import get_logger from dataflow_agent.state import MainRequest, MainState +from fastapi_app.config import settings from fastapi_app.dependencies import AuthUser, get_optional_user, is_auth_configured from fastapi_app.config.pricing import estimate_mindmap_points from fastapi_app.services.billing_service import BillingService -from fastapi_app.services.managed_api_service import resolve_llm_credentials +from fastapi_app.services.managed_api_service import resolve_llm_credentials, resolve_model_name from fastapi_app.utils import _to_outputs_url, get_outputs_root, resolve_outputs_path router = APIRouter(prefix="/mindmap", tags=["mindmap"]) @@ -326,6 +327,11 @@ async def generate_mindmap( raise HTTPException(status_code=400, detail="Please provide at least one file or some text content") resolved_api_url, resolved_api_key = resolve_llm_credentials(chat_api_url, api_key, scope="kb") + resolved_model = resolve_model_name( + model, + managed_default=settings.MINDMAP_DEFAULT_MODEL, + fallback_default="gpt-4o", + ) owner = _owner_slug(user) run_id = f"{int(time.time())}_{uuid.uuid4().hex[:8]}" run_dir = (_mindmap_root_for_user(user) / run_id).resolve() @@ -353,7 +359,7 @@ async def generate_mindmap( text_blocks=parsed_files, chat_api_url=resolved_api_url, api_key=resolved_api_key, - model=(model or "gpt-5.4").strip() or "gpt-5.4", + model=resolved_model, max_depth=max(2, min(int(max_depth or 3), 6)), language=(language or "zh").strip() or "zh", style=(mindmap_style or "default").strip() or "default", @@ -371,7 +377,7 @@ async def generate_mindmap( "owner": owner, "style": (mindmap_style or "default").strip() or "default", "language": (language or "zh").strip() or "zh", - "model": (model or "gpt-5.4").strip() or "gpt-5.4", + "model": resolved_model, "source_count": len(local_paths), "estimated_points": charge_info["points"], "billing": charge_info, diff --git a/fastapi_app/routers/paper2any.py b/fastapi_app/routers/paper2any.py index b12b9b4a..c70d4b6a 100644 --- a/fastapi_app/routers/paper2any.py +++ b/fastapi_app/routers/paper2any.py @@ -1,9 +1,7 @@ from __future__ import annotations -from typing import Optional -from fastapi import APIRouter, Depends, File, Form, UploadFile, Request, Body -from fastapi.responses import FileResponse -from fastapi_app.schemas import Paper2FigureResponse, VerifyLlmRequest, VerifyLlmResponse +from fastapi import APIRouter, Body, Depends +from fastapi_app.schemas import VerifyLlmRequest, VerifyLlmResponse from dataflow_agent.logger import get_logger log = get_logger(__name__) @@ -26,109 +24,3 @@ async def verify_llm_connection( Verify LLM connection by sending a simple 'Hi' message from the backend. """ return await service.verify_llm_connection(req) - - -@router.get("/paper2figure/history") -async def list_paper2figure_history_files( - request: Request, - email: str, - service: Paper2AnyService = Depends(get_service), -): - """ - 根据邮箱,列出该用户目录中的所有历史输出文件(pptx/png/svg) - """ - return await service.list_history_files(email, request) - - -@router.post("/paper2figure/generate") -async def generate_paper2figure( - img_gen_model_name: str = Form(...), - chat_api_url: Optional[str] = Form(None), - api_key: Optional[str] = Form(None), - input_type: str = Form(...), - email: Optional[str] = Form(None), - file: Optional[UploadFile] = File(None), - file_kind: Optional[str] = Form(None), - text: Optional[str] = Form(None), - graph_type: str = Form("model_arch"), # 'model_arch' | 'tech_route' | 'exp_data' - language: str = Form("zh"), - figure_complex: str = Form("easy"), - style: str = Form("cartoon"), - service: Paper2AnyService = Depends(get_service), -): - """ - Paper2Graph 接口(带邀请码校验 + workflow 调用) - """ - ppt_path = await service.generate_paper2figure( - img_gen_model_name=img_gen_model_name, - chat_api_url=chat_api_url, - api_key=api_key, - input_type=input_type, - email=email, - file=file, - file_kind=file_kind, - text=text, - graph_type=graph_type, - language=language, - figure_complex=figure_complex, - style=style, - ) - - return FileResponse( - path=str(ppt_path), - media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation", - filename=ppt_path.name, - ) - - -@router.post("/paper2figure/generate-json", response_model=Paper2FigureResponse) -async def generate_paper2figure_json( - request: Request, - img_gen_model_name: str = Form(...), - chat_api_url: Optional[str] = Form(None), - api_key: Optional[str] = Form(None), - input_type: str = Form(...), # 'file' | 'text' | 'image' - email: Optional[str] = Form(None), - file: Optional[UploadFile] = File(None), - file_kind: Optional[str] = Form(None), # 'pdf' | 'image' - text: Optional[str] = Form(None), - graph_type: str = Form("model_arch"), # 'model_arch' | 'tech_route' | 'exp_data' - language: str = Form("zh"), - style: str = Form("cartoon"), - figure_complex: str = Form("easy"), - resolution: str = Form("2K"), - edit_prompt: Optional[str] = Form(None), - tech_route_palette: str = Form(""), - tech_route_template: str = Form(""), - reference_image: Optional[UploadFile] = File(None), - tech_route_edit_prompt: Optional[str] = Form(None), - output_format: Optional[str] = Form(None), - service: Paper2AnyService = Depends(get_service), -): - """ - Paper2Graph JSON 接口 - """ - resp_data = await service.generate_paper2figure_json( - request=request, - img_gen_model_name=img_gen_model_name, - chat_api_url=chat_api_url, - api_key=api_key, - input_type=input_type, - email=email, - file=file, - file_kind=file_kind, - text=text, - graph_type=graph_type, - language=language, - style=style, - figure_complex=figure_complex, - resolution=resolution, - edit_prompt=edit_prompt, - tech_route_palette=tech_route_palette, - tech_route_template=tech_route_template, - reference_image=reference_image, - tech_route_edit_prompt=tech_route_edit_prompt, - output_format=output_format, - ) - - return Paper2FigureResponse(**resp_data) diff --git a/fastapi_app/routers/paper2drawio.py b/fastapi_app/routers/paper2drawio.py index 9aae103c..a54e2790 100644 --- a/fastapi_app/routers/paper2drawio.py +++ b/fastapi_app/routers/paper2drawio.py @@ -10,6 +10,7 @@ from dataflow_agent.logger import get_logger from fastapi_app.config.settings import settings +from fastapi_app.services.managed_api_service import resolve_model_name log = get_logger(__name__) router = APIRouter(prefix="/paper2drawio", tags=["paper2drawio"]) @@ -24,7 +25,7 @@ class ChatRequest(BaseModel): chat_history: List[Dict[str, str]] = [] chat_api_url: str = "" api_key: str = "" - model: str = "gpt-4o" + model: str = settings.PAPER2DRAWIO_DEFAULT_MODEL class ExportRequest(BaseModel): @@ -80,13 +81,19 @@ async def generate_diagram( request=request, chat_api_url=chat_api_url, api_key=api_key, - model=model or settings.PAPER2DRAWIO_DEFAULT_MODEL, + model=resolve_model_name( + model, + managed_default=settings.PAPER2DRAWIO_DEFAULT_MODEL, + ), enable_vlm_validation=( enable_vlm_validation if enable_vlm_validation is not None else settings.PAPER2DRAWIO_ENABLE_VLM_VALIDATION ), - vlm_model=vlm_model or settings.PAPER2DRAWIO_VLM_MODEL, + vlm_model=resolve_model_name( + vlm_model, + managed_default=settings.PAPER2DRAWIO_VLM_MODEL, + ), vlm_validation_max_retries=vlm_validation_max_retries, input_type=input_type, diagram_type=diagram_type, @@ -116,7 +123,10 @@ async def chat_edit_diagram( chat_history=body.chat_history, chat_api_url=body.chat_api_url, api_key=body.api_key, - model=body.model, + model=resolve_model_name( + body.model, + managed_default=settings.PAPER2DRAWIO_DEFAULT_MODEL, + ), ) diff --git a/fastapi_app/routers/paper2figure.py b/fastapi_app/routers/paper2figure.py new file mode 100644 index 00000000..5fc475f3 --- /dev/null +++ b/fastapi_app/routers/paper2figure.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from typing import Optional + +from fastapi import APIRouter, Body, Depends, File, Form, Request, UploadFile +from fastapi.responses import FileResponse + +from fastapi_app.config import settings +from fastapi_app.schemas import Paper2FigureResponse +from fastapi_app.services.managed_api_service import resolve_model_name + + +router = APIRouter(prefix="/paper2figure", tags=["paper2figure"]) + + +def get_service() -> "Paper2AnyService": + from fastapi_app.services.paper2any_service import Paper2AnyService + + return Paper2AnyService() + + +@router.get("/history") +async def list_paper2figure_history_files( + request: Request, + email: str, + service: "Paper2AnyService" = Depends(get_service), +): + """ + 根据邮箱,列出该用户目录中的所有历史输出文件(pptx/png/svg)。 + """ + return await service.list_history_files(email, request) + + +@router.post("/generate") +async def generate_paper2figure( + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + input_type: str = Form(...), + email: Optional[str] = Form(None), + file: Optional[UploadFile] = File(None), + file_kind: Optional[str] = Form(None), + text: Optional[str] = Form(None), + graph_type: str = Form("model_arch"), + language: str = Form("zh"), + figure_complex: str = Form("easy"), + style: str = Form("cartoon"), + service: "Paper2AnyService" = Depends(get_service), +): + """ + Paper2Figure 文件下载接口。 + """ + ppt_path = await service.generate_paper2figure( + img_gen_model_name=resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + fallback_default=settings.PAPER2FIGURE_DEFAULT_IMAGE_MODEL, + ), + chat_api_url=chat_api_url, + api_key=api_key, + input_type=input_type, + email=email, + file=file, + file_kind=file_kind, + text=text, + graph_type=graph_type, + language=language, + figure_complex=figure_complex, + style=style, + ) + + return FileResponse( + path=str(ppt_path), + media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation", + filename=ppt_path.name, + ) + + +@router.post("/generate-json", response_model=Paper2FigureResponse) +async def generate_paper2figure_json( + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + input_type: str = Form(...), + email: Optional[str] = Form(None), + file: Optional[UploadFile] = File(None), + file_kind: Optional[str] = Form(None), + text: Optional[str] = Form(None), + graph_type: str = Form("model_arch"), + language: str = Form("zh"), + style: str = Form("cartoon"), + figure_complex: str = Form("easy"), + resolution: str = Form("2K"), + edit_prompt: Optional[str] = Form(None), + tech_route_palette: str = Form(""), + tech_route_template: str = Form(""), + reference_image: Optional[UploadFile] = File(None), + tech_route_edit_prompt: Optional[str] = Form(None), + output_format: Optional[str] = Form(None), + service: "Paper2AnyService" = Depends(get_service), +): + """ + Paper2Figure JSON 接口。 + """ + resp_data = await service.generate_paper2figure_json( + request=request, + img_gen_model_name=resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + fallback_default=settings.PAPER2FIGURE_DEFAULT_IMAGE_MODEL, + ), + chat_api_url=chat_api_url, + api_key=api_key, + input_type=input_type, + email=email, + file=file, + file_kind=file_kind, + text=text, + graph_type=graph_type, + language=language, + style=style, + figure_complex=figure_complex, + resolution=resolution, + edit_prompt=edit_prompt, + tech_route_palette=tech_route_palette, + tech_route_template=tech_route_template, + reference_image=reference_image, + tech_route_edit_prompt=tech_route_edit_prompt, + output_format=output_format, + ) + return Paper2FigureResponse(**resp_data) diff --git a/fastapi_app/routers/paper2poster.py b/fastapi_app/routers/paper2poster.py index 0471e3d1..d241ee3b 100644 --- a/fastapi_app/routers/paper2poster.py +++ b/fastapi_app/routers/paper2poster.py @@ -4,6 +4,9 @@ from fastapi import APIRouter, Depends, File, Form, UploadFile +from fastapi_app.config import settings +from fastapi_app.services.managed_api_service import resolve_model_name + router = APIRouter() @@ -33,8 +36,16 @@ async def generate_paper2poster( paper_file=paper_file, chat_api_url=chat_api_url, api_key=api_key, - model=model, - vision_model=vision_model, + model=resolve_model_name( + model, + managed_default=settings.PAPER2POSTER_DEFAULT_MODEL, + fallback_default="gpt-4o", + ), + vision_model=resolve_model_name( + vision_model, + managed_default=settings.PAPER2POSTER_VISION_MODEL, + fallback_default="gpt-4o", + ), poster_width=poster_width, poster_height=poster_height, logo_file=logo_file, diff --git a/fastapi_app/routers/paper2ppt.py b/fastapi_app/routers/paper2ppt.py index 3df6df59..ac169806 100644 --- a/fastapi_app/routers/paper2ppt.py +++ b/fastapi_app/routers/paper2ppt.py @@ -9,6 +9,7 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, Request, UploadFile +from fastapi_app.config import settings from fastapi_app.schemas import ( ErrorResponse, FrontendPPTExportRequest, @@ -19,6 +20,7 @@ PageContentRequest, PPTGenerationRequest, ) +from fastapi_app.services.managed_api_service import resolve_model_name from dataflow_agent.utils.version_manager import ImageVersionManager from fastapi_app.services.billing_service import BillingService from fastapi_app.utils import _to_outputs_url, resolve_outputs_path @@ -207,6 +209,72 @@ def _consume_paper2ppt_frontend_charge(request: Request, req: FrontendPPTGenerat ) +def _build_ppt_generation_request( + *, + img_gen_model_name: str, + chat_api_url: Optional[str], + api_key: Optional[str], + credential_scope: Optional[str], + email: Optional[str], + style: str, + aspect_ratio: str, + language: str, + model: str, + get_down: str, + all_edited_down: str, + result_path: str, + pagecontent: Optional[str], + page_id: Optional[int], + edit_prompt: Optional[str], + regenerate_from_outline: str, + image_resolution: Optional[str], + skip_pages: Optional[str], +) -> PPTGenerationRequest: + return PPTGenerationRequest( + img_gen_model_name=resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=resolve_model_name( + model, + managed_default=settings.PAPER2PPT_CONTENT_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), + get_down=get_down, + all_edited_down=all_edited_down, + result_path=result_path, + pagecontent=pagecontent, + page_id=page_id, + edit_prompt=edit_prompt, + regenerate_from_outline=regenerate_from_outline, + image_resolution=image_resolution, + skip_pages=skip_pages, + ) + + +async def _execute_paper2ppt_generate( + *, + request: Request, + req: PPTGenerationRequest, + reference_img: Optional[UploadFile], + service: "Paper2PPTService", +) -> Dict[str, Any]: + _consume_paper2ppt_generate_charge(request, req) + return await service.generate_ppt( + req=req, + reference_img=reference_img, + request=request, + ) + + @router.post( "/paper2ppt/page-content", response_model=Dict[str, Any], @@ -247,10 +315,18 @@ async def paper2ppt_pagecontent_json( email=email, input_type=input_type, text=text, - model=model, + model=resolve_model_name( + model, + managed_default=settings.PAPER2PPT_OUTLINE_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), language=language, style=style, - gen_fig_model=gen_fig_model, + gen_fig_model=resolve_model_name( + gen_fig_model, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), page_count=page_count, use_long_paper=use_long_paper, pdf_as_slides=pdf_as_slides, @@ -266,6 +342,176 @@ async def paper2ppt_pagecontent_json( return data +@router.post( + "/paper2ppt/slides/generate", + response_model=Dict[str, Any], + responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, +) +async def paper2ppt_generate_slides( + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + credential_scope: Optional[str] = Form(None), + email: Optional[str] = Form(None), + style: str = Form(""), + reference_img: Optional[UploadFile] = File(None), + aspect_ratio: str = Form("16:9"), + language: str = Form("en"), + model: str = Form("gpt-5.1"), + image_resolution: Optional[str] = Form(None), + result_path: str = Form(...), + pagecontent: str = Form(...), + regenerate_from_outline: str = Form("false"), + skip_pages: Optional[str] = Form(None), + service: Paper2PPTService = Depends(get_service), +): + """ + 显式的批量页图生成接口: + - 根据 pagecontent 批量生成 / 增量生成页面 + - 不承担单页编辑和最终导出语义 + """ + req = _build_ppt_generation_request( + img_gen_model_name=img_gen_model_name, + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=model, + get_down="false", + all_edited_down="false", + result_path=result_path, + pagecontent=pagecontent, + page_id=None, + edit_prompt=None, + regenerate_from_outline=regenerate_from_outline, + image_resolution=image_resolution, + skip_pages=skip_pages, + ) + return await _execute_paper2ppt_generate( + request=request, + req=req, + reference_img=reference_img, + service=service, + ) + + +@router.post( + "/paper2ppt/slides/{page_id}/edit", + response_model=Dict[str, Any], + responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, +) +async def paper2ppt_edit_slide( + page_id: int, + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + credential_scope: Optional[str] = Form(None), + email: Optional[str] = Form(None), + style: str = Form(""), + reference_img: Optional[UploadFile] = File(None), + aspect_ratio: str = Form("16:9"), + language: str = Form("en"), + model: str = Form("gpt-5.1"), + image_resolution: Optional[str] = Form(None), + result_path: str = Form(...), + pagecontent: Optional[str] = Form(None), + edit_prompt: Optional[str] = Form(None), + regenerate_from_outline: str = Form("false"), + service: Paper2PPTService = Depends(get_service), +): + """ + 显式的单页编辑接口: + - edit_prompt: 文字编辑 / 局部改图 + - regenerate_from_outline=true: 按当前 outline 内容重生该页 + """ + req = _build_ppt_generation_request( + img_gen_model_name=img_gen_model_name, + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=model, + get_down="true", + all_edited_down="false", + result_path=result_path, + pagecontent=pagecontent, + page_id=page_id, + edit_prompt=edit_prompt, + regenerate_from_outline=regenerate_from_outline, + image_resolution=image_resolution, + skip_pages=None, + ) + return await _execute_paper2ppt_generate( + request=request, + req=req, + reference_img=reference_img, + service=service, + ) + + +@router.post( + "/paper2ppt/finalize", + response_model=Dict[str, Any], + responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, +) +async def paper2ppt_finalize( + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + credential_scope: Optional[str] = Form(None), + email: Optional[str] = Form(None), + style: str = Form(""), + reference_img: Optional[UploadFile] = File(None), + aspect_ratio: str = Form("16:9"), + language: str = Form("en"), + model: str = Form("gpt-5.1"), + image_resolution: Optional[str] = Form(None), + result_path: str = Form(...), + pagecontent: Optional[str] = Form(None), + service: Paper2PPTService = Depends(get_service), +): + """ + 显式的最终导出接口: + - 基于已有结果目录导出最终 PPTX / PDF + - 不承担批量生成和单页编辑语义 + """ + req = _build_ppt_generation_request( + img_gen_model_name=img_gen_model_name, + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=model, + get_down="false", + all_edited_down="true", + result_path=result_path, + pagecontent=pagecontent, + page_id=None, + edit_prompt=None, + regenerate_from_outline="false", + image_resolution=image_resolution, + skip_pages=None, + ) + return await _execute_paper2ppt_generate( + request=request, + req=req, + reference_img=reference_img, + service=service, + ) + + @router.post( "/paper2ppt/generate", response_model=Dict[str, Any], @@ -302,12 +548,16 @@ async def paper2ppt_ppt_json( service: Paper2PPTService = Depends(get_service), ): """ - 只跑 paper2ppt: + 兼容旧接口,内部仍然分派到: + - /paper2ppt/slides/generate + - /paper2ppt/slides/{page_id}/edit + - /paper2ppt/finalize + + 旧语义: - get_down=false:生成模式(需要 pagecontent) - get_down=true:编辑模式(需要 page_id(0-based) + edit_prompt,pagecontent 可选) """ - - req = PPTGenerationRequest( + req = _build_ppt_generation_request( img_gen_model_name=img_gen_model_name, chat_api_url=chat_api_url, api_key=api_key, @@ -327,15 +577,104 @@ async def paper2ppt_ppt_json( image_resolution=image_resolution, skip_pages=skip_pages, ) - - _consume_paper2ppt_generate_charge(request, req) - - data = await service.generate_ppt( + return await _execute_paper2ppt_generate( + request=request, req=req, reference_img=reference_img, - request=request, + service=service, ) - return data + + +@router.post( + "/paper2ppt/slides/generate-task", + response_model=Dict[str, Any], + responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, +) +async def paper2ppt_generate_slides_task( + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + credential_scope: Optional[str] = Form(None), + email: Optional[str] = Form(None), + style: str = Form(""), + reference_img: Optional[UploadFile] = File(None), + aspect_ratio: str = Form("16:9"), + language: str = Form("en"), + model: str = Form("gpt-5.1"), + image_resolution: Optional[str] = Form(None), + result_path: str = Form(...), + pagecontent: str = Form(...), + regenerate_from_outline: str = Form("false"), + skip_pages: Optional[str] = Form(None), + task_service: Paper2PPTTaskService = Depends(get_task_service), +): + req = _build_ppt_generation_request( + img_gen_model_name=img_gen_model_name, + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=model, + get_down="false", + all_edited_down="false", + result_path=result_path, + pagecontent=pagecontent, + page_id=None, + edit_prompt=None, + regenerate_from_outline=regenerate_from_outline, + image_resolution=image_resolution, + skip_pages=skip_pages, + ) + return await task_service.submit_generate_task(req=req, reference_img=reference_img, request=request) + + +@router.post( + "/paper2ppt/finalize-task", + response_model=Dict[str, Any], + responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, +) +async def paper2ppt_finalize_task( + request: Request, + img_gen_model_name: str = Form(...), + chat_api_url: Optional[str] = Form(None), + api_key: Optional[str] = Form(None), + credential_scope: Optional[str] = Form(None), + email: Optional[str] = Form(None), + style: str = Form(""), + reference_img: Optional[UploadFile] = File(None), + aspect_ratio: str = Form("16:9"), + language: str = Form("en"), + model: str = Form("gpt-5.1"), + image_resolution: Optional[str] = Form(None), + result_path: str = Form(...), + pagecontent: Optional[str] = Form(None), + task_service: Paper2PPTTaskService = Depends(get_task_service), +): + req = _build_ppt_generation_request( + img_gen_model_name=img_gen_model_name, + chat_api_url=chat_api_url, + api_key=api_key, + credential_scope=credential_scope, + email=email, + style=style, + aspect_ratio=aspect_ratio, + language=language, + model=model, + get_down="false", + all_edited_down="true", + result_path=result_path, + pagecontent=pagecontent, + page_id=None, + edit_prompt=None, + regenerate_from_outline="false", + image_resolution=image_resolution, + skip_pages=None, + ) + return await task_service.submit_generate_task(req=req, reference_img=reference_img, request=request) @router.post( @@ -367,7 +706,11 @@ async def paper2ppt_generate_task( task_service: Paper2PPTTaskService = Depends(get_task_service), ): req = PPTGenerationRequest( - img_gen_model_name=img_gen_model_name, + img_gen_model_name=resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), chat_api_url=chat_api_url, api_key=api_key, credential_scope=credential_scope, @@ -375,7 +718,11 @@ async def paper2ppt_generate_task( style=style, aspect_ratio=aspect_ratio, language=language, - model=model, + model=resolve_model_name( + model, + managed_default=settings.PAPER2PPT_CONTENT_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), get_down=get_down, all_edited_down=all_edited_down, result_path=result_path, @@ -426,7 +773,11 @@ async def paper2ppt_outline_refine( api_key=api_key, credential_scope=credential_scope, email=email, - model=model, + model=resolve_model_name( + model, + managed_default=settings.PAPER2PPT_OUTLINE_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), language=language, result_path=result_path, outline_feedback=outline_feedback, @@ -468,12 +819,20 @@ async def paper2ppt_frontend_generate( api_key=api_key, credential_scope=credential_scope, email=email, - model=model, + model=resolve_model_name( + model, + managed_default=settings.PAPER2PPT_CONTENT_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), language=language, style=style, include_images=include_images, image_style=image_style, - image_model=image_model, + image_model=resolve_model_name( + image_model, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), page_id=page_id, edit_prompt=edit_prompt, current_slide=current_slide, diff --git a/fastapi_app/routers/pdf2ppt.py b/fastapi_app/routers/pdf2ppt.py index 9f71d733..72e621b5 100644 --- a/fastapi_app/routers/pdf2ppt.py +++ b/fastapi_app/routers/pdf2ppt.py @@ -7,6 +7,7 @@ from dataflow_agent.logger import get_logger from fastapi_app.config import settings +from fastapi_app.services.managed_api_service import resolve_model_name log = get_logger(__name__) @@ -59,8 +60,14 @@ async def generate_pdf2ppt( api_key=api_key, email=email, use_ai_edit=use_ai_edit, - model=model, - gen_fig_model=gen_fig_model, + model=resolve_model_name( + model, + managed_default=settings.PDF2PPT_DEFAULT_MODEL, + ), + gen_fig_model=resolve_model_name( + gen_fig_model, + managed_default=settings.PDF2PPT_DEFAULT_IMAGE_MODEL, + ), language=language, style=style, page_count=page_count, diff --git a/fastapi_app/schemas.py b/fastapi_app/schemas.py index a849e454..657c74a7 100644 --- a/fastapi_app/schemas.py +++ b/fastapi_app/schemas.py @@ -285,7 +285,7 @@ class DeepResearchRequest(BaseModel): file_paths: List[str] = [] api_url: Optional[str] = None api_key: Optional[str] = None - model: str = settings.MODEL_GPT_4O + model: str = settings.KB_CHAT_MODEL language: str = "zh" email: Optional[str] = None user_id: Optional[str] = None @@ -478,7 +478,7 @@ class KBReportRequest(BaseModel): file_paths: List[str] = [] api_url: Optional[str] = None api_key: Optional[str] = None - model: str = "gpt-5.1" + model: str = settings.KB_CHAT_MODEL language: str = "zh" report_style: Literal["insight", "analysis"] = "insight" length: Literal["short", "standard", "long"] = "standard" @@ -613,3 +613,4 @@ class Paper2PPTResponse(BaseModel): pagecontent: List[Dict[str, Any]] = [] result_path: str = "" all_output_files: List[str] = [] + error: str = "" diff --git a/fastapi_app/services/image2ppt_service.py b/fastapi_app/services/image2ppt_service.py index ee6a30c5..c9362d16 100644 --- a/fastapi_app/services/image2ppt_service.py +++ b/fastapi_app/services/image2ppt_service.py @@ -5,11 +5,13 @@ from typing import Optional from fastapi import File, UploadFile, HTTPException +from fastapi_app.config import settings from fastapi_app.schemas import Paper2PPTRequest from fastapi_app.interprocess_lock import AsyncInterProcessSemaphore from fastapi_app.services.managed_api_service import ( resolve_image_generation_credentials, resolve_llm_credentials, + resolve_model_name, ) from fastapi_app.workflow_adapters.wa_pdf2ppt import run_pdf2ppt_wf_api from dataflow_agent.utils import get_project_root @@ -64,6 +66,14 @@ async def generate_ppt( api_key, scope="image2ppt", ) + model = resolve_model_name( + model, + managed_default=settings.IMAGE2PPT_DEFAULT_MODEL, + ) + gen_fig_model = resolve_model_name( + gen_fig_model, + managed_default=settings.IMAGE2PPT_DEFAULT_IMAGE_MODEL, + ) # 0.5 如果启用 AI 增强,必须校验 API 配置 if use_ai_edit: if not resolved_chat_api_url or not resolved_api_key: diff --git a/fastapi_app/services/managed_api_service.py b/fastapi_app/services/managed_api_service.py index 37b1be3f..20e1e811 100644 --- a/fastapi_app/services/managed_api_service.py +++ b/fastapi_app/services/managed_api_service.py @@ -41,6 +41,10 @@ def _normalize_api_key(value: str | None) -> str: return (value or "").strip() +def _normalize_model_name(value: str | None) -> str: + return (value or "").strip() + + def _get_default_managed_llm_credentials() -> tuple[str, str]: return ( _normalize_api_url(settings.DF_API_URL or settings.DEFAULT_LLM_API_URL), @@ -222,6 +226,27 @@ def resolve_image_generation_credentials( return _normalize_api_url(chat_api_url), _normalize_api_key(api_key) +def resolve_model_name( + requested_model: str | None, + *, + managed_default: str | None, + fallback_default: str | None = None, +) -> str: + """ + Resolve a workflow model name under the current billing mode. + + In free/managed mode we intentionally ignore any client-provided model and + always use the backend-managed default from .env. In paid mode we still + respect the client model and only fall back when it is empty. + """ + managed_value = _normalize_model_name(managed_default) + fallback_value = _normalize_model_name(fallback_default) + if is_free_billing_mode(): + return managed_value or fallback_value + requested_value = _normalize_model_name(requested_model) + return requested_value or managed_value or fallback_value + + def get_runtime_billing_config() -> dict: pricing = get_pricing_config() managed_api_url, managed_api_key = _get_any_configured_managed_llm_credentials() @@ -229,6 +254,7 @@ def get_runtime_billing_config() -> dict: return { "billing_mode": get_billing_mode(), "user_api_config_required": is_user_api_config_required(), + "model_selection_locked": is_free_billing_mode(), "managed_api_enabled": bool(managed_api_url and managed_api_key), "managed_api_url": managed_api_url, "server_side_billing_enforced": True, diff --git a/fastapi_app/services/paper2any_service.py b/fastapi_app/services/paper2any_service.py index e8338820..28a27f5e 100644 --- a/fastapi_app/services/paper2any_service.py +++ b/fastapi_app/services/paper2any_service.py @@ -17,6 +17,7 @@ from fastapi_app.services.managed_api_service import ( resolve_image_generation_credentials, resolve_llm_credentials, + resolve_model_name, ) from dataflow_agent.utils import get_project_root from dataflow_agent.logger import get_logger @@ -261,11 +262,29 @@ async def generate_paper2figure( # paper2figure 前端历史上把 tech_route 的文本模型塞在 img_gen_model_name 里。 # 这里按 graph_type 分流,避免技术路线图仍被固定到 gpt-4o。 selected_text_model = ( - _normalize_tech_route_text_model(img_gen_model_name) + resolve_model_name( + _normalize_tech_route_text_model(img_gen_model_name), + managed_default=settings.PAPER2FIGURE_TECHNICAL_MODEL, + fallback_default=settings.PAPER2FIGURE_TEXT_MODEL, + ) + if graph_type == "tech_route" + else resolve_model_name( + settings.PAPER2FIGURE_TEXT_MODEL, + managed_default=settings.PAPER2FIGURE_TEXT_MODEL, + ) + ) + selected_image_model = ( + resolve_model_name( + settings.PAPER2FIGURE_IMAGE_MODEL, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + ) if graph_type == "tech_route" - else settings.PAPER2FIGURE_TEXT_MODEL + else resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + fallback_default=settings.PAPER2FIGURE_DEFAULT_IMAGE_MODEL, + ) ) - selected_image_model = settings.PAPER2FIGURE_IMAGE_MODEL if graph_type == "tech_route" else img_gen_model_name # 4. 构造 Request p2f_req = Paper2FigureRequest( @@ -382,11 +401,29 @@ async def generate_paper2figure_json( # paper2figure 前端历史上把 tech_route 的文本模型塞在 img_gen_model_name 里。 # 这里按 graph_type 分流,避免技术路线图仍被固定到 gpt-4o。 selected_text_model = ( - _normalize_tech_route_text_model(img_gen_model_name) + resolve_model_name( + _normalize_tech_route_text_model(img_gen_model_name), + managed_default=settings.PAPER2FIGURE_TECHNICAL_MODEL, + fallback_default=settings.PAPER2FIGURE_TEXT_MODEL, + ) + if graph_type == "tech_route" + else resolve_model_name( + settings.PAPER2FIGURE_TEXT_MODEL, + managed_default=settings.PAPER2FIGURE_TEXT_MODEL, + ) + ) + selected_image_model = ( + resolve_model_name( + settings.PAPER2FIGURE_IMAGE_MODEL, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + ) if graph_type == "tech_route" - else settings.PAPER2FIGURE_TEXT_MODEL + else resolve_model_name( + img_gen_model_name, + managed_default=settings.PAPER2FIGURE_IMAGE_MODEL, + fallback_default=settings.PAPER2FIGURE_DEFAULT_IMAGE_MODEL, + ) ) - selected_image_model = settings.PAPER2FIGURE_IMAGE_MODEL if graph_type == "tech_route" else img_gen_model_name # 4. 构造 Request p2f_req = Paper2FigureRequest( diff --git a/fastapi_app/services/paper2drawio_service.py b/fastapi_app/services/paper2drawio_service.py index f7a052cd..35be658c 100644 --- a/fastapi_app/services/paper2drawio_service.py +++ b/fastapi_app/services/paper2drawio_service.py @@ -19,7 +19,7 @@ from dataflow_agent.logger import get_logger from fastapi_app.config.settings import settings from fastapi_app.interprocess_lock import AsyncInterProcessSemaphore -from fastapi_app.services.managed_api_service import resolve_llm_credentials +from fastapi_app.services.managed_api_service import resolve_llm_credentials, resolve_model_name log = get_logger(__name__) @@ -163,6 +163,14 @@ async def generate_diagram( api_key, scope="paper2drawio", ) + model = resolve_model_name( + model, + managed_default=settings.PAPER2DRAWIO_DEFAULT_MODEL, + ) + vlm_model = resolve_model_name( + vlm_model, + managed_default=settings.PAPER2DRAWIO_VLM_MODEL, + ) run_dir = self._create_run_dir("paper2drawio", email) input_dir = run_dir / "input" @@ -320,6 +328,10 @@ async def chat_edit( api_key, scope="paper2drawio", ) + model = resolve_model_name( + model, + managed_default=settings.PAPER2DRAWIO_DEFAULT_MODEL, + ) current_cells = ( extract_cells(current_xml) if (" List[Dict[str, Any]]: system_prompt = """ -You are an expert academic slide frontend engineer. -Generate a single 16:9 presentation slide as HTML/CSS for a browser-based PPT editor. +You are an expert academic presentation designer. +Generate one strictly structured 16:9 slide for a browser PPT editor and true editable PPT export. Hard requirements: 1. Return JSON only. No markdown fences. No explanation. 2. Output schema: { "title": "short string", - "html_template": "HTML string", - "css_code": "CSS string", - "editable_fields": [ - {"key": "title", "label": "Title", "type": "text", "value": "..."}, - {"key": "summary", "label": "Summary", "type": "textarea", "value": "..."}, - {"key": "key_points", "label": "Key Points", "type": "list", "items": ["...", "..."]} - ], + "layout_type": "cover | section | bullets | two_column | cards_2x2 | image_focus | comparison | timeline", + "content": { + "...": "layout-specific content" + }, "generation_note": "one short sentence" } -3. Every visible text in html_template must come from placeholders only: - - text/textarea fields: {{field:key}} - - list fields: {{list:key}} - - controlled images, when required: {{image:key}} -4. css_code must only target .slide-root and its descendants. -5. Do not use external assets, remote fonts, raw image URLs, svg, canvas, script, iframe, video or img tags. -6. The slide must fit inside a 1600x900 canvas with safe margins and no overflow. -7. Use the supplied deck theme so every page looks like the same presentation family. -8. Treat theme_lock as non-negotiable. Do not invent a new palette family, component language, or typography system. -9. Keep titles within 2 lines, with title font 42-60px and body text 18-28px. -10. Prefer grid/flex layouts over brittle absolute positioning. -11. If visual_assets are supplied, reserve layout space and place them using {{image:key}} placeholders. Never write a raw tag yourself. -12. If visual_assets are empty, build a text-first slide using editable text blocks and CSS decoration only. -13. The HTML must contain a single .slide-root root element. -14. If reference deck slides are provided, preserve their shared component grammar, spacing rhythm, and card treatment. -15. Never put {{field:...}}, {{list:...}}, or {{image:...}} placeholders inside HTML attributes like aria-label, title, alt, data-*, href, or style. Placeholders may only appear in element content. +3. Never return HTML, CSS, SVG, coordinates, raw style code, or arbitrary DOM. +4. Use only the allowed layout_type values. +5. Keep the slide strictly editable: + - all visible text must live in `content` + - images must be referenced only through the provided visual_assets slots +6. Use the supplied deck theme so every page looks like the same presentation family. +7. Treat theme_lock as non-negotiable. Do not invent a new palette family, component language, or typography system. +8. Keep titles within 2 lines, body content concise, and list lengths <= 6. +9. If visual_assets are present, prefer `image_focus`. If no visual_assets are present, do not choose `image_focus`. +10. `cards_2x2` must contain exactly 4 cards. +11. `timeline` must contain 3 to 5 items. +12. `comparison` must contain left and right sections with short bullet lists. + +Layout content schema: +- cover: + eyebrow, title, subtitle, presenter, footer +- section: + eyebrow, title, summary, quote, footer +- bullets: + eyebrow, title, summary, bullets[], takeaway, footer +- two_column: + eyebrow, title, summary, left_heading, left_body, left_points[], right_heading, right_body, right_points[], footer +- cards_2x2: + eyebrow, title, summary, cards[{title, body} x4], footer +- image_focus: + eyebrow, title, summary, bullets[], visual_caption, footer +- comparison: + eyebrow, title, summary, left_title, left_points[], right_title, right_points[], footer +- timeline: + eyebrow, title, summary, timeline[{label, body}], footer """.strip() outline_payload = { @@ -1309,7 +1337,7 @@ def _build_messages( user_sections.append(f"Revision request: {edit_prompt}") user_sections.append( - "Ensure the editable_fields fully cover all meaningful visible text shown on the slide." + "Return a compact structured slide. Do not emit arbitrary layout code." ) return [ @@ -1387,13 +1415,9 @@ def _summarize_slide_for_review(self, slide: Dict[str, Any]) -> Dict[str, Any]: "type": field_type, } if field_type == "list": - entry["items"] = [ - str(item).strip() - for item in (field.get("items") or []) - if str(item).strip() - ][:5] + entry["items"] = self._normalize_outline_points(field.get("items"), limit=5, item_limit=140) else: - entry["value"] = str(field.get("value") or "").strip()[:280] + entry["value"] = self._clean_text_content(field.get("value"), "", 280) summarized_fields.append(entry) visual_assets = slide.get("visual_assets") or slide.get("visualAssets") or [] @@ -1434,63 +1458,307 @@ def _normalize_slide_payload( theme=theme, visual_assets=visual_assets, ) - html_template = payload.get("html_template") or payload.get("html") or "" - css_code = payload.get("css_code") or payload.get("css") or "" - if not isinstance(html_template, str) or not isinstance(css_code, str): + layout_type = str(payload.get("layout_type") or payload.get("layoutType") or "").strip() + content = payload.get("content") or {} + if not isinstance(content, dict): return fallback_slide - if len(html_template) > 16000 or len(css_code) > 20000: + if layout_type not in { + "cover", + "section", + "bullets", + "two_column", + "cards_2x2", + "image_focus", + "comparison", + "timeline", + }: return fallback_slide - if _FORBIDDEN_HTML_RE.search(html_template) or _FORBIDDEN_CSS_RE.search(css_code): + if visual_assets and layout_type != "image_focus": + layout_type = "image_focus" + if not visual_assets and layout_type == "image_focus": return fallback_slide - normalized_html = self._sanitize_html_template(html_template) - normalized_css = self._sanitize_css(css_code, theme=theme) - editable_fields = self._normalize_fields( - payload.get("editable_fields"), - outline_item=outline_item, - slide_index=slide_index, - ) - if not editable_fields: - return fallback_slide - - normalized_html, attribute_warnings = self._sanitize_attribute_placeholders( - normalized_html, - editable_fields, - ) - if attribute_warnings: + try: + return self._build_structured_slide( + layout_type=layout_type, + content=content, + outline_item=outline_item, + slide_index=slide_index, + slide_count=slide_count, + theme=theme, + visual_assets=visual_assets, + generation_note=str(payload.get("generation_note") or "").strip(), + ) + except Exception as exc: # noqa: BLE001 log.warning( - "[Paper2PPTFrontendService] Sanitized attribute placeholders for page %s: %s", + "[Paper2PPTFrontendService] Failed to normalize structured slide payload for page %s: %s", slide_index + 1, - ", ".join(attribute_warnings), + exc, ) - - field_keys = {field["key"] for field in editable_fields} - placeholders = set(_FIELD_PLACEHOLDER_RE.findall(normalized_html)) - image_placeholders = set(_IMAGE_PLACEHOLDER_RE.findall(normalized_html)) - asset_keys = {str(asset.get("key") or "").strip() for asset in visual_assets if str(asset.get("key") or "").strip()} - if not placeholders: - return fallback_slide - if not placeholders.issubset(field_keys): - return fallback_slide - if image_placeholders and not image_placeholders.issubset(asset_keys): - return fallback_slide - if visual_assets and not image_placeholders: return fallback_slide - title_value = ( - self._find_field_value(editable_fields, "title") - or outline_item.get("title") - or f"Slide {slide_index + 1}" + def _field_entry( + self, + *, + key: str, + label: str, + field_type: str, + value: str = "", + items: Optional[List[str]] = None, + ) -> Dict[str, Any]: + return { + "key": key, + "label": label, + "type": field_type, + "value": value, + "items": items or [], + } + + def _clean_text_content(self, value: Any, default: str = "", limit: int = 280) -> str: + text = self._extract_outline_text(value) + text = re.sub(r"\s+", " ", text) + return (text or default)[:limit] + + def _extract_outline_text(self, value: Any) -> str: + if value is None: + return "" + if isinstance(value, str): + return value.strip() + if isinstance(value, (int, float, bool)): + return str(value).strip() + if isinstance(value, dict): + preferred_keys = ( + "text", + "value", + "content", + "summary", + "title", + "label", + "body", + "description", + "reason", + "point", + ) + for key in preferred_keys: + extracted = self._extract_outline_text(value.get(key)) + if extracted: + return extracted + parts = [self._extract_outline_text(item) for item in value.values()] + joined = " ".join(part for part in parts if part) + return joined.strip() + if isinstance(value, list): + parts = [self._extract_outline_text(item) for item in value] + joined = " ".join(part for part in parts if part) + return joined.strip() + return str(value).strip() + + def _normalize_outline_points( + self, + value: Any, + *, + limit: int = 6, + item_limit: int = 120, + ) -> List[str]: + normalized: List[str] = [] + + def _append(item: Any) -> None: + text = self._clean_text_content(item, "", item_limit) + if text and text not in normalized: + normalized.append(text) + + if isinstance(value, list): + for item in value: + if isinstance(item, list): + for nested in item: + _append(nested) + else: + _append(item) + elif value is not None: + _append(value) + return normalized[:limit] + + def _clean_list_content( + self, + value: Any, + *, + defaults: Optional[List[str]] = None, + limit: int = 6, + item_limit: int = 120, + ) -> List[str]: + cleaned: List[str] = [] + if isinstance(value, list): + for item in value: + text = self._clean_text_content(item, "", item_limit) + if text: + cleaned.append(text) + elif isinstance(value, str) and value.strip(): + cleaned = [self._clean_text_content(value, "", item_limit)] + if cleaned: + return cleaned[:limit] + return (defaults or [])[:limit] + + def _build_structured_slide( + self, + *, + layout_type: str, + content: Dict[str, Any], + outline_item: Dict[str, Any], + slide_index: int, + slide_count: int, + theme: Dict[str, Any], + visual_assets: List[Dict[str, Any]], + generation_note: str, + ) -> Dict[str, Any]: + fallback_title = str(outline_item.get("title") or f"Slide {slide_index + 1}").strip() + section_template = str(theme.get("section_label_template") or "Slide {page_num:02d}/{slide_count:02d}") + try: + default_eyebrow = section_template.format(page_num=slide_index + 1, slide_count=slide_count) + except Exception: # noqa: BLE001 + default_eyebrow = f"Slide {slide_index + 1:02d}/{slide_count:02d}" + key_points = self._normalize_outline_points(outline_item.get("key_points"), limit=6, item_limit=120) + default_summary = key_points[0] if key_points else self._clean_text_content( + outline_item.get("layout_description"), + "", + 280, + ) + default_footer = str(theme.get("footer_text") or "Paper2Any Structured PPT").strip() + + editable_fields: List[Dict[str, Any]] = [] + layout_data: Dict[str, Any] = {"type": layout_type} + + def add_text(key: str, label: str, default: str, *, field_type: str = "text", limit: int = 280) -> str: + value = self._clean_text_content(content.get(key), default, limit) + editable_fields.append( + self._field_entry( + key=key, + label=label, + field_type="textarea" if field_type == "textarea" else "text", + value=value, + ) + ) + return key + + def add_list(key: str, label: str, default_items: List[str], *, limit: int = 6, item_limit: int = 120) -> str: + items = self._clean_list_content( + content.get(key), + defaults=default_items, + limit=limit, + item_limit=item_limit, + ) + editable_fields.append( + self._field_entry( + key=key, + label=label, + field_type="list", + items=items, + ) + ) + return key + + layout_data["eyebrow_key"] = add_text("eyebrow", "Eyebrow", default_eyebrow) + layout_data["title_key"] = add_text("title", "Title", fallback_title, limit=120) + layout_data["footer_key"] = add_text("footer", "Footer", default_footer, limit=80) + + if layout_type == "cover": + layout_data["subtitle_key"] = add_text("subtitle", "Subtitle", default_summary, field_type="textarea", limit=220) + layout_data["presenter_key"] = add_text("presenter", "Presenter", "Presenter / Team", limit=80) + elif layout_type == "section": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=220) + layout_data["quote_key"] = add_text("quote", "Quote", key_points[1] if len(key_points) > 1 else default_summary, field_type="textarea", limit=200) + elif layout_type == "bullets": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=220) + layout_data["bullets_key"] = add_list("bullets", "Bullets", key_points[:5] or ["Add key points"]) + layout_data["takeaway_key"] = add_text("takeaway", "Takeaway", key_points[-1] if key_points else default_summary, field_type="textarea", limit=180) + elif layout_type == "two_column": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=220) + layout_data["left_heading_key"] = add_text("left_heading", "Left Heading", "Core Idea", limit=80) + layout_data["left_body_key"] = add_text("left_body", "Left Body", key_points[0] if key_points else default_summary, field_type="textarea", limit=180) + layout_data["left_points_key"] = add_list("left_points", "Left Points", key_points[:3], limit=4) + layout_data["right_heading_key"] = add_text("right_heading", "Right Heading", "Implication", limit=80) + layout_data["right_body_key"] = add_text("right_body", "Right Body", key_points[1] if len(key_points) > 1 else default_summary, field_type="textarea", limit=180) + layout_data["right_points_key"] = add_list("right_points", "Right Points", key_points[2:5] or key_points[:2], limit=4) + elif layout_type == "cards_2x2": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=200) + raw_cards = content.get("cards") + cards = raw_cards if isinstance(raw_cards, list) else [] + card_refs: List[Dict[str, str]] = [] + for index in range(4): + item = cards[index] if index < len(cards) and isinstance(cards[index], dict) else {} + title_key = f"card_{index + 1}_title" + body_key = f"card_{index + 1}_body" + editable_fields.append(self._field_entry( + key=title_key, + label=f"Card {index + 1} Title", + field_type="text", + value=self._clean_text_content(item.get("title"), f"Point {index + 1}", 80), + )) + editable_fields.append(self._field_entry( + key=body_key, + label=f"Card {index + 1} Body", + field_type="textarea", + value=self._clean_text_content( + item.get("body"), + key_points[index] if index < len(key_points) else default_summary, + 140, + ), + )) + card_refs.append({"title_key": title_key, "body_key": body_key}) + layout_data["cards"] = card_refs + elif layout_type == "image_focus": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=180) + layout_data["bullets_key"] = add_list("bullets", "Bullets", key_points[:4], limit=4) + layout_data["visual_caption_key"] = add_text("visual_caption", "Visual Caption", "Supporting visual", limit=90) + layout_data["visual_key"] = str((visual_assets[0].get("key") if visual_assets else _DEFAULT_VISUAL_KEY) or _DEFAULT_VISUAL_KEY) + elif layout_type == "comparison": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=180) + layout_data["left_title_key"] = add_text("left_title", "Left Title", "Track A", limit=80) + layout_data["left_points_key"] = add_list("left_points", "Left Points", key_points[:3], limit=4) + layout_data["right_title_key"] = add_text("right_title", "Right Title", "Track B", limit=80) + layout_data["right_points_key"] = add_list("right_points", "Right Points", key_points[3:6] or key_points[:3], limit=4) + elif layout_type == "timeline": + layout_data["summary_key"] = add_text("summary", "Summary", default_summary, field_type="textarea", limit=180) + raw_timeline = content.get("timeline") + timeline_items = raw_timeline if isinstance(raw_timeline, list) else [] + timeline_refs: List[Dict[str, str]] = [] + count = max(3, min(5, len(timeline_items) or 3)) + for index in range(count): + item = timeline_items[index] if index < len(timeline_items) and isinstance(timeline_items[index], dict) else {} + label_key = f"timeline_{index + 1}_label" + body_key = f"timeline_{index + 1}_body" + editable_fields.append(self._field_entry( + key=label_key, + label=f"Timeline {index + 1} Label", + field_type="text", + value=self._clean_text_content(item.get("label"), f"Phase {index + 1}", 60), + )) + editable_fields.append(self._field_entry( + key=body_key, + label=f"Timeline {index + 1} Body", + field_type="textarea", + value=self._clean_text_content( + item.get("body"), + key_points[index] if index < len(key_points) else default_summary, + 120, + ), + )) + timeline_refs.append({"label_key": label_key, "body_key": body_key}) + layout_data["timeline"] = timeline_refs + else: + raise ValueError(f"unsupported layout_type: {layout_type}") + + title_value = next( + (field.get("value") for field in editable_fields if field.get("key") == "title"), + fallback_title, ) return { - "slide_id": str(payload.get("slide_id") or slide_index + 1), + "slide_id": str(slide_index + 1), "page_num": slide_index + 1, - "title": str(payload.get("title") or title_value), - "html_template": normalized_html, - "css_code": normalized_css, + "title": str(title_value or fallback_title), + "layout_type": layout_type, + "layout_data": layout_data, "editable_fields": editable_fields, "visual_assets": visual_assets, - "generation_note": str(payload.get("generation_note") or ""), + "generation_note": generation_note or "Structured slide generated", "status": "done", } @@ -1501,11 +1769,7 @@ def _normalize_review_payload( slide: Dict[str, Any], local_layout_issues: List[str], ) -> Dict[str, Any]: - issues = [ - str(item).strip() - for item in (payload.get("issues") or []) - if str(item).strip() - ] + issues = self._normalize_outline_points(payload.get("issues"), limit=12, item_limit=220) combined_issues: List[str] = [] for issue in [*local_layout_issues, *issues]: if issue and issue not in combined_issues: @@ -1542,11 +1806,7 @@ def _normalize_fields( ) -> List[Dict[str, Any]]: normalized: List[Dict[str, Any]] = [] seen_keys: set[str] = set() - outline_points = [ - str(item).strip() - for item in (outline_item.get("key_points") or []) - if str(item).strip() - ] + outline_points = self._normalize_outline_points(outline_item.get("key_points"), limit=6, item_limit=120) if isinstance(raw_fields, list): for raw_field in raw_fields: @@ -1560,11 +1820,7 @@ def _normalize_fields( field_type = "text" label = str(raw_field.get("label") or key.replace("_", " ").title()) if field_type == "list": - items = [ - str(item).strip() - for item in (raw_field.get("items") or []) - if str(item).strip() - ] + items = self._normalize_outline_points(raw_field.get("items"), limit=8, item_limit=140) if not items: items = outline_points[:4] normalized.append( @@ -1577,7 +1833,7 @@ def _normalize_fields( } ) else: - value = str(raw_field.get("value") or "").strip() + value = self._clean_text_content(raw_field.get("value"), "", 280) normalized.append( { "key": key, @@ -1595,7 +1851,7 @@ def _normalize_fields( "key": "title", "label": "Title", "type": "text", - "value": str(outline_item.get("title") or f"Slide {slide_index + 1}"), + "value": self._clean_text_content(outline_item.get("title"), f"Slide {slide_index + 1}", 220), "items": [], } ) @@ -1605,9 +1861,11 @@ def _normalize_fields( "key": "summary", "label": "Summary", "type": "textarea", - "value": str( - (outline_points[0] if outline_points else outline_item.get("layout_description") or "") - ).strip(), + "value": self._clean_text_content( + outline_points[0] if outline_points else outline_item.get("layout_description"), + "", + 280, + ), "items": [], } ) @@ -1804,21 +2062,13 @@ def _clean_int(value: Any, default: int, min_value: int, max_value: int) -> int: def _clean_list(value: Any, defaults: List[str], limit: int = 6) -> List[str]: if isinstance(value, list): - cleaned = [str(item).strip() for item in value if str(item).strip()] + cleaned = self._normalize_outline_points(value, limit=limit, item_limit=140) if cleaned: return cleaned[:limit] return defaults[:limit] - layout_rules = [ - str(item).strip() - for item in (payload.get("layout_rules") or []) - if str(item).strip() - ][:6] - component_rules = [ - str(item).strip() - for item in (payload.get("component_rules") or []) - if str(item).strip() - ][:6] + layout_rules = self._normalize_outline_points(payload.get("layout_rules"), limit=6, item_limit=180) + component_rules = self._normalize_outline_points(payload.get("component_rules"), limit=6, item_limit=180) return { "theme_name": _clean_text(payload.get("theme_name"), fallback["theme_name"]), @@ -1898,25 +2148,25 @@ def _build_theme_lock(self, theme: Dict[str, Any]) -> Dict[str, Any]: theme_lock = theme.get("theme_lock") if isinstance(theme_lock, dict): return { - "must_keep": [ - str(item).strip() - for item in (theme_lock.get("must_keep") or []) - if str(item).strip() - ] or fallback["theme_lock"]["must_keep"], - "preferred_layout_patterns": [ - str(item).strip() - for item in (theme_lock.get("preferred_layout_patterns") or []) - if str(item).strip() - ] or fallback["theme_lock"]["preferred_layout_patterns"], + "must_keep": self._normalize_outline_points( + theme_lock.get("must_keep"), + limit=8, + item_limit=180, + ) or fallback["theme_lock"]["must_keep"], + "preferred_layout_patterns": self._normalize_outline_points( + theme_lock.get("preferred_layout_patterns"), + limit=8, + item_limit=180, + ) or fallback["theme_lock"]["preferred_layout_patterns"], "component_signature": str( theme_lock.get("component_signature") or fallback["theme_lock"]["component_signature"] ).strip(), - "avoid": [ - str(item).strip() - for item in (theme_lock.get("avoid") or []) - if str(item).strip() - ] or fallback["theme_lock"]["avoid"], + "avoid": self._normalize_outline_points( + theme_lock.get("avoid"), + limit=8, + item_limit=180, + ) or fallback["theme_lock"]["avoid"], } return fallback["theme_lock"] @@ -2146,20 +2396,21 @@ def _load_reference_slides( return [self._summarize_reference_slide(slide) for slide in references] def _summarize_reference_slide(self, slide: Dict[str, Any]) -> Dict[str, Any]: - html_template = str(slide.get("html_template") or "") - css_code = str(slide.get("css_code") or "") editable_fields = slide.get("editable_fields") or [] return { "page_num": int(slide.get("page_num") or 0), "title": str(slide.get("title") or "").strip(), + "layout_type": str(slide.get("layout_type") or slide.get("layoutType") or "").strip(), "field_keys": [ str(field.get("key") or "").strip() for field in editable_fields if isinstance(field, dict) and str(field.get("key") or "").strip() ][:10], - "html_outline": self._extract_html_outline(html_template), - "component_classes": self._extract_component_classes(html_template, css_code), - "css_selectors": self._extract_css_selectors(css_code), + "visual_asset_keys": [ + str(asset.get("key") or "").strip() + for asset in (slide.get("visual_assets") or []) + if isinstance(asset, dict) and str(asset.get("key") or "").strip() + ][:4], } def _extract_html_outline(self, html_template: str, limit: int = 12) -> List[str]: @@ -2225,315 +2476,41 @@ def _build_fallback_slide( theme: Dict[str, Any], visual_assets: Optional[List[Dict[str, Any]]] = None, ) -> Dict[str, Any]: - palette = theme.get("palette") or self._build_fallback_theme(language="zh", style="")["palette"] - typography = theme.get("typography") or {} visual_assets = (visual_assets or [])[:_MAX_INLINE_VISUAL_ASSETS] - has_visual = bool(visual_assets) - has_multi_visual = len(visual_assets) > 1 - key_points = [ - str(item).strip() - for item in (outline_item.get("key_points") or []) - if str(item).strip() - ][:4] - summary = key_points[0] if key_points else str(outline_item.get("layout_description") or "").strip() + key_points = self._normalize_outline_points(outline_item.get("key_points"), limit=4, item_limit=120) + summary = key_points[0] if key_points else self._clean_text_content( + outline_item.get("layout_description"), + "", + 280, + ) takeaway = key_points[-1] if key_points else "Refine the narrative in the editor" section_template = str(theme.get("section_label_template") or "Slide {page_num:02d}/{slide_count:02d}") try: eyebrow = section_template.format(page_num=slide_index + 1, slide_count=slide_count) except Exception: # noqa: BLE001 eyebrow = f"Slide {slide_index + 1:02d}/{slide_count:02d}" - - visual_markup = "" - if has_visual: - visual_markup = "\n".join( - f'
{{{{image:{asset.get("key") or self._build_visual_asset_key(asset_index)}}}}}
' - for asset_index, asset in enumerate(visual_assets) - ) - - html_template = """ -
-
-
-
-
-
{{field:eyebrow}}
-

{{field:title}}

-

{{field:summary}}

- """ + ( - """ -
    {{list:key_points}}
-""" - if has_visual - else "" - ) + """ -
- """ + ( - """ -
-""" + visual_markup + """ -
-""" - if has_visual - else """ -
-
{{field:points_label}}
-
    {{list:key_points}}
-
-""" - ) + """ -
- -
-
-""".strip() - - css_code = f""" -.slide-root {{ - width: 100%; - height: 100%; - background: - radial-gradient(circle at top right, {palette["secondary"]}33 0%, transparent 28%), - radial-gradient(circle at bottom left, {palette["accent"]}22 0%, transparent 32%), - {palette["bg"]}; - color: {palette["text"]}; - overflow: hidden; -}} -.slide-root * {{ - box-sizing: border-box; -}} -.slide-shell {{ - position: relative; - width: 100%; - height: 100%; - padding: 68px 72px; -}} -.grid-layer {{ - position: absolute; - inset: 0; - background-image: - linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px), - linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px); - background-size: 48px 48px; - opacity: 0.22; -}} -.hero {{ - position: relative; - z-index: 1; - display: grid; - grid-template-columns: {'1.08fr 0.92fr' if has_visual else '1.5fr 0.95fr'}; - gap: 28px; - height: calc(100% - 120px); -}} -.hero-copy {{ - display: flex; - flex-direction: column; - justify-content: center; - gap: 20px; -}} -.eyebrow {{ - display: inline-flex; - align-self: flex-start; - padding: 8px 14px; - border-radius: 999px; - background: {palette["secondary"]}22; - border: 1px solid {palette["primary"]}55; - color: {palette["primary"]}; - font-size: {int(typography.get("eyebrow_size") or 18)}px; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; -}} -.title {{ - margin: 0; - max-width: 880px; - font-size: {int(typography.get("title_size") or 56)}px; - line-height: 1.04; - letter-spacing: -0.04em; - font-family: {typography.get("title_font_stack") or 'Georgia, "Times New Roman", serif'}; -}} -.summary {{ - margin: 0; - max-width: 840px; - font-size: {int(typography.get("summary_size") or 26)}px; - line-height: 1.42; - color: {palette["muted"]}; - white-space: pre-wrap; - font-family: {typography.get("body_font_stack") or '"Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'}; -}} -.stat-card, .takeaway-card, .visual-card {{ - border-radius: 28px; - border: 1px solid {palette["primary"]}30; - background: {palette["panel"]}; - box-shadow: 0 30px 60px rgba(15, 23, 42, 0.35); - backdrop-filter: blur(10px); -}} -.stat-card {{ - align-self: center; - padding: 28px; -}} -.visual-card {{ - padding: 18px; - min-height: 420px; - display: flex; - flex-direction: column; - gap: 14px; -}} -.visual-card.visual-card-grid {{ - justify-content: stretch; -}} -.visual-shell {{ - width: 100%; - height: 100%; - min-height: 384px; - border-radius: 22px; - overflow: hidden; -}} -.visual-card.visual-card-grid .visual-shell {{ - flex: 1 1 0; - min-height: 160px; -}} -.visual-card.visual-card-grid .visual-shell-1 {{ - min-height: 236px; -}} -.card-label, .takeaway-label {{ - font-size: {int(typography.get("eyebrow_size") or 18)}px; - letter-spacing: 0.08em; - text-transform: uppercase; - color: {palette["primary"]}; - margin-bottom: 14px; - font-weight: 700; -}} -.bullet-list {{ - margin: 0; - padding-left: 26px; - display: grid; - gap: 14px; - font-size: {int(typography.get("body_size") or 24)}px; - line-height: 1.35; - font-family: {typography.get("body_font_stack") or '"Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'}; -}} -.bullet-list li {{ - color: {palette["text"]}; -}} -.bullet-list.compact {{ - max-width: 720px; - gap: 10px; - font-size: {max(18, int(typography.get("body_size") or 24) - 2)}px; -}} -.footer-row {{ - position: relative; - z-index: 1; - display: grid; - grid-template-columns: 1.4fr auto; - align-items: end; - gap: 18px; -}} -.takeaway-card {{ - padding: 24px 28px; -}} -.takeaway-text {{ - margin: 0; - font-size: {int(typography.get("body_size") or 24)}px; - line-height: 1.4; - color: {palette["text"]}; - white-space: pre-wrap; - font-family: {typography.get("body_font_stack") or '"Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'}; -}} -.footer-tag {{ - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 220px; - padding: 14px 18px; - border-radius: 999px; - border: 1px solid {palette["accent"]}55; - color: {palette["accent"]}; - font-size: {int(typography.get("eyebrow_size") or 18)}px; - font-weight: 700; - background: rgba(15, 23, 42, 0.45); -}} -""".strip() - - editable_fields = [ - { - "key": "eyebrow", - "label": "Eyebrow", - "type": "text", - "value": eyebrow, - "items": [], - }, - { - "key": "title", - "label": "Title", - "type": "text", - "value": str(outline_item.get("title") or f"Slide {slide_index + 1}"), - "items": [], - }, - { - "key": "summary", - "label": "Summary", - "type": "textarea", - "value": summary, - "items": [], - }, - { - "key": "key_points", - "label": "Key Points", - "type": "list", - "value": "", - "items": key_points or ["Summarize the page content here"], - }, - { - "key": "takeaway_label", - "label": "Takeaway Label", - "type": "text", - "value": "Takeaway", - "items": [], - }, - { - "key": "takeaway", - "label": "Takeaway", - "type": "textarea", - "value": takeaway, - "items": [], - }, - { - "key": "footer", - "label": "Footer", - "type": "text", - "value": str(theme.get("footer_text") or "Paper2Any Frontend PPT"), - "items": [], - }, - ] - if not has_visual: - editable_fields.insert( - 3, - { - "key": "points_label", - "label": "Points Label", - "type": "text", - "value": "Key Points", - "items": [], - }, - ) - - return { - "slide_id": str(slide_index + 1), - "page_num": slide_index + 1, + layout_type = "image_focus" if visual_assets else "bullets" + content = { + "eyebrow": eyebrow, "title": str(outline_item.get("title") or f"Slide {slide_index + 1}"), - "html_template": html_template, - "css_code": css_code, - "editable_fields": editable_fields, - "visual_assets": visual_assets, - "generation_note": "Built-in fallback template", - "status": "done", + "summary": summary, + "bullets": key_points or ["Summarize the page content here"], + "takeaway": takeaway, + "footer": str(theme.get("footer_text") or "Paper2Any Structured PPT"), + "visual_caption": "Supporting visual", } + slide = self._build_structured_slide( + layout_type=layout_type, + content=content, + outline_item=outline_item, + slide_index=slide_index, + slide_count=slide_count, + theme=theme, + visual_assets=visual_assets, + generation_note="Built-in fallback structured slide", + ) + slide["generation_note"] = "Built-in fallback structured slide" + return slide def _sanitize_html_template(self, html_template: str) -> str: cleaned = re.sub(r"<\s*/?\s*(html|head|body)\b[^>]*>", "", html_template, flags=re.IGNORECASE) @@ -2567,13 +2544,9 @@ def _replace_field(token_match: re.Match[str]) -> str: if field is None: return "" if str(field.get("type") or "") == "list": - raw_value = " • ".join( - str(item).strip() - for item in (field.get("items") or []) - if str(item).strip() - ) + raw_value = " • ".join(self._normalize_outline_points(field.get("items"), limit=12, item_limit=180)) else: - raw_value = str(field.get("value") or "") + raw_value = self._extract_outline_text(field.get("value")) return html.escape(" ".join(raw_value.split()), quote=True) next_value = re.sub(r"\{\{field:([a-zA-Z0-9_]+)\}\}", _replace_field, next_value) diff --git a/fastapi_app/services/paper2ppt_service.py b/fastapi_app/services/paper2ppt_service.py index f2747960..b00d03c3 100644 --- a/fastapi_app/services/paper2ppt_service.py +++ b/fastapi_app/services/paper2ppt_service.py @@ -105,6 +105,7 @@ from fastapi import HTTPException, Request, UploadFile from PIL import Image, ImageOps, UnidentifiedImageError +from fastapi_app.config import settings from fastapi_app.schemas import ( FullPipelineRequest, OutlineRefineRequest, @@ -114,6 +115,7 @@ from fastapi_app.services.managed_api_service import ( resolve_image_generation_credentials, resolve_llm_credentials, + resolve_model_name, ) from fastapi_app.utils import ( _to_outputs_url, @@ -234,7 +236,11 @@ async def get_page_content( credential_scope=credential_scope, chat_api_key=resolved_api_key, api_key=resolved_api_key, - model=req.model, + model=resolve_model_name( + req.model, + managed_default=settings.PAPER2PPT_OUTLINE_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), gen_fig_model="", input_type=wf_input_type, input_content=wf_input_content, @@ -249,6 +255,21 @@ async def get_page_content( resp_model = await run_paper2page_content_wf_api(p2ppt_req, result_path=run_dir) resp_dict = resp_model.model_dump() + resp_dict["pagecontent"] = self._normalize_pagecontent_items(resp_dict.get("pagecontent", [])) + if not resp_dict["pagecontent"]: + backend_error = str(resp_dict.get("error") or "").strip() + if backend_error: + raise HTTPException(status_code=502, detail=backend_error) + raw_text = (req.text or "").strip() + if str(req.input_type).lower() == "text" and use_long_paper_bool and req.page_count > 20: + raise HTTPException( + status_code=400, + detail=( + f"当前为文本模式,输入内容仅 {len(raw_text)} 个字符,不足以稳定生成 {req.page_count} 页长文大纲。" + "请提供更完整的正文,或改用 Topic 模式。" + ), + ) + raise HTTPException(status_code=502, detail="后端未生成有效大纲,请稍后重试") if request is not None: resp_dict["pagecontent"] = self._convert_pagecontent_paths_to_urls( resp_dict.get("pagecontent", []), request, resp_model.result_path @@ -286,7 +307,11 @@ async def refine_outline( credential_scope=credential_scope, chat_api_key=resolved_api_key, api_key=resolved_api_key, - model=req.model, + model=resolve_model_name( + req.model, + managed_default=settings.PAPER2PPT_OUTLINE_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), gen_fig_model="", input_type="TEXT", input_content="", @@ -307,6 +332,7 @@ async def refine_outline( ) resp_dict = resp_model.model_dump() + resp_dict["pagecontent"] = self._normalize_pagecontent_items(resp_dict.get("pagecontent", [])) if request is not None: resp_dict["pagecontent"] = self._convert_pagecontent_paths_to_urls( resp_dict.get("pagecontent", []), request, resp_model.result_path @@ -381,8 +407,16 @@ async def generate_ppt( api_key=resolved_api_key, image_api_url=resolved_image_api_url, image_api_key=resolved_image_api_key, - model=req.model, - gen_fig_model=req.img_gen_model_name, + model=resolve_model_name( + req.model, + managed_default=settings.PAPER2PPT_CONTENT_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), + gen_fig_model=resolve_model_name( + req.img_gen_model_name, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), input_type="PDF", input_content="", aspect_ratio=req.aspect_ratio, @@ -446,8 +480,16 @@ async def run_full_pipeline( api_key=resolved_api_key, image_api_url=resolved_image_api_url, image_api_key=resolved_image_api_key, - model=req.model, - gen_fig_model=req.img_gen_model_name, + model=resolve_model_name( + req.model, + managed_default=settings.PAPER2PPT_CONTENT_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_MODEL, + ), + gen_fig_model=resolve_model_name( + req.img_gen_model_name, + managed_default=settings.PAPER2PPT_IMAGE_GEN_MODEL, + fallback_default=settings.PAPER2PPT_DEFAULT_IMAGE_MODEL, + ), input_type=wf_input_type, input_content=wf_input_content, aspect_ratio=req.aspect_ratio, @@ -845,4 +887,66 @@ def _parse_pagecontent_json(self, pagecontent_json: str) -> List[Dict[str, Any]] for i, it in enumerate(obj): if not isinstance(it, dict): raise HTTPException(status_code=400, detail=f"pagecontent[{i}] must be an object(dict)") - return obj + return self._normalize_pagecontent_items(obj) + + def _extract_outline_text(self, value: Any) -> str: + if value is None: + return "" + if isinstance(value, str): + return " ".join(value.strip().split()) + if isinstance(value, (int, float, bool)): + return str(value) + if isinstance(value, dict): + preferred_keys = ( + "text", + "value", + "content", + "summary", + "title", + "label", + "body", + "description", + "reason", + "point", + ) + for key in preferred_keys: + text = self._extract_outline_text(value.get(key)) + if text: + return text + for item in value.values(): + text = self._extract_outline_text(item) + if text: + return text + return "" + if isinstance(value, list): + parts = [self._extract_outline_text(item) for item in value] + return " ".join(part for part in parts if part) + return " ".join(str(value).strip().split()) + + def _normalize_outline_points(self, value: Any) -> List[str]: + if isinstance(value, list): + items = [self._extract_outline_text(item) for item in value] + else: + items = [self._extract_outline_text(value)] + return [item for item in items if item] + + def _normalize_pagecontent_items(self, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + normalized: List[Dict[str, Any]] = [] + for raw in items: + item = copy.deepcopy(raw) + if "title" in item: + item["title"] = self._extract_outline_text(item.get("title")) + if "layout_description" in item: + item["layout_description"] = self._extract_outline_text(item.get("layout_description")) + if "key_points" in item: + item["key_points"] = self._normalize_outline_points(item.get("key_points")) + if "asset_ref" in item: + asset_ref = item.get("asset_ref") + if isinstance(asset_ref, list): + normalized_refs = self._normalize_outline_points(asset_ref) + item["asset_ref"] = normalized_refs[0] if normalized_refs else None + else: + normalized_ref = self._extract_outline_text(asset_ref) + item["asset_ref"] = normalized_ref or None + normalized.append(item) + return normalized diff --git a/fastapi_app/services/paper2video_service.py b/fastapi_app/services/paper2video_service.py index 4d7c2182..fc8a9298 100644 --- a/fastapi_app/services/paper2video_service.py +++ b/fastapi_app/services/paper2video_service.py @@ -22,8 +22,9 @@ from fastapi import HTTPException, Request, UploadFile +from fastapi_app.config import settings from fastapi_app.schemas import GenerateSubtitleResponse, GenerateVideoResponse -from fastapi_app.services.managed_api_service import resolve_llm_credentials +from fastapi_app.services.managed_api_service import resolve_llm_credentials, resolve_model_name from fastapi_app.utils import _to_outputs_url, get_outputs_root, resolve_outputs_path from fastapi_app.workflow_adapters.wa_paper2video import ( run_paper2video_generate_subtitle_wf_api, @@ -141,6 +142,16 @@ async def run_generate_subtitle( api_key, scope="paper2video", ) + model = resolve_model_name( + model, + managed_default=settings.PAPER2VIDEO_DEFAULT_MODEL, + fallback_default="gpt-4o", + ) + tts_model = resolve_model_name( + tts_model, + managed_default=settings.PAPER2VIDEO_TTS_MODEL, + fallback_default="cosyvoice-v3-flash", + ) run_dir = self._create_timestamp_run_dir(email) input_dir = run_dir / "input" @@ -175,6 +186,11 @@ async def run_generate_subtitle( else: pdf_path = input_path log.info("[Paper2VideoService] using PDF for workflow: %s", pdf_path) + talking_model = resolve_model_name( + talking_model, + managed_default=settings.PAPER2VIDEO_TALKING_MODEL, + fallback_default="liveportrait", + ) or "liveportrait" talking_model = self._normalize_talking_model(talking_model) # 可选:数字人头像(上传文件优先;否则使用系统预设 avatar_preset) diff --git a/fastapi_app/services/pdf2ppt_service.py b/fastapi_app/services/pdf2ppt_service.py index fb689ba8..c38448c5 100644 --- a/fastapi_app/services/pdf2ppt_service.py +++ b/fastapi_app/services/pdf2ppt_service.py @@ -6,11 +6,13 @@ import fitz from fastapi import File, UploadFile, HTTPException +from fastapi_app.config import settings from fastapi_app.schemas import Paper2PPTRequest from fastapi_app.interprocess_lock import AsyncInterProcessSemaphore from fastapi_app.services.managed_api_service import ( resolve_image_generation_credentials, resolve_llm_credentials, + resolve_model_name, ) from fastapi_app.workflow_adapters.wa_pdf2ppt import run_pdf2ppt_wf_api from dataflow_agent.utils import get_project_root @@ -73,6 +75,14 @@ async def generate_ppt( api_key, scope="pdf2ppt", ) + model = resolve_model_name( + model, + managed_default=settings.PDF2PPT_DEFAULT_MODEL, + ) + gen_fig_model = resolve_model_name( + gen_fig_model, + managed_default=settings.PDF2PPT_DEFAULT_IMAGE_MODEL, + ) # 0.5 如果启用 AI 增强,必须校验 API 配置 if use_ai_edit: if not resolved_chat_api_url or not resolved_api_key: diff --git a/fastapi_app/services/rebuttal_service.py b/fastapi_app/services/rebuttal_service.py index 547691b0..d9b64acb 100644 --- a/fastapi_app/services/rebuttal_service.py +++ b/fastapi_app/services/rebuttal_service.py @@ -19,7 +19,8 @@ _fix_json_escapes, ) from dataflow_agent.logger import get_logger -from fastapi_app.services.managed_api_service import resolve_llm_credentials +from fastapi_app.config import settings +from fastapi_app.services.managed_api_service import resolve_llm_credentials, resolve_model_name _CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -54,6 +55,11 @@ def init_llm_client(api_key: str, chat_api_url: str = None, provider: str = None """ global llm_client chat_api_url, api_key = resolve_llm_credentials(chat_api_url, api_key, scope="paper2rebuttal") + model = resolve_model_name( + model, + managed_default=settings.PAPER2REBUTTAL_DEFAULT_MODEL, + fallback_default="gpt-4o", + ) if not chat_api_url: raise ValueError("chat_api_url is required; URL and API key are passed from frontend.") diff --git a/fastapi_app/workflow_adapters/wa_paper2ppt.py b/fastapi_app/workflow_adapters/wa_paper2ppt.py index f9254195..ebbf0595 100644 --- a/fastapi_app/workflow_adapters/wa_paper2ppt.py +++ b/fastapi_app/workflow_adapters/wa_paper2ppt.py @@ -248,6 +248,15 @@ async def run_paper2page_content_wf_api(req: Paper2PPTRequest, result_path: Path final_state: Paper2FigureState = await run_workflow(workflow_name, state) # 提取结果 pagecontent = _state_get(final_state, "pagecontent", []) or [] + outline_generation_error = _state_get(final_state, "outline_generation_error", "") or "" + if not isinstance(pagecontent, list): + log.warning( + "[paper2page_content_wf_api] invalid pagecontent payload type=%s, coercing to empty list", + type(pagecontent).__name__, + ) + if not outline_generation_error and isinstance(pagecontent, dict): + outline_generation_error = _state_get(final_state, "error", "") or pagecontent.get("error", "") + pagecontent = [] log.critical(f"[paper2page_content_wf_api] pagecontent={pagecontent}") result_path = _state_get(final_state, "result_path", "") or str(result_root) @@ -256,6 +265,7 @@ async def run_paper2page_content_wf_api(req: Paper2PPTRequest, result_path: Path "success": True, "pagecontent": pagecontent, "result_path": result_path, + "error": outline_generation_error, } return Paper2PPTResponse(**resp_data) diff --git a/frontend-workflow/.env.example b/frontend-workflow/.env.example index eec91647..0215334c 100644 --- a/frontend-workflow/.env.example +++ b/frontend-workflow/.env.example @@ -1,6 +1,9 @@ # =========================================== -# Internal API Configuration +# Frontend Example (Advanced / Fine-grained) # =========================================== +# 如果你只想要少量默认源和少量模型,请优先使用: +# frontend-workflow/.env.simple.example +# 当前这个 .env.example 是“细粒度 / 高级模式”示例。 # 本文件只负责前端可见配置。 # 不要把后端业务 key(例如 OCR / MinerU / Supabase service role / 各类第三方服务密钥) # 再重复写进 deploy profile。 diff --git a/frontend-workflow/.env.simple.example b/frontend-workflow/.env.simple.example new file mode 100644 index 00000000..e02a6876 --- /dev/null +++ b/frontend-workflow/.env.simple.example @@ -0,0 +1,40 @@ +# =========================================== +# Paper2Any Frontend - Simple Mode Example +# =========================================== +# 目标: +# - 前端只展示少量默认源和少量默认模型 +# - 用户不需要理解几十个模型名 + +VITE_API_KEY=your-backend-api-key +VITE_API_BASE_URL= + +# 前端默认展示的主源 +VITE_DEFAULT_LLM_API_URL=http://123.129.219.111:3000/v1 +VITE_LLM_API_URLS=http://123.129.219.111:3000/v1,https://api.ikuncode.cc/v1 +VITE_DEFAULT_LLM_MODEL=gpt-4o +VITE_LLM_VERIFY_TIMEOUT_MS=30000 + +# 各页面只保留少量推荐值 +VITE_PAPER2FIGURE_MODEL_MODEL_ARCH=gemini-3-pro-image-preview +VITE_PAPER2FIGURE_MODEL_EXP_DATA=gemini-3-pro-image-preview +VITE_PAPER2FIGURE_MODEL_TECH_ROUTE=gpt-4o + +VITE_PAPER2PPT_MODEL=gpt-4o,deepseek-v3.2 +VITE_PAPER2PPT_GEN_FIG_MODEL=gemini-3-pro-image-preview + +VITE_PDF2PPT_GEN_FIG_MODEL=gemini-3-pro-image-preview +VITE_IMAGE2PPT_GEN_FIG_MODEL=gemini-3-pro-image-preview +VITE_IMAGE2PPT_USE_AI_EDIT_DEFAULT=false + +VITE_PPT2POLISH_MODEL=gpt-4o,deepseek-v3.2 +VITE_PPT2POLISH_GEN_FIG_MODEL=gemini-3-pro-image-preview + +VITE_PAPER2DRAWIO_MODEL=gpt-4o +VITE_PAPER2DRAWIO_IMAGE_MODEL=gemini-3-pro-image-preview +VITE_IMAGE2DRAWIO_GEN_FIG_MODEL=gemini-3-pro-image-preview +VITE_IMAGE2DRAWIO_VLM_MODEL=qwen-vl-ocr-2025-11-20 + +VITE_PAPER2REBUTTAL_MODEL=gpt-4o + +VITE_SUPABASE_URL=https://your-project.supabase.co +VITE_SUPABASE_ANON_KEY=your-anon-key diff --git a/frontend-workflow/index.html b/frontend-workflow/index.html index 3da8207b..f3bf7a22 100644 --- a/frontend-workflow/index.html +++ b/frontend-workflow/index.html @@ -2,7 +2,9 @@ - + + + Paper2Any diff --git a/frontend-workflow/package-lock.json b/frontend-workflow/package-lock.json index 2589b264..9d012d45 100644 --- a/frontend-workflow/package-lock.json +++ b/frontend-workflow/package-lock.json @@ -15,6 +15,7 @@ "i18next-browser-languagedetector": "^8.2.0", "lucide-react": "^0.294.0", "mermaid": "^10.9.5", + "pptxgenjs": "^4.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-drawio": "^1.0.7", @@ -2262,6 +2263,11 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -3089,6 +3095,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, "node_modules/i18next": { "version": "25.7.4", "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz", @@ -3150,6 +3161,30 @@ "node": ">=0.10.0" } }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -3273,6 +3308,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/jiti": { "version": "1.21.7", "dev": true, @@ -3307,6 +3347,17 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/katex": { "version": "0.16.28", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", @@ -3352,6 +3403,14 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "dev": true, @@ -5905,6 +5964,11 @@ "node": ">= 6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -6110,6 +6174,35 @@ "dev": true, "license": "MIT" }, + "node_modules/pptxgenjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz", + "integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==", + "dependencies": { + "@types/node": "^22.8.1", + "https": "^1.0.0", + "image-size": "^1.2.1", + "jszip": "^3.10.1" + } + }, + "node_modules/pptxgenjs/node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/pptxgenjs/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -6120,6 +6213,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -6264,6 +6365,20 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "node_modules/readdirp": { "version": "3.6.0", "dev": true, @@ -6938,6 +7053,11 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6959,6 +7079,11 @@ "semver": "bin/semver.js" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/source-map-js": { "version": "1.2.1", "dev": true, @@ -6977,6 +7102,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -7368,7 +7501,6 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/uuid": { diff --git a/frontend-workflow/package.json b/frontend-workflow/package.json index a1b65fce..4fdb81e5 100644 --- a/frontend-workflow/package.json +++ b/frontend-workflow/package.json @@ -15,6 +15,7 @@ "i18next-browser-languagedetector": "^8.2.0", "lucide-react": "^0.294.0", "mermaid": "^10.9.5", + "pptxgenjs": "^4.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-drawio": "^1.0.7", diff --git a/frontend-workflow/public/paper2any-favicon.png b/frontend-workflow/public/paper2any-favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a7876acfe50ebc4ec0038a5e8869c0db413e170e GIT binary patch literal 367682 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

&tw0T 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


7{PHv{pj-P085< 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%0Dh0^jfXuQ=5-iuS*oF=ChE-3gU(E`6?c5)-Du>c zUI@u}(;BTn&)Q{gb67JbQDsKk!bfnV%?(nPfvN3bU!B)pFnF115@XcdT)x4v3@e~n zqo`B48Lp!Us8;xP$P`r>RI5N8A%{DtW#WeBZput zz@QSn9Tqm8QYM1At(ir+*cNz5%cYe`Qj1RJO~Pwsa+^x>5ITcm4cEKxvO#Hv)cz{k zy}5J|yi}qIr+gQqkN|i#XrH$yUAIk_T{6?{I_wFHXgCz%Z^v>A31}rkfztp=&3u+fCs=8njf8vkg z(IFqM+*k*vf9os|s7q-x-}&gslC5yIc3GP@rLCXwb2{-Q&tZR#Nwed9=&4IRM$;}4 z{)I~bx(b?)XD{yV9viXhd?{b_D8p5TTx688fo$l26LM|kE_#%321T5U5`(5+Kb&4y zC&t=^ZvKtVv7Arj&<(ze9B-N~Jf>&n2n`Vyg(;rpN`A`LLwk-M=6kI|W1JS*o`%*n zB1DbN!!nz5P~&)Z(251U!)5R3qCB=OyE0~_2yvUbhnMjGL8_WVdGfAX;Zb(F^gHht z0Q)`XqxQo>I0qIAIOO+c$f}?VVyhu&IyTw~El+%UvmI1;o8;q|Qbq*o? z$+K!x##nO;rFa~khf+ss4Fn{k5pX?-d@?>jwQ*tk5^SA9_XyTXD23QZeuu{ehPruJWPkZWn}Vdtb@$ zX{~bR5~@1s>o$z@&F0lg7n1#$Z*YCByYNYMMJnOIRD%&GAcRvP6gnYS`R?=QxqAgH zC5uR+;*l=>=&iwYy?1hrJ~sYR4wVE)MGqX`=q`)@HP1_MMQN!}5{#()^TbT^@a`XB zL!SzZ;##AZ_H#0Z$MVN^C^TO&$5*`nZE;lQVSL3woVYvyk)|Px7r!S8p`*=C+U`NGm~#>zD*mWCM)H3!GKSPRm1Z*9E9L>9PAOE8NBFmhw(m6vtk+llua? z1X8^=N;uazx$J#&HhM8KSesQJ`)K1L%!8i0EJaN?GYXcFN?zPTs!oZ_#I;D2HdQPG z4wa^i4yRAF7}KoiC=yLi-7EJQrzOPB+ud>d zASo(cP&?*V_9CqGZ8(^l1b>!6yNFPXGpt}C!_WDR@Az0lWpy*S1G6Jibp~=gCe0io zo7Q3!`nGN?Hjzxw>Fak+aOG(9``Vp%cWMR>6_P0e_h_?>2p<_NgArW1Du>;Y;S7^Hd(O3G!E>X8QI?}JY+AeKMj8 z9ZU@v%PYx3mWeARqS9~2t-G+k`+Yi_{X|=bE?XHr*cn)GR!m#vNv{b`sP!D=b!AAm zk^<2CeYu!v!%f*aX&>5&O(p$`m0I-)XQiECKh%d3IkRx z06ddSp(@@@;PX+BzFWdU3yCFiy>{(wrqN_U+)gtoF}->e4z7n92TJixm}s#{w1`vS zwRVwocq`Sg6-(6Fil0X7BG)H7=gd^(ZWDQ*4NjCbtf168eV{rbd1YP7>F=zWjg5fil7+2nWT;H>G% zy{59QZr!=96~fESu1%~M2d*iA*9zdZ{&{*8qvQ&RUco915ul=gYxJN8-fSdJ4d95l zs5m*t1t_O3&RcFp9+i2UVhfo9HPE(d?zFJ)nr6ogwt3--Y8CRG8NE4L6VJ7xqxIzO zct{9P4qUmv<*<#9)gW2dlu3^oJ_r24?Fy{J+LMDv&p1Lop#_~3mAMo4Y=)TMFihcw z3okwgqnu)pz#$kuTlPdGW_qUGLWC|VeBErR6yqZB5V1NstS1EN_wIN$srh%8Z*OpY zNDTXd0SSI5=rF`eW)Sd zUcjP|!#GJ>M+ZAZn6m1N_sZl`5l+Fz!IJZ0>TWSyomrm1GLx=MV++ILN44BvO|Y8>Wl*8WWh0o)x3Hy;5Z?o1{WA_O)HPU-h&SEsbJpEa21C`6A6k(dggt| z#pzrQo!%?#ON6j5OtG`@**hoG4J7I0dxG*C4^_uA+?H;_WuA>_IQGF-maOs+KZc>x zZpRU0#t#msY&TD^b!1E3w*ElNDuFmh96vYF-h-c|Kvu=*lcAxHUCB{TAGrMwUPj)! zr!}R4tw$RJR=z0kO?x(+!1WNpfg2iZI|=yS$qgWTs)OAO0CQuuTALC3z(yY-K(bK7<7hGD250*LG7WF;OQW3K%H~;$>Wz zGmm1+!avnFg)lm&9-7r>NW;avqiflUMd2YviAr!`%Ik3xb9SwG(!mGcotvX=TCRuH zGNIO@i*WQT*zxx(`Eb`WsKKw2CWCsYfDIp0bBXrgBAhJA1?NjBf{LU{$kW~_bvC#H z_mOA=Va_*#@HroZI*njvC%Yh!k`heBB%~fy93&?n`qoK2HDx}Yr~!IP|KO@wP|-GX zw!ZSvWk$i?L`VAi>I40d$MSSZN))7@+FGSv81)+)u*Ijij?kU&O6i%UGviL4q8+6q z{73si9rDMuxTZLWl!?>(1ZgIE4kbc{%R@;3r+r0kx-JaZYez+<-_u)ow90pT1BPve{ z#FUf~xxKY_rRDp4>0_4$tNZw_mqyV%y_)7mKbmn>tZkL3pA12&gDLZfd4QQX=e35qW$SQUC5ko>nbjc0CFNQD zdY>j(0l$pI8k7G!J9kuJ)C0FS9vY<3u8B|Q!f10K$lc0fNE+Au&YPZS z2>%aP1Y&VXjE0EoV`q~8_|0I;NH+j7>kc^cqMdg^b9d_R^is;%Fr;`UNo%07<{W)k5V&P4{e-upW;_jI ze~~n$lv7h?G!3{CNl$<%hqJxb$}WvcuAZq)87g~-$ppu13cV(vtH&Jung*AG0YviW z$*7V;R`0jHO<2vp-}J4T0RR1}Td&nEDKg!{P*n%CuN&zwJDB5k(BdGSrXUVeF}qt8 zDIN1>rAd>kdVKm^$f=QUMm66L@$n~g`&M|=*<7T5_8%n!!X@x+yE5jeiL74=3LT2u zz8_!fsw~AWCZ=)DiM0vA%y&SWXW)HkpV;r7Ce5nXEm!k&^7SLF+xULck9#AW(Ru9j z%PoRlc&0L#5c*2*8z>)C;5fWj|7_wvykRgo0P@EAGMj`}Bau$dSJLPjKuA}2cDC%4 zQ&1x-Ergl6GQOfJE#HFaE+r&|9jJ3xD*B)-(1PqJ)`0Vq9qj8aMr>~rz!x3WI&0!< zJp80+bIn!p_gUOb*K9YARdo=gY72FcCr4*0_o)sE&4vTdH22X%!qElxKI(h)VeCW24L}S&B!8ADqPsAbM^Z@-*pU%hFmFn?Jxl^0$}`?7N0Jw?W=3i?9^W*WnhqJR+hcBHTLDQ)vv0W8~7U$t1)HtM9ga{?7^K3 z+P|o!m$u1z5iuyETk?T<-^NG1=wUH8`R1gl=tZyWW=r1^(dTsz;CEsWQvEA;-0M=t zKbh=SZ}Z+7BcSx02F;8*OPQ>`imbNqhNmLROm_|35ALOoyxfVa?JDR1?&0LX;7MWj z>V_aZisT|4gW8x59axL$TWR6@GR@Fp26SQ|fcFH-|5$VAw^2-fSK22MS-nW2!+@yZ z8@&jL>-Z2t;4dHg1Lx&*TZ!dSNsGcms&skYCZMXi!^-iYJt!uA1aqxO=?G33HOWa% zWZz=-xkY+Rca@(ErD7ND&CTkYyJQzP3lY(&)zT_Pf-8&i44h=duCvE8enbNbhVMQ8 z7IV?G7StnevQp)ja{g{JG66ofDBlOgti3F- zvDpvj>TNI^DdjMRuuy&@E|F#>#;!-8OsYI_AB!&5J@qa(r2%NVuW^*?t?0wM2t75|Av6>#0!P+fn?b}N~ zi(#=M&@F3d!{%Wa{3@TNO)bj#(!GwkB>jJeISdxgv}?_j7M4>d=D=NE55h8ys4OWA>eymMbtXgIdWE<6uO9g!D?8a+ zeLBl-cmhutXHDrxAlqgj0@=YY?b79DL7}iXsFFzocOi$II#4R!B?I8w<5{aE?8{+l z+GDju!D_P`=PX$C90SocsO3s;FM?i9KjBqz8giU+YYt8)oY)y)CrajOCF${}-aUAr z+*X@@iWrx`$WAn;zgcU=NR@Bf_m(bX5^A-uOQ4A6HERVrd4fbocBq)F61#aiyjJN) z3$a{%ldUL8O}n>A_0=_u0e&Cj+~2+y&*U3J{f0sx)5Cg)TXHE+6ka2Qt{mI52;rHW z#cEt&i_&-e*(mdz9GZpbOOYQEFh96%Z91qAoPe8T)Z06k8N;?7A(wc&BnRpJa4s*i zPkKwfm5UxLCiYpi$XwDI-Cskuft_-22A&*^%j>FQsc1rGb!lny?yYe z9V){%uh%=wbT^XePK-P18L>>l)KQfM1!~)!J0>gfNoKYH%1;dZ7br@o&zTnkQ>xGg~ z=BL@4NZ9~%)%j!0!roZkO#o)Exx#T6EQ(pFt`Vzu8s>*S!(2uZI6E@ycGUYiOeced zv)Xoc%6K2hSz2XhebgC%^b`{u(3^|;Q<2ux@|GmW%VF`l*=+2K0-eWjx;omPuIEzH zTc0Rf52K(HkPl$4-ZQh!W{MLHrb2$x5}*93eR91c41iI_^tQEZZeM5s03ZNKL_t(Z zbQBVtsBC~*aOg!>gnkyQ;4SgLlC9{TJb2+S6so_&jCbh{K2r?!QyK)rI9k?3_6+y4 zgkXZr=H)G2Z62fpZi}v8F$GSWP}1v(s24u})23bc1)9|$lznkz-DgSoU9pm1+tG?{ z23pGR=&RXXkR8qZoBe!x*?9-3V=4YV-^aOsXBN5qkTl2H`gE7yy(vztzg6`?>yBLU z^WJT4Wbi6alukw93p9;ysP|y<%k0Jtb1Uj5*Mc?Kv3Kt8x+hlwZW3h6oy%``7OYEaimvx;&HsPq<&)C0dn-B3f*_8-mZ> zCS+QDA~DXzkm)Y)vcC;K!9Hw0b!3ABgt?EFeeX(Nd9t_AcE0BL{1qoOV$;fc1GDGB zL`_)|qd~!$vhlbp9rSh!p5etHTsd$m@v`1c9Q#V&V#v-#*2IIwdO|9Lq3~>&-_#ev z*SBbA8SX6kG(G$q9Ch^YP$#4O%Tjs*RzD60u^o&6G;CbaZUcT8H2m=U{^sTHO7*>G zxj3{p!jJPy90FM>%hLjw!Sr$d*>X?N{q%qmKZJDRrvs)cqx8llLyVMSA}V-e1RS6Yk`()w`EjM7^uu!(_kxfKmM7JAas+BsB5< z?I&}kNRW8zls0I_*ipsjjH9R38<$zTiypewb89jL4&)BSg=U4>a?|E3w0^X+~g$XAV*qJylzpa8c{1FQPxUbObyz3%~GL#Ho z4ewgotd@-hEutQqB$N?+o%im+#xUNs`<5qfIE(gs__UBhKN;;+u6n-#$RlLE`1 zt{2iATgpJO*4t<@MNm;FLjxo7~BGzut z+86;0i>FY0J9V*F=f2A#r;ZBh*?V@lVT)})BhX%}J2z^p_Q*@@l{%4SU6G16*iK)t z(?=oySHB$m>=ITR{L#tx`D%txH8d&NFut{=RoA$!IUU0l)EF*RW$INKhB9qARFd2{C!V8Dg1e? z=m|T{$GQ-po5b=l;q65(X`MC3@Asi?KV|(BSGZ^;7VGb8CKJurqedlJnXTaa)x@+T zLuUfJmW^R;I)^DyE1Qw(ScE?)rRAnnAvp(Sy zem;kTfifnI4Q+e}dO-Jf3u7*jF0uEq_BddSr`^Ee6igwQwEtbE_ zvY?P+l!q;dSAW47V*joC_CKly>9GEqwjUyR)?*X2GK#LF9CW1n#=1Bi_%nhC)oJ@z zv;BI)&zJXYE`m%>j}MbA0^Fuhf4f?39b9q>e7|V&z1J?I=C5AAy}4+sumV?_j*7=T z&pN;nG*;zcx8L8n{Tpa_Uo9a5QY0q!fanv}IodPr_7xP6V zrdc}y`9@$}xwo`Ta>j#646_vse)EpiBK9afnmKLBuh1oz~b(Ns{kNU@iqwu72RQg^35Iqn~6+K;ID{((9&Q?J*f+{ml#q9hOV1E+nNQxBva48x4sd zxQyu@S^rzaj;dHV9eJWQ{rzvw5c!abGdswGsC*Hrs)lV$L z`IkA^|J@S)rAIg#x+Q8L5pc$%DIWLEMa7bB#e49t@!uc*!^j{cgb3Ry3`QG5AN*mj z)tLd3gyXK7B7im{3N(Lu*?p{1J@y)N-E`x($tcH8c!pJ0p3F7!sDrj%WC9wB zEG4e=Oqu2qFF>DlV$Y%4I^ZL4%{tb7p6Cyw`xCOI^`ro28^9n88E1Y@P8k9)t0OYHl=&wEDD>9d-2VPlApLSPE3Z~ zQOVGKD4sXTG7#&$$*iRj6Gtm%Z<>G9M@DdcTnM>YEL+5rx&z%~#|decVqvYcb&{u(gt|9vK}* z+%gLMI7cLs(HeFV3%$}|wbh|>TEb?A^^sMqaAWnIIcelxXL)WV|4A{m3iEi%f5!o6 zqdn1gRBP|jns^mDnFq!Zk)Swj+p-Oc7P&utgTe}$8R6D?+2%P+^4#mRmiR~jO@PwR z;^{eweK(9BL8>&~r)fV5^sj4~`J31*q?_JESGm5x51FnEy!xb{i)`|5fPQo37jLaa zU>!~yUN5vFV=Il%%DuAosceCK?^uH1)reDA(J5-U#M9{UW`z>|#zJ!%Hwx*wK~{FA z^H+!!Af!K2HH~J5U1oRy-i%p_B`0Q2eeeCC@wCe2>)mk0ysg_z<1Bc#u5HNlNe!k( zb{BDZnL{yWqy9Yd4jgJ2o+wehftiN^JI-eE4TCM$WnZBrnq@GYZ_2^XodrF! zRy^@P%onNw-dJpn3BJlb!vUVCq$gZ@MlEI=a$qq;++k*%{kMw3e+t4U5NHFNf(#~= zU?Xckn(X6xz{yaaTzc>_owbfWk4NVi6>=2@F@C$ifLjk(<88ebc5&eF`MR0ujUr#Y z-tLoOkzCIA_WY6YS10Bd)XW69b-L3<(%j-6! zW)tgi^aCbQB2G|$9x3kEQxm)0VS~REp-9YB35IW9H*s&KgTdS1M%IWr)e(qNSG`Ou=KL=yE|NC+9 zhhgW|8li;tS6dr;xN}%HM5QRC%PAe)G3=A(Q1rJA`>Himre)1J;)c5-=%0y}ubY05 zsI18`J8%0UwxnYnBbQl(4oaO37#qitynPHf4s-4gcT886{T;5SKWU2&k;v3)m4PHh z@>LLQW#QYbv1rp~d>}E3)R{+|(`%TO3sPB~*FkdeU=BxaU$|rsMwnxuEK4d)aPV{# zA@UlJ+i;PNwMVERi`IvZ?aQ~y&7hb6%KJ>oFO9fY2z_J%p8_p zgVh)3m=G^L`m-d^D>gZlM0*8n)b?kzVQ|KjW$%Z1nO``dr+MLFcoU<#&o97p$@z@M zQL)!xLD-Ivd%LFQYY(GKW6(B~zvszFPd$%Cwu`PE))crz*K%Mw4lrsb#pQg0@}@g8 zUOxbTLT+jQ%d6YUzC7o@>i0#df|_NEnP6;h?uX6P_Pl+p==Pd??#vG=4Sodt&iIBp zVf9BpTBqjnhI{KBUIIP7sc#=G-bKc1r?WAguO3HHxtI1@YJs&unAOIbBtQ)cql*ZY zC--K?Ji<^=67|<^i>h;cerD}~#-Mhb9@n8DafdCu9lDAHFCG~4dv$#td$;5LU^&Ud zqajLe_3A};sfa$luuVhE4xh8C(+T#NNUsX#lac+gAJh6^41!_ecmXxo18e?8?lA7T z^7*aM-Oo0917AhWv#$=$IMh3c0Vn6oHa1#}>%|~BBH=}zFU7@}@}dV!@ZE&aqI^FA zIml4Q4u>DXO?~3+>H|WI8EwVSvXjE;(!!}`Z=#lPLnxB~L5s|tw0y`}$8M7USZhx% zn{!o2_Q}VNLdl2Mc7o1a553dDarrSefYQpTI9{?w@T`JN7LB(f{8{Y3 z4ubh)Jmcz1)VCqm`-#CGvX?}^+Uu_gm96HJK4?9y)d-LZ`5z41brOe8Qu(@2F5CR= z=<1vmRoS?aEOBCt{+e{c>@00Bx4Pw>RpF_Q0Am`mEQ@jF5bnvYi^B0Gn}(lOeKFbW zqN(TFNd^le{3VVBd#C#!npzt1L-m_UQshg<7QlbKi|@m=%7J zP#`P3 zC{SEwdFkN*kks~lfjvHf3O?)0HZcx6r*SC-?58;mNajFG{Xfnm&>Y3n@y?S6M0zYJQ$ z2;6C#5_cI;rK2tRQ9O)8hdlq!)tMyOal|l`i;N&~{|nn)#K5nG2ic02^t&nt5O~m8 z$1R`)oZ0X-k6WX$pf>_7Po(T}omj-Fy9&viuUDVhjqV4XkfKU#2;TS}qdzVPR~cCq zp?VP^edQCmhmMm>+VrdpHOl$e{OiQ%q^Ge`8kMD@e+G^r!~0$gJq1a&OHPF1j9Nj8 zWGxLcjBuWgc=&hsHYAP^{!DLOrN%CE3=WZ?{FcBrX-}{dt)2Fj$bfiS+IRicIl~R+ zb9N=$R|dii%*j(iAFW1O4z@i8xFD?iOIiJjS)vjJRh6ONDO|H0yJ39qO>Vn$fbG)5 z_aL%cEPdGCuh~=tve9w3R9L@eWJIXoi^cAI$~^fgpBdWP{rgy4_)h78$Z~T=E*Le@ zo9Bccne*;&?AW9Ue$3I-j_}^%j|{V-ND>Eksu7~XIzaZfxz&`T>u}{$o&&$!&kV-f z?GQc%tB1$BYoY}CU!%7sy_%MrxUCbM^+oBkm8zEx3vk3|V$0?n?!1K#_0GoFkZ`pU z@LCZdk$_+PscO)kdi%{0+^}yUff(-&8b`BlvG8bC90;ZBg_IFDc<+YAr=I zHtARcx+1VznIOGST^beg%WBQ$kXH&xZn5(DX#IA-_k))aceGEnJPtS5Sdy=tLfegs zkBECGXo%>=O3wPUW#(*Wx*;D`gLC>MJL2?Vi>&hyCzn`hUlY6;t*o*BX&Wjj5%;z?WtombcR^7 zIdi3ayA&Y!`@X*1Ayifq`zjAW2&phnYp1M6C!r!8))KFcqc(!W3B_2Q)J}1$BmOw#_2gRyo?ZIX0m6EN0Uy< zKGZ=m*VQR~m^NPqymHz@yeS@A%Sushk+vX+(~lYMxb{@;3i=THD)2|i;sI&VKicKY zcMwb}sxcz-!zNb5V*(kD?rdk$4^g_Z&Dq4Ys~`vG{$# zHtz2zrKV#!n^0+1B0@enp><2TIB&TC;)$3ejj2PFjh+(kN|7Vl+NHUQ-1z!7Yb#-=p?9HmQ1>|<_ zvzXJPHh`^YfM`2vj~)YL%5YA(&(7&)j)QEhnvS}Iw{(K{!eMX3T-KQaebNO_-e2B* zo5;zO*&5{PGlMdpK{0^Qs)LlJrD&j%#Jx#Z0g!(5=A7nEM?<-CZQMn%K7Mhgr&Kzk zxVh>^-y?8TC93n2B~;X~{_Qn3`G>p6Q+YCdXE476Ir>!jF4!!CcMq>+grIwrWm>Y( z=!C!#o5+xE`_>ANQ)v+uHaTx7I^Ys6P6vFhvP*G=5Ram*BCCRv7X%y?P51wmi=C)t zm#x8l)@Xvvp4AlA(y7axtb-@*D(|UV@kVL$w}gwSHU&P`nEAGEM8yqgz$) zRr5V)69@9%E}Vq;liZjl)e2=z&s{U(UYM0Ha%CxynZKVFCfua;i$h`ROktMxGu_Wyg8W zT>Cxk0_J+tbrYW_D+;ui`dnCIuAWi+VX3IKI$e}7NPKJS^59!Ut%UYdWP&4hn zxKtS6!lh?>TP6i*zFuf`Ki6D!5suzq$EDPyP|AS9TuFGSdHd`vazehlgL4mz^yE1i zw}TxKaDnN_nmv749<>sStj_REmeg%wss=JSerLkc;C?Gak91THkSog`@C-$J9Q3mM zH(P9$P}Z6DoEo3L4+WF%cB>`;@a@@LPfPvT|L8fPWz3w|!?^CEQ;VSd%q8Q)OMvML z-^X!_;3+Xy${;-6trSyWV14;W;<*Yg7Dsa@LLhYDmKKidr4t>WTS`ADh~!?BrmLp(%cNZu*G#E$xV*SQsKQN7$Tg-2!-@W~`Apkx`svDeuB|f~g&FFi ztAnAEtZr)C)C7yCOIk~~uT-O{eaFNPYb7;EFwITDQz>91iy1CUla8fBAKWLOZf%x~ zJ@#H&A86Z%HS*$J1*eX*tA4~EH-l8b-xDqcZ9wCG4}-%rCMvfoJK_B^i;??qyf!j+ zX6E-SZ?U)-{WK3h$6eIqrkoAIyOo7hvPI27_{wkRw+RdP8}(1t>8k7yN9CJi9PJe5 zidksJdWl}escq?aHEzwxA!}Zem|Cq~EaMwPrCtOnsxQ|mOXXj@s? ze&QI(>5eLAA)H3bxUoYvkcf zd0VNllvE4K=1DK6kiK3_6eBnxJLlFL6F1(m{2Gu{lj6kEtUYZ%X`3`2XKsH0MNP;$ zOvNZ}@bAE4SY;bA%fC;ZdckAB!jZF97l%qAbc7=S_8VbHKt0>ikL*+zu-Wp|*To3% zH!GXR_$U4}%;x^B{#bi*rf%H)p!FHG-e-R}C@Xo~*^l#cEBtJ}fB(mQi%@^vehXEE z1=&8}g95XI^mdMd`+PYUdseF0?H_}k2uB9>CrN7Y_x-YyWe zUyy~>6!2mRdIC3^x~eaM#Z?^hn%_y`;y{Btu<@GmAx15DI&gaJTUEq9n6e#P5L~ML zp^BuA$2|~^X<_X{3#Rp6@5z|pdZk!qrE9(FXLFOWzYZnV?5K5hDv7ah2H$KU^;U`g z(^={?=C~QheW}Q18ntoGnW@UR*{8G=i>Eq5Ndat&D@g|g z^Fe>4ChFmc2|vD3HuCjxiZioPey2A052R}b#=MH|p^6JpspjGJ_DIuC$OF|`NaxQQgyPo$RJ zjVg21*kP1W@_tUNePohJ-p z5P;U_WwUI*hAc{;WaLXR;+s_tPs=FJJAisU0K1jc@@jA`cQ{c`Tab_?+D5| zUpDLe=UVY8he5E!z{P)D-eZw`o!4Sa55y{7t*H_e_@-n0*h{UFo`O{Q%Bc;45Fxkx zN^tiY<@D=#{ZNX=7PYVQX|&b1$r&m%RlT+41&xt{IY?PmdLYNc(6S^Z&I}m`R^7A? z>Q-EJFWxwHgo$^IqfETA5APr1$Q}2`Q>(PEzv!Fu;O-_S)PA#?JhDAjeRGyN=xP{o zSm~!T&mWZPz!Vri{p=ajuCkj+wkuU?Z0$<;`FI1DFIrcShSWnAGP6JLUNWkFL60TG&dI5JFM79#*olFf&+o{jf)}U-;(4lEX`o9HM zUDrIxjuQ@mjS1?rn{)5zEN4X#3}PXx`J3wtKTxnkaw?Y@Vpt$yRnRgm_U=ei!V5Bv zLH~2IPFYY33dD=jOyE%QW`0j3Hq7AS-*RkAYu z#n{-H)UJ8oFOf#Pe%9TGC=b@ zpP&D@I=9`(k{kx=p@0Vo<7H3>Lc>wA(J9lc@_C^ziGVPszGSKfXb+xtxHI3?2q~HA(HcN+hVap>wil<)3kv8FMkMogP3w222%15;&__w zHgPM8h6+%{hoND|qMW#HJ0zoU-K+{RKi;LqW2DhwX`?3EI%3O=2yo%8tyk43u5Ym} zAkl|Zm%LLJ^PkV}k>%d(6jJZwE!tN|@@aD$AX)i!{>1dRLa}KZtOooZU|TK}h9_-P z=AEV@w<03ZNKL_t)(j8Q4%YHSGc4<4dh z-<-_nTfkCqTlXLf{AVuKsCTJ)$ij24&S6@=ypB3wVA(==V%U2?GL2jj2E|0~x^s=% z{%}f1J$0`6TDIl-{^#l0l(HY0j>{iu>oz&nIB`5N2h4@6-Ygldu~?Cpl(kB(DwUaS zpn87U1rDdYAK2hzJTkk>Q0b za7!m3Yiq~M>vEgHCF~f7R~PjYYKfWFF3Pi7)L|jsmhkgiW0g=t{^?08QC*V{sm;~q zB%Uh%V;N0g4SZ{gh-4`Rs@+5e$YQ@wr%$KKNz*IE91O{E)c@8EBrch?8&=aFx8dubOG*O)>*#u6U*2sQ9Gc zsvOa_3m)EvlTg9DAg>Z=nACY653f^B^mZ!ADm$zgY|>L%>HFzvyLXJfl&vD$nW?RP%m0p- z_P~ATa1>j1?vz`&kA()Gt`j-^^`*gWB1d>QmJQ<1N^~W`qBj@AIazW8Pi2sOgH|1M zQU!NenlPdcDVXxth&5%SZbj39i3QV8n;&{I0h4rZM73W^bvzS+?mt_?6l+|PTq1SJ zdprHZ3-pFUP9BPfUbo~Itz#y|kD_sI+zp|Sxsej&M0w&Q8=eo;B{Pf4A~&kpcRqdh ziIO8$oKaQEKA=`(T$?5N;@3SG>eR5c203px3yTJfXog2!Khze3{1GF0%``Pod$Tq- zfWKpd1?9~qme*{+c2~eehJk&ZUWu?J`GtKXkW^->Gpb#tTfG$W#(DrwK(W74 zM`ipV2fi3lz92DQTHb7Dd1xHhdnXBL6MWB%E_{@MHBQERM~S{`!DkA8ACGng*m=V8 zxMc0lR6eiy->aI<`2z1Y{(||0g6~=I=66QzkYHOW#C}jn%&y<*@O)~Sdg0r%KTyRP zWipr_R)YU1Z5pu@u_0T&C9qP86W#eWid=Y}t3}hk>j?AIu2{77@$PKi!b;_5K-~Vi zERHBr!-P0BbqCRh(?TI~7N?astmcY&8T*8Zo8(L)4pYn>46;HjjhA2d|5fkT+!7Pa zei@$fP-pq|Q4%bfG>itXQzWy}19s5aa`6Uvz+=3;%gVT7&|}P3*6~XYQN4di!k_>D zIk<*Y!k=S7q0D$2@GE*MrvgXys>sa3DIM3C2hYK3bg_wlVJ|~IexESdYJFXI z**&sz&ui=*nZ3MR9KWq8`jIn5iKd@GsMGpSJs4x#904`kr`ursBlFm1#{dXeW#yRj zQFV@CMx$&{mL0iq9}Zxj{Aijnyj1x8oBHFQzl-zV6p5+d=2%Wvq3Z(OpG1q_HmTW_ z)I57yh-H5L@l7QB^6l8^MnKKC$5fgdYl{G^RF667hWDUe=*-koe4c@i8GAk!+J}{Y zl8+;Q-JWS#GhlZV_tsA8gF~(L3MlYp*+ugDq{FK>&!#VG#yUbjKjb30*nUhgyMA1n zua;z}kn=**0r{Tn3%6soU?_a(%8=(VYOh}_;niV%;R|{FzTe7UTD|=0XBxJw%v}W> z{f&7dBsWAF6!Lw{19sMWQFM6A7Wf?ctVlHIAoSu^(c-v>1ni}HNIP!~!qN^$j>eg> zZXBY&QO#n((H(3qVvHvdAz8}M1bTsH!9E46?7LGa5od~xtx><-YDbXNdj%`eP};<% z%?+OW`fKS*@FHTkl_JBxI#j)6m$F(GW`Do9kK$5WHoo5eIPY7%oyJ?qO9eU2(KvoE%KCvn zGlqo|sX`}O)P^To-ydN7j1=b{3N($Is~C{pOaBn5PsJVuixaT8_J}dwL3=DJ_9oSf zLkn-;Ng=!u3UzXzW!{5!D#Bk;if5-5&>a;HZP$SU)CnVQPkZNYXuA`!L~8vu6HWN% zb^c=B1DM(7V*9K;i#~oBj6Oaqrka17(12O0eqOOTNS~Y758EO&gE~Fdqwmm#>x7ye zm4{EQdH!cPF~mzV<5T|>=`jBK9)apIhrxJ`;$p@LZNX6?YoQow+2TS&Shc~JzkI+D zX%9J2fj*^Io#B|9;ipZX*tO8L%zbaf-ipN$$EX{XL}nj<9tUL!tvD1al=w`>YeVJg zQgM5A-D%6wVc-325;hg&RH#C4Cpn5ALAX*1oh1sN1lLvsKv`%l*KX~Q@?y=ErfU-= zoD!&F;zsudIhMN29c#ykreaN|^PL$w#*4&$o!W@>7M7mpNW~nrO2- zYuX@fVkp~44g-{YwlwC6*{0S_u)7R$x|WKzEzE@8Uk)nb`SDY+qWMS;^ubE$m~$)@ zmB&AR4l-VWQJ|e^=E(TmwLcMsJHzS@x(fhnZ{kiVh537HaKvp!&{1%o&j-!*PBYHWKT6(z1UrzoFis zD1twuvszJ5bfP5VX;zmSe&SE}dpUsw(rLBTssXFStBE^w}x;?JOry*vc9+KW>LA^PAy~c!-H6G)S zg)@r6#1GY6lsP!p?0FPR>%xiJqyjg~stsz3>vgSk5qZ`IK)xi{kRZbFZl<4!^cJe_ z(wSq8#>-)oTF-}-b*bYC0EZ}B?o6ftANZQ*EalkyMf#Qf%%68f4H;scUBd*fd~AcA zSjeVrSHiri>$vM#leL{GCW^@m8hrk9w1kB>=ZROg^%E&vC)8s_bPs@0dnfqij_D2x zf7Yai7CXlhBWmVpoj^I_Sl1mQqAgOcOw4CgMsgJ%mITrPc1Qg z!*{nNthuoewQMWdFcsdW4fJBWwAn&>GfhPd36v!N`V#Pd^Ti^XgOj0>ZuJ9a0z`*5_kTK^@sO-;gT#aH~tlz<=CNyrL)A6gZ2GV~Z21+yT)? z9#u9I#E!Tedn&3sn*SE0qK)68=A`gVUML0%A5$~wEjQ9G_Uy>F?1)5rQBmbpL&~WW zTbo&w8wO>EUFjp79OC{Bh09VljoEU0nvyCWYKK3(oUToHr3?3H22xT4zv$T=gU<~e z_c5Ld1=#w06T*l1GHa*wQ((F23m_9=@Bc1qeF;3;f|Hpy)j|Eh023j7f|{N9b)FAf zS)RO4cVkpY+OQ|pTLv27{tQ5V{KO(7!N^Vjp9V9>W|Yu!Yt+& zsoGbA_uW8tDWW4Jr;nvY@Qthp6nGF%qr%>6bY21iG>@R=9iU*WP}2tO8TyIdw^jdf zbtXBIV=)ZX!hpp4U$`nP4sv!c8VzJs#$bPIARpDU>|B5E-ezXQ-2p0SS1yi9%CmI! zr(aVeJbk!LnDC!WIqwkLqN*5Jc1#nAb`0f3il^1h2rrxbnc^-yf#Z9>oREmxD$>>| zYZ+fDqZGM2zwrQx8xb!knWokOuLZ4WpA0H8UdGdsA28Mi$JXylzij}la4?k8{VKlf zk1wrpMoHX!?6U?I@{MXPO0jAbCdrMZ#49g7=IQ?16^_i$a~b51c6XlZ z9JJW5q&hu1I@mc|OSZi3MO3;VZCNysVRfR_>DQQ)?mh>d`!G@A9Y?u>T`0bqn|j$? z=9wZT1z9#Ct6n6HppV#Z`_&O?!Xc2dKh=~8euTa|Z*VN)XY?DVVdBBU%5L6h+HaZm zvff6ZqvtQ*TYCF~sg%bxo^J7#1=a$_p-N0+VUQJ1g={_d+*c}4nzTJS{NXx{`4Il( z8V{&lPXql)5vtT&dc`TJ(s-or*D#*J=ABq$gyOfZ2=6Dlj7cvJnd*Z zX*sf}UOE9x#LFK(aTt=zj{SM{`3KQzuuGi78q`8>CNaX>8mx3DL<#L)AmoC>j$AQ% z1vE)Pj_{Rq^7xl4Aq^E+SLKWAkiUwDWEf1mxCW;Dw)Wr_j!b9v$C?m*92vMyLbrlf z)=-(JN*^jhyC`dq#&riac6refwxB%G(Tc5|Xbz6zcfNA;MLLGXEz$wb*vX^YMSB=! z52*&i!pZ}6z%CR)iBZJwJyhS3)}}a`kHx8^q1MoxiY==zvs3Q_-&mBbyeL>f`vi+# z<%Q%r3-`KuGQ8WeXXbF{$&%XHR9VUe>l{_Q$iE8Urko}4@8VW@L{x84hv(Zo} zH&4IT*RKH~w0+yERH|7|(-}|2utsDVnkkM;dY&?SQSudvV){M_z?LW86YiXaTj0*e zyhV}-Mx`%L>J~eKGo{J}Cfej{M9%E$`EW;nLoK|b2N*c|fO+A4_RY3a@8SG(o%qaH zXaxdz_*MMafWMdaJXDH2oIkC;!B62i(^i|B)jB1M*it?#PdtFK3^}`RM|OHNbLj3( z6?>I%UmlXOvuQ62-{lDIb0{e*U`FJYRO{+#i(}Qlbi_?O{`%bBYCrD6E!bUgY`Lx~ zDPz#boR!VU8o)c*+vdmWVG4tC+VVYlTo`g}3b2WT zw2SIbv_U7ak>$e3t%57MR|FWWy~Fuv`I7Mgt{w#jEX3mK;)a+I~J!asqY5Kx!~PRi3MD_#+%; zyT~Z})jZ(sBWcx2a>kY`&jSDAtexNK1&RAt1+ODe;>g%$Uf&Ot(+!QGt(CNeMh{Sb z0_Owu4YjNXOaj)@65^bHj;%j7VNGcFMaA8)@ui4x4&Ig^5TP3y5TtIOk8Ghw$eEhU zfgzwLfdU8B;BiIJ?na88Rn9rO-xEbv2OL1U44w25&dEV-zk7-`bXu!4hBA6R=I`LR z>mut@1T2RSmi7<&&%NnaZ!NV1UTR$@z86-7*gf4O3&D~moiL8dk?7?@YV1Y@?iCJ)t zs{j4S8lLwi6s`&=N1$=w39Y^{XYbHb36@6jh*B0g>izP0K{6sJ&hV=j9mh6ABLFeA zbVDe{j}O=Jr3##MV<9q6#t2Wpg9qt*MbYS|Z41cLa0+S5XVcf;D&)5(`dqBH>Idlq z_H{WwVi8w}-D#@5&TrN)o$4nD!EvzYh}jl}9>l4)cFwW^M2s=^Xlv+f ziP#c$r<1l5+t{CT1Qxfoq>k?9VInmBGk4--3LI(p-Jeq+ZF0+&28;!pHh@>6vDNi^ z94ph9gN%HOj)oaDIAk;K{Y-RkTg@Zczg`mZWe*6{LIAM{)co+Kq;iVVZLd59tbzTz zW*0fSwYP>$XZ{@TdhZxNH0Hsrs=kjzBk736)l_Ec81HI=lhT&EuVoUl=7LW&9A7mI zy@R@_|)E=%QLW>A=5RQoxcQpJHk9Fd6RM##KdqAbVjzOj4Y578LO;!wY z(;N!O9=C|jNeA9J{xrbhI8UgRk=cVg7LVXH+C4QG@6^itjOd48ml1aD`JSw3tN2H? zanJ?S;=1T#&lFaN0VVL6H8*)h zMwgTO;++j_5w=`1wtCmwT5fa_Z58P5eP|zdmZ|d!fbrD}X%l0h4|jQ~>XdmALQ4Fj zR{w^iOGAC(NAgfnw<(OHEO=CSlywaDQI`t~)2D#!<1B5y0Fl1ZEj6%3*p}mSTah1` z-ErhXo+g*fncJhZDily$nep^N2Zlo@-T{m41Gdb%Qz@xK{yDHke~=8|`7| z6x+k%Y}VybkxP=L0lCH{_M;dr$3GqNwRoU{x2LGo6XWL~sG}s^P791m#N{}^XcUFA z=DuIE;xG_+`mbRc)e;_8G&m>c+xe>sTpjG1Z7KE8oBy zoBYlUCUnJ)V(4u3Moa2))x@W#-Ze+OaogFR3WGEbe;k%c!nQ`1>$CX@R9{j#InzN7 z*!iw;u*WXiZslaARiJzTP&_Z1^eTWovf@WZQ9z_EF7u%yR@^H{?*N@t5(t4MWQ;@O z#2FkoQ>zW8ziaeY^MJDPt@smzt`9LpiY0MJ56H$5ZZafk=OFD`nbpKTZqyDQ+@e~o zUA5(Ct#oZ;N7fcmz9(3)SYMWCB5$6h)*%6zA96TK6?wCE-*E8gW5CYz8@WwaqNs>HLo;c z`A!yw+mHIvwRK;qq5+If`J*B`46uwd!1(QLyrI(5}*q~JN`w+Q0TT6?I z_=-y@4mhDEiau>^^$+EjbV?4fHUGXH_kkZ(W~|z$pS`G@brX%U%@SwTFs^#=MfP!t zVu+5;Y!f={6L@C}3JBQ++Nl2 z0dGj_5&_XZqIfy$ zgW%M0b&+@Famx~^`Ifr$O&-3q^}mf!fSoz3Hg5$sqqStnZQ=+`Cn?2{=uw#$X0_f+ zs3%$@9D?=*)Y(qZU58x`#kH(75;aQcPBVGh2z4(g>ia6XG~A=BCPi~+%~wni0M_cB zIvG(smgW?^=5|iD8kv8NIPmgd1S+a0>tjYhLQA5xu1HncO|Rm%?Z8v}q7WxD$vdKx zUp`}L4^-I*3Ffc>6EwDg3>A5NjfpPqoA#%r>S8HG6ifjfoGW-MF+`Z=7GllTZ?4SI zyI&pF=l>t*B~g6UcMn|mSvOiiC|6t7{f+y2{3(SUe}|x75tkAB?=mnZP{Uwahw!$G z{3|Q}04zhJOsJ^?4T1wOA#{=L0BLVll5_|h4;7-G`iPsCwjrfE*<*RCN14lACO*p8 zD=w{uIgg63mCzTAxH)M&R(&ZHx?H`>$-WDNS-%f-H+nJR?W{g+xjf3KKgY9&VO^&v z(g`wUJ(j~Sp6^hey^c&@M;b8(-j7}_>>Q7vMHtpp<1Ow(La)g6%6DK&?vn3)sD{uX z4D!r)?MI8;OgdgmOJgJIkE)kC4Dqb;Pu>zU{<^Y2E1}Bd;&t7esL$`}BJZvUF^{rY`jNk{b=0LB}g$&U;=@oHah@dmoL+{0O^}G zYO14kCamu57TV7Ia+|k-1G>3J|)0TC37_95JM!_h`=wRC9rOf^&^tCn5dWRTB!2N&fQx*BDRFse5N z*Mx%RF@+kVk;xWvZi@E#hrFym8Z#T$)|!rVtb&v2agL2(RU69KTD)(+tJal$q_Yax z?zzIaIvgwb{2LcM@AvO@KAoI2qYeYE001BWNklD@D-*m5H`HyC3jyQri$ zggP&u0%usAOsDm$c%@CA_i{}AnlC)f)mUGf4DEO2O|Ok+QrU5yV1CIB>NVEtcB#af zuG02R69!J1O6$cRpO*uD%+F8S@NHQ>pE#(NKz7D9=Pf$rP6ljl@ckG~=8wjr8E~_0 zv-dmB+nuy?g&ih(XRCE}8P7Z7s8(qU(`j=ljG@K?=)v>vmF)HN)hdjZmK@-FL| zDc{8%gaBg?)NQenk(_G_Y0cp?WI%PieVYs$$8hR=^-#1p?`hPl4qu=hFT6v*%5}Hv zHqo$1LZV3dF-_Y!^!eEmetu_IT>*uB4uPS9Gm0C_l>h~yJ0f>^{#}RJson@FFr#U5 z?2fv>;-qL(el()Tt`57-Fo*|5mp_XtKfx3vF4%?|Sh5ErNbu@fC64Bixgwj4&hYnIX*=;VkNoqq!rnb4 z-B_lZMW9bqdbJaq;)_gE#TieejwKVKVLRx!BJC(I($X8&VautBn!eIGiuWO`DbZtz zSg7+>3(TB0kM!E>&1Hgo6b7&JWPg&lG_}|tyDBndi*78HxWhXr!G_#RGxVJa3;W!p z(I^?t3yOUAwR3?t-+nyGoS(Eo=bKQ*MU)$DtmDzmsfJx;5R}QAG4*U4kGbkww^bxEW2=%z#v? zeYc`Ip>x$9i+-Yqp0|>zYn#bY$6kK)-ed7#A( zno!FN$!M2oanGGB824Vo-Q{~kxl}RTiCwE&diKQPX^9ac=t62cc+d`-%jpNrv=-xB zA#WiMsL%X*vd&awHTcpOUZyMH`(CAcz)B?z*xQjJ8XL%j& zSKfzT{j^#qXq+E-!$`w^t7_R zt%LIl)r)a}CLtdTxsxXbET!T_Fg_O95$~Ej6n?_|u+brb(i^jpN7d_0UnaW7@oRu= zCHcrC`q9Q=PYAAc##_gNwH|Y+S0WQn!f%tSkB4v~eA1z`k{JyB0HA%i(f??EL66A}HD z@$@?DvwTlS2b32OK6Jqz>9kOFxqSv%g;BrbIvizNA1?7Lzh*r7kOz4q#O%~=6)U8k zs*hlRlJg4$BxoFJ)p@$@jOy~bbr>LSt>YeTs^9T<-(!*WoMfTo?+;+Bv(du!B$3_O z4>^u`P~m#6xbZS%djcS~4Yx91)9XyTBkh1+r(^FvOvcQ}=`tETyT+A}7^=yWSL?FqMF0zlzMvC0_Q`R?@ zR|EXxms0vrV93(vC~S#QNX3m0mcjZ-=I9|w1D{fXanTMaSwvX8?H1c@ip@n~G z@8IsH?&U<@@z^>d^f3|I*(oyHSrhWU2NDW5{HyWei8RphJg83k&|U%kQ>VV~849wZ zm(>=)djax85*$}3moK#VSpdyc;g`$!O#JV)1#6?LGwz8c&SOm&{B{fqre`%t#C^%` z3J$QmgfjlbKkKxg=X0k~GoS4%a2-GdsVVH6n8?S+9xP;C9g~fzsZt+I9}(R0^k7ZK z+!ikLYeZ)g0KwG50*plPLT);zsiycyYn8`@KpXAOD5ZVPlW^`kv6M~%(~W!+r~s3< zn=IiO+5Wse6*3G-mHc50h!+&}Lu8PFSdYqhn!+!r@B2Ty_<6Y)GfF9`&mb6nmBB#K zaLx1H!=F$6?~jjo=5KsX!FU}`7&>MiZ)Ji1kf zG>uufLzsHhdgp{1V|u0cnbqr%_t#NO3ps>3{an(tS}t^jw7;iIKuk@9Yim} zNGjI4ki*9t?FpWyq3h`)+2;X$XjhUfAQVhkCnH{Pmj22gHQ2O%9i zK2B-3>KzY-V-n+pQfz>L{h{QO5Q7Wp{PcL4nxL6?vmq8Ks=w8dZa3$`aF!m+XnABQ zC(Q%~`z9La$!8Tm@CNyVxm{YwemMD7b&meb&M%bqlKRQJWN%n2z+A(o3=n6@29yf; z5YpWe{+mlET^8lw_m%(t@i)lyz`n+EDdsiBc~qbFh)q$%Z7&Y@?uVc*1B!djK(?DH zBz4_2RMgpfB^lslNc|IQ|0rGj(o8d+%3q*%()oX+K-&0Me61P%$SC{#VY%bee)RA? z&Gm!KdE=bM#S#>SW+Hm%b1db(uk^DqoVtc0#|!+ye`K9Ya^+SGLpe|t!ToQzZ4n>J zJxQg~iDN%Y_wobaR2D@h1!}mU$fbysr@XYTq`nGq;=m>9%DAJ`r_hH5bLP6=keskx zpmd&cI&Cy-wElq85D36cj~J*GPp0917%2@UWe|yBpnb(q$X!OwubzgsI!1vKoL{BTc&cm^JRri$NXEUdXU7KsBVRa^_0;DpT5=Sta9rB=eZ}=%gN@!grY=MC)<9o*Ksc0j?K5hV{s)~dEwvLsIxiV&R zsK=6v*h!&eZI2BeI^yLQH4(BY0rGmx^m*7{Ewy;uZm{}>03_cU(1c?Z{!R(NK9B>& z8YuSRFztjOp97F&m%nkh_Ka zi!#d(J5I@!q>;9>Kp^|y=sOvzV-psrQE^F2&qQ}HuTMd8oQ})kb^{d_9MK1$hWM1& z#5xS^dmtfR3nZRjtLJFv4=nI#{~>`C`@wgf{v;*&t*~VO!x+@JW%M>Yxc(`xoETfO z%}Q7Z5>{*oLc^klns^}lWGhBW5 zx{7(r$(tM~xcs zbI9`&@x?-WkfoXyO(HRCPK+D}8TP4UAtV#B*Ja-EN#ga!e+5)ns?9cjn`LrmV(a7c8M zkJ&BCDY*c|mYaFZv+T=;DMK+!tM;M)cNn-S{f7h$unrp^c(~&FCr`ZN(HTUb#DDjo-CxU4f6|OwdGa^+ zkOBI}1XiGX9GckcY(~ZrU}#mW9Ev{lmW2|iMAf8ZpFx|1&R1+@n0ouB46TnwfV2rj zifCiaNNm|>b$n?w9y8uJD&jkwipR!_sIiU($?HJh2)4}nc5zAY7HV_is*CyjNnMzv zgnW59OsoC*-kBYAs9Dj74R+4|Bx-qxY1;4 zyUBk!N4IDgMdU!2ZN>3smhRCDPMsX0po482DQD1J_gA*u)2>{O(vcBNPQMby!|xQz z`PEB$ie%p+Njbc_PW!7ZG&h^Agy~D2r_4#j?}_qyM<>{W29?n;4aU@6l48I*N~1zO zO}rWC#qR6~j4J0M_RT6bj-Amqou6E9oZj>rTSW~A@q5Za;y~-1oAsbykFK1l8@uCQ zfhAlWD*}^9zz_pXUiW#zheA1)cUaGMfb@3K=_hhdm9tD@p)&$)Ocvk(^4;jkz_4Oc zGt5vH)P?a`PPdQ?iBnibh3oJ00i1X&8(h3QUvqQdUdeSYrZgmTrW=mfAqf`NC-^H# zJo9+)^gP_Re+rTam>K3)NYtYYq!Spr#8ji2Ma%_9{v7QF6I{BE$`xhPLz^H`28rL) zzA{omXa z;Fg67G0i7qd}!khYNg8o^E7-Y=3L@1I*oXuAomiKW`e`Jw1tc7>@i1v#;TJ8B$q*A zT(-;qs$br2sotTsfuU`dGvta$}XHc+<+igyAz&ccq%6|F=^Kzm2G3Qch;YWDG&68d9e5j68IJC+twW*E zkXLC8p^MFB47ri^+=8AU{Tap5QpfC`d<~kdweE1MEbV8V@t0$oa0E$chb3}87bYn` zG#_)SV9v22r*QG?MqtfQDr-utE)!8CO_pG!`tVi-FA@GoWdf~(N1kbU$h!?N5g-&P zk7SM7GTM@gcGv50Kx%(zsS43-YevPMYuqFyaRa>U<_4a7FybmVEWr6(A z;v4fcWZN1IXR8(2@uXs~`j9dXYlg=XM52EcL^$6+vMHDomB32*IjWaRMo!BBM+UOdAV4&U6Byu(D$c|V zwOK%rwnSEKuK|tWoU47+$*><$*Qr+5lNlm;JH!*a*)WFwum8C+-@<#M(E$B)W|)|( zf`Wt``V~ry49C`=Eh}fOe6A%rR_v?{m?lz5bzF`u9;XX;*ms4Gy(fVITd9=EQQT-H z6ef8iCyGJBy0CVY#{k6=$piRoYT%JoT1liaet=8|G^I5akYF`GM{%N9(o|Vumh;p! z0f5w__DptUYb(}qtQazjm+)Qy!!b~zMjd+o4X0L@T|*U?B0CpEouibmH#h@{R82o- zYqPrvwE(*s05QZWtckVPd3N2AO?urq>mZJ?L08A_*@~aqdf>SUqb;toMfG0MK&HWD zLQfQO61NNwB=#kh#Kb-Upge5=TP4jkgz3H>j_L^hgowJO_orqFFAj?KB*{!H*2%)4 z8^T$jo`laSsP`L9`gxRHIC%MO{>dVP^Sw$IIDS>!?ysOrN;z$gn`@*5+Zt+~=g$ml zPp3W0-WT%W+4b{vp4OmUN8|W{0ZXdKu493dvbf;TwzB6_{vBAr6Us#Yr0Fy5#7myp zhy~4A3w3`t#J=u?3GQ&@GZzG47~&2CtRBTCGL^uNsvEDykDf)v0cJ3{_b4L%%V;z52> z`m8fM3x;;H!4`%;rP`lzYl?_)$g(FscjW<=GM`S<%Vh zc?{mlL!RCH1ZBti5|yO(vE@h7j9o7~7NTb^4GwJo0?kH7w;L{wn+BoJFUavZRoZB` zVyz7?^M`v^7YlXgxOEtjQ73oAc!?^v{37E;)x-P#V@?81as0tefD!^+Tq_-_kElZh zeu^uK$CFBGW(*i%sQolQV8}8uxxWF2%>CCZKP|wl@FXJfeukAq*D9f4)M>dam}4uI z3`lhO2v?+&dSGGYK9+Q`9&eLBio*(`DJy5bFCH1cTni(ljm@dNiD0f~?4Z=;s;hx% zJ{~DW9x{HAO;Gd(8fFSfz;C$@##Vt_2;y;2>FRN5ftj{2WsG-qwt_6D;=H1R#Lifg zYD;{IHDq_TAIA>F4E{K*@v~${W%t|K7w>tZyg`Z87Ax_bl!i}BsKQmhA1DT$>JSI^ zaFkkd8N$Y0A!ExkqdIoM6HeqT4t57R^H(7YMY&^c2%R+8sq==SX3n$q`g=*9JnzwgOs3*khliKMMZrQ`#s04`u5WO3gle=n@f&(pYPf|Y9PdtiN^5)ZQE z%M{QD()zl;es=oCo$Y5d2i(Npu|I>f6--3d_OClyvvvok8red&kzq#`TWem+b1pOQ z<7Rf5Y9WOERHeg>-yVuVt6f!9xJ2ZAHRx>XXeQ$r# zD|n;%T`+$LMIBx`Z8*6U>9hC%c7{m&cj4ZA#pgD_)4P9RbJcn07a3iU>ovKD>JYtb z#_152Glk&L54U7Ljlw({$>w+X>S1@K`5+goqYX@vRYZ4L&x(qTK^&?k?<#lXF&1_KiL z&87|$Mga~3S}z6Oj;Cy`(8`8L@e6UD$3~5GdQRkI0c|38K`S1TwFBA zxBcbfn#+W>u!7QSt*Mc3^i?nIM zs{R4#+rqFiZdH&6jGrf+-k3S@<)pCOo@~u#_90Z7@sxF5-4MQKlkElxPs0Gk!p{Ue zN)$eYt|K6KQ5Z5yMVM!=_d7v65rnPSeO)X}jLEP%bg|e!*<@D4M#WPGVCMA&->?L( z8Azhw*k{eFU8N=p%^0G>0W&lFC-MQI*6x$3;8~& z*mWWNOekieR7NMZzb?Bc`{b6mm-Gdr5Z!-VC!Z_D`rQ)F7nRlD=27kn**D7nqf=ZS zL^9!tp=L}S&Ya(NEwY#Dofy0y<=LBXgQ0lCM`oiFTy8^;@(nkyEWVzP%!xGTz>A9x zm$)a`7T29laNGFzO_JkjrxU@Lq#b*7A?%!7dU;(=TUvChz$V_L*)DN2J9yu$IlFE_)q(;Ka8*8kFY zol});ji1OUJO}q^yYPjjY!@yy9V>$D6>O!q!dE$3SmVCoqLc=Mb1a`OVpR{wz;X3c zT`|M@p^>_*%6?lbMC}7f`R9v&Vyk~W-dM3USdN&~Jh)c(37SY}&*&;@z=;CoWj^57STAZB!pFz;>M|`)7SdQFh}@t}}sp zjTChm@8I%iN928=j4Z zsgh{2q%_(RfS1=%4#C93-yNtT@3mpjgZ)}0KGoIPnfz8F$=tg~&t{Hs416bMHp=TF=tyw)bjP6*3u0t8v55Ak&o+a5-r3kdbE_rua} z$oj{a7|M#c2A_yVS zmEeV1%5(3&Zw1YrNa7630+-snO?KvB{A<0l6qpXFzGkCOp3TV-0?WIwXgFe z&$d(QAF9AVfBS=kx@xtYadG{o)L#u22bP!18~pjlpCwuAh}oqJo- zBz-3<0iZzD$x2KFW0BY}&HYB#P7md6)_Rk*_kvNQdG|l*N0kxO32a)){A&A{@s?*9 zy-*A@FZ*Cw-ZNA*001BWNkl z5&^yHaDZadab@l`-H;mK9EOgv;s-Z)V7~j?**(0H{m8m(n6EQvgsK7(?D>urua}n| z9%YfTU9O;l=~UAU7^||2vOeq3i zj6k8n_CT;+98K@~@%po4CN-B-$R@`9!Modh(jkj=J{l3Udi&vsCiDk1!A0}AbN7Fx z=7@QJj?+)+vx-x&sykvC`|_F`mm^~6wQyWoJDc}tA>QBtun3L5Uy(rTOUXQsiT^1{ zylYja1WX)|Xkl+rWr!A|#K}n5&i!SZbzUltJ=GCc@3_OAdtvjh6Plz86@KeZkgk?Iz ze5K1Nd;d@(XbYFTaD8^|0qCS2rGYn>K|snZ0-j1vcn2MX1fkaU6XDnNNk3SF7ViA@ zc62N=uEWp;#q8|L`03HLR1Mt#-wvE61tc1GdnTWng>rbGD{pw0LoyX>Y4XGHKup9^ z7KsgwRmGID5d!ZZ_mE2e5lrYk293ja+{G;=9aJxZN->#-Q}LbFALiEkd_$^nmj%bV z)}_|@$_S*Lpe?uh#{Sl7m1<`yp*}{NV8dVwz@?qbSg_M4*vvGZ+4kPnCT}*t*|Hu& z0T;NrI4SKf!<8UxfE-%%ZGCH^kx_;C@16gp_C5hq;B|9>Z7v4Hc z!Y#kNNbfS^YQdC^r*3w6-uo>~eDl`4vtwEW2us*3NI%A2ef}$#@bl$jafB=JU0jcB zQ9+=Zke;*}lcPnZ`3C z=maZ^M-R{$ue>GOVPII9Ni*K@6k3s_pi)@#Xd!o;g^zwSP_y5sT5G#Xnz5yull;oG zD=G?4H)Dto-xuS!?ZnO2ivDam0vu^WHD~FuI{;lkqQ6qDi1{l@?<7!K*q{C+WU7oH zhVi`iEaSqFHwGb=M3oQ(0~sda>~ZHW4Jv6^l%+H$qN)42s>%B1^o7 z6YQ@Qi*0-yOm}@2ko>>TK{>pq|6>m{2ug@m6>>oKa&z>eJD8i9`q8%c=2Z)zQZ0Z8 z<>eCB^xN?-7TgHQB2g=JDzyN<<@v!UXmE<{YJ>9k37&svPdE#TfX?DJ^-vkNQCaau>rBQ!5#QvwpN_=daX^1iYEUfdah#Zb0jeDMb{?%+>Sjq&S^^ zM|HmNJ5u>}0%iz(oLOk;jAw&CR{vQQz9aCd8)$LA=ls4!rn;Mg4m z&t@rc(y>Tv^7UQ~N2Ru(esW7YQBSCDb2^6m=5;mT7#$M8?qJv1R_PT&iopEs><6t< z0^hUNrKAP@TArrW@6eg z?QzLcDiZ`AF6-di!l!`15mCUE>mG1o1hW5f>NcVt+ps3NoNx2Zps0Y2o^h566!wKj ztdhi*zvrTTq0G1XJ$Bn+ao(*(bC$qnJI1)9L;jUX)7@SVaEWBH1apif0+YZTT_btK zO_gxut25dF%L^Q5fq5~`@W`o>TYx3&N;jBM#>WVjXp@-CyX=jJ2V|+*Y zmCcO3%Z`VcZt&=^NkW9lOveGj!69kted19_k*D3`MS#;Ln_X^Kh7+=5M8qIouFkDO zkrFmXFz={aV~(+`#PWpnZ=5!d1Zib>qY(rZ2o~x>5Pbn%Km+B9Gb3H`ZqGX^_R!ox zdqLe-HD*w>e=Eh$-f778QnF`sNUu0L3U26(7+>lZA|>;Ynp^(yiR1?(1=t)1;AZ~W z5blj1VZG6fIBVSFJ-ou&=2KnQ_E4E^%^1?hg-Sveq3^%?(+57s?_=;fO|vdbQqUFv zg%fkGN1!lqmLR+D`zCs9uxw8F5I0>4P9snVcVfqxl+f89Phm@_mRIKlNdgr^Qi&t2 zEOE`jsMrnJEe#Zj3o(T!D=w?#W^h@ZRnBpTg^fOIhwZ{Z2wsPVX^&2wZeg4bh&I$G z*)Sa9iqzoBpEQcEF>G*Xm+k2zg1jP9q8d@@DXf8qO*wX-W-n`GN-R3QS#BZ-^`MbYKFvr5y)RB7!wqRT(19 zz`Tiq4ltM`PKrD&`CwALPFSbue{D{&^E|qAvgF4k#k50R@k`0WVaG(eclK4QpnnN( zN_$t`S$ROj!{AbD0&n;SP%?@QTnBq$CXw8Il8Df5``t&zE2$1#(KLFoN zaxDPh(+)HvVx>we-2EX3IU_Tl(GBn^w&8*eVQGe3ZwU@3ktzT-bODhg%fi`CcRPAj z_8qFUuabEy&_Qi~L93Koit@Hm*EV>3@s;XyaVc~N&)JXHJy+BNJ#9%ii&gh2S<}0; zFz#(h$GRFncd<7rJ)0GbbgBmAI@km=)d3_oO3OyFj}q5N@T`+)AAfpzhiI6A;iS@h z>N6Iqt1N{>&xVLG(4=hs_ZA)ggQob7V=0pLZ6h0Xrul4Z!#CDRD@V062DYy zj*lwl8uBz%4KXZ_50N0jl)?=wgSmG<#^sHcscf2CLnvF{XFT3{f2px^o(a{Mlw<^c z)-yX=UrK2jsA0f~WP$WRS(T^ENq`IfOkUX7UUneF9a?^lTF_T z2jUn6sNuJ?(gwY%<5eSx468p0F|Iua8{RyB+{-|bYk&7!)ZL2N((VN3Mb+S3YH}kB zH6BAnP2+j>5)0P5SE`w$r{*(E1dg0^Wj(84zuH6<8!wKnmRU!wUAw5bsYHu0R5L&V zs^lJRxxwyf9-vDFwRkmzpfq-F%JKr0E=@yKZj@Qx_EAZSBo)5@+%-Dx5X z^tl`(jVE9Dvt)har(NmFMYc>KkkF#hxCR^sbHJ0)4qg7c%WF}oEjwdimzFAy z1b1RiZwDM>Sjsr>(@}?Gc+x%+dMBtPF!PlJidS9|8r~9$-A=nj%T4PGa&VIx&ez>2 z8Kb%;r?P9w=rVHF=oKg6uADb>NFtRyv~~_Q$*a18#(B`sqnFJXh;){6THX@VdoFL3 z;pfEk9~{$kWv)Y`XodD?GwvxA?2?vsSb~07`DN{|cC8sg6SJVIww{ojU3X%`F)@5S+|no zc<{*yTp$;3iSm3^M=dptfGfK_0D>$pyU7M8C6+!Dz4m|29XhmbdHIEXGAF-q*Sl4T z;JA$Z(4JVsepcBBVC3W(;;A>8Gm42-RRsb{RPQLu?g(pE4X?Z+wdnw71ba}pcU0$rG*gE?i5WY**Bm0}^9DQ;CaLFB!r|n#W zfR!FEY6%&;tiLD0@tO6Wh~c>*QK^-+S~&hj=;H zr#BGi^+9L3!VVr^`3LV#{BG^CuvJk(A!Zr$VsaigpmEOfJlJ?Y>O&DGlD0b`oXPP1 zD4UAp2lVCK?PxbUQ3M?y&pp8~8?$V<>O4^5;H)nTB3!P}QD+Ego(9GP`x@uDM@O0U z`|hKmLrT%0I<{3op2;CoW_M1-Ez5AU0_{a6otp5jcz;I-p_9;EF5i~^QUwsQ6A$N9 z*KA_qmNJqO+&YlGRp#eB^2=B^b{|M|5rOIQWDF9wr(K0UC!C~4uQn=4TWqc{bg{_k z02Ldwg&zsfkn3(a7bS%Rbl#Yd5Jv;>X%lg$sjNiP4iX-ZyZ=oh(nGx)3eaempd+8l zizJis3pn)FG^Kn$G6afw3Ag?eN^m z29=!cJ|7&PY1ACrdWJ7|F;V4F=X=}51Sk*L{Nv5>48u>XBS9S|jOZnXdjhA}qkOL^ zh+8*md)5J7@8rLe& zK-$80>R@mTpnd8xZ}Zn_KB(hyJuZ=KJ?|A1vbfA*i&aWnIu6Y*3; zo1n%w3|Hq627>=l-XL3EZw<(T0F%3~59YmmkI_lz^@={LFAs|%)iyvs`?4+hGVdjLO54mWVB_N82I=cgl}Bcj z-6nhvS8f$dh!@l0!PjDhk5Ik0KX?d|d@d9EsT<(N3!XsOf#z^itsYTlQ^{`{;vH)^ zU1O~grZU+^#i`jUfh50hrxbTPP(Pjab>`7irfn)rCJ|b}LGY3bN0lm5?tr#M4ymGp z1mg9D>E!`-%47`=qf{6U(;~%2c7oQ(HoA~hf9@IThQ1aMJywbmVN3BQS5rv>!U=MJ z%maL>3p9?XcH0&0dZXO;EbC8mp_Y(kd+VJ1*#@*)1!pdyiut=TW*p4`EM1iI7zB5o zv+H@!nM@i}h*}^tw|z9m3D74)?z;H@l%U{%V(XT=$pa?ktaY5!4h!Fws_^2THfdT$ ztbXFi@=WttMv|dBzxuu3cyJsf|9Po~V0)n@vJvTqiUA!Q$zo6%?y zH0sp1a@`&{Jw=xcWRBk)C>D>Tsj=(l#(;lYo9w@bBsa(-NTCkFE&5H9XV^|AgG$Hg zc6DOtB?>kVYc)kV@weH*o${n%O2>-!)LpZrcK6sB>eUhCw7;S~2Bw&(LUj&P3a2AR zZNDmE`_z@avothsQ|A{KM~W6jry~-tl(B>4X)#&2~ z%W=RtlWPyqs|QmjW_c>t93IMu%$W-LF0fzfMq?pQ~<4zQmJ7 zOhhfJzgy809J2lX-BxjM|LZ3FR@J|uWg16WRsrW*3M?sPTgyR^`hPA5L%AhUfV;E= z_X*`h{!SUr0jPg=USY1RRO30m8Xyb?P9tW4=sh8eQjNm79sP9uw~2#1QEXvsPQNNwk3DWPM)HF3rY z8mkC1q>OUcDliAEgJf9LvgXmJgNq)04Kxz(>{T?uf}GK`ciV!@BntXAtmP;)>0eP& zFz9W_RP~RnGud+F+F>9Uc@D|{|KayaNd%B>FYJYTP)W*rIRF7KwCvRlEA>@NK-6^W z49lRQ!uEnAGBsmRTUN06duoj<&Bfh37pwdxp=fhh$j&2elYL9HGQ7c)mYz&m8~r57e?tJAR#dW3Doubw zc&wOgR}JXT&Y6to22gj3@ge0$EO%!Zk=I9 zB|Ei`k^;3iO~$g+_(N2kR`oT2ITUN9i*L23Zz^}eb+Dq{-imGBBH;DFM9VEcxC+8| z8C6vu1hc`4qciNQ1tW%Kf+ZyF>QvHD-k)(9>wRc1vNiJyU1Dh9q@qk`-DShj|2xU! zIzaw<>G2)FXxoJY;{cky5ktHC5p2T`JDHVz0#oSQZ;XM@m*^dFZ8sQ?Q6%c?G*;wm zCG2Hhseh?j2V|CB=qC1kc=$A9i$+t*^eCCM%+c+T;S^r!q9`dhKl^(u{Z_VOF@EohOP1{wU=LgVUnjKDVGHr^J&^-CEdkZ#N~wa=K#AuLGF!j zyPY7y2HM`#x4m19cU%0l!iMu+#^nIbCAEr$;Ort*y_py)sSyzG{3g63+KOVj{|pT@ zeL2?hEx1(z`UFUJAd8*1A6!BonvIq6U$30lcgt<~>WtPRt4?JXQke~et)@4~L{1g=8O}&`Sxpi>$z581fyJu|F`j&slMQ%B z7RkKJqNNE}oy9`CBcbd*n5lUIqzs&0u zlj9|3Sfc+vN$<(G$j@20K*Ko>tl~R0R+b2~6M(l_WIziUz+HEFUC%_AGc%=274Y?M zja$f8?s_RW3A&HX38>f(WNhd}NUlZ=!{}+SJARdc9mX(ubkD+d8v;(fPUkUE6BEHU z0a|H7z>sjAhYWA_RLG|qg>xA-HzXUZXQMf1SlL%EVIkrOqSR!dm20kx;2rm5nJD6) zuM0Na?Wne#HOreh6&5!yrhjAXJ@+4UdF>oJUZz)_OJ+fv!TJfB75r+Ipy;Qaj&&S` zp`y^P6zu^cKLQC~hM%t}e;Yxp=ZzTCE5~oqim8}-d}D;j`PGV4`gqMTbwE)vO(Ju` zN{a6AQa<_HTqWOKohZa;7wmwXTsWGe@=C;Z*W%$lxq(f&&KGNaR zmA?b4nE09(raEz(Xmv|jc+fsJhZgrml|eXO%V37Rf!4-{I6{%m$4w&G(fMi0$N7MY zM2oGtODWyxm6L~tOlsk-%{v`!BK8(7Z7Y0&MBxm*9koURI19!ME4(k*J3 z=uryaBA}-u^PIDk93P*OJIclp>T}{b={Q(~Tl?X`CvhQt@l9IofNEUj#1tdOYo!*b z;z`c929(5B7~w(Sh~T}JbPSXzsQr54mF#PT!kunxf|j*l;>vwKhQd))(bIMfd7n2* zZ_1Zj$?Zj11Z=D|`Cyb zL6-LVgh}XnU#+?a*!&5rz^o3VC?Shqvob>tUx7LTmYLDry97SBPji$^b1+ajI95_7 zAtoqZP<-D!x(_KccR%=5iCxWmF$PPvW(uyG1IU}{i z*i%u+l+APUq}=7koLAk>wK?-zQRD;FoaORDW843he$c{j`Lp*zERlJwO+5CGko=xl z+mTV<;$cbg^8dr@?<@2>X&&->E`WW#r+71k=Vic*j zGDbvJScbZ`&k*=?`4q3VNp89^wzky)5@|Y4(>^nZtKTDk-8*)jg4&#sF>M)NG1Ky7 z2^r~l*tdRb1=PI9XtNy{OevG`nBsP@hm5QQO-I)UqQQ6fnwyztgvN$E5xZtlr@X%X z6*)x3+n!~_Ic9pr61hXpR()Kr;d7Mk=;`9QwHY`W7;%>j0*eO95}Icw4$<;oERzBS zhjjE$thvrBdB_GHLNN=SGpho<%38>;7;2HMID5%e*PWDJ>@IMpEh$1_FX_u!3ZIt@ zA7q1B5SX$6|5fK8vZP#)Ho!vnzy9yUOU3q)rj@ZDhs^ddfwP2PJmp_iaGHoOEpix4u61M zD*h%H9wWhyMRv>}gaJB-w!mo(q!xbWo58WG@}_iToj>}m)we8eWtN@%62%36U>Kcc zi{pm0{2N=@U)%i63XP9As@QsDX!^eEp$eS+JFeBzcI|)uSg_?MHYFb7?)-BXRLCr< zBi{8AT(nYI9{FL>%!F5)(wBy!m@~EvgPLL`9(4GKb^>hjdP#fI@^cs`RZ8vrDK!Og z$3Xi=u{}w;V)woqL?Kq9N*kKgZ!>}Ud+(K%hYcLIT|AXB)2wEBD($hpC^x^?UAq}& zKB(E3@sAhl&Kx5L9I16X1!K-h{a6oo;?Z0ZzESmcLieL6OP14x8P4nHRjP-Oy0Qgh zD927HWQl+(B_S`EpkXXTH-&dAo-BGD?Mpn6C0y`;^9wTc8tQw(SRR~@1h7J@qxy;- z8b^Y@0B?B3Fn?v!V}ThnOE*3f`s|V=X_Z`aamiZuo!LWrPzX&}KI}nheG|q3z06t8 zNe`gH;YUo6Z%&vIW}|G4u-_}yt=wNv6bm2U!bT{tCQOm#F+```>U-dr(Px(3n}$4X z$24(<8Bk2Ru8UJLny!?imfuUbZ_xUE9JHH--$8eO$PMZyf@`+AIGdSS?B56bjAqEm z2pSq`LVTm(2bZhRc%Y)XKR>Ao6!WmC0bH#)!DcIHd2K)igQdYr`i}!vgP?iI$vUPg z>^Fova73{ZKu(wjiJRX78_?!x1n2n6lYh*^dX!5ac$w#wJHErB2Wi8Vl(i3dk_tVr z@`|jJ?*k_r==WJOG+qUdSmI)iWWLJMH?58&9R2dwNl2-*Zoi$dr3Af!>H8+AoDN0zS`ammcnvj;4FcKu&aseeISmEQf*_dDa7EY~#5_N8H zcSLF#eYRHK?s%KS?&Y3)*w%uMI0<0m_d%hG+#W~We8LauSPwxwI4gulMv{*o>%Q)f zQlA5o+&-`}SMb5DFZ$T0T+zKex-_@!vq3Jd;LNjxH_3Ifv5%Icwn9mL?{FE0VX17n%3}l#4J3hElks;6YzBnzL^8kU|pT0-TablC%W!j0g1A<(TzJpns&5d@8;RJqFYyqpLLJ(gfMBF{*ikCWAO&LH zBui%!bk7%I^Eg%gt2Ujmj>CV-HG>5GKmNpE#M}!uqmm4(FkZS0cj*+QI37fSe%D|; zQe?wh3X>NVS9ulXsMNR0I>puBmhj#a%t>KyFxngad_@#lG(;m@RSx9D)``yrDf+_T z=-G(1Rvy=2K~|j$IrdyWo3H-#eM%lK_N(LAW6eLrfc=N7nTTpk6@k9y8Y zH7&ivBw2g}v(8e=@)cR)hie8wkDx+}>~&pdw7O_Jh%RL?Ric-Yy&0ge&L0Z66VI7&Q|FV9g+J$ zVt78(DAtzHG|^fqpK=EisT6?Lf+P^)l{)3x&79LQh$5gOV|!i*(Dw^Q*Fb0uty()X zkAzqpcB~$9(wvW7FkndqN8`|x%$e2QQ<1U+&%t;4qW1A>N8O!Ox4(Bn@pNavH4s(z zd1hSzM#AL!%n|FDcvnTfDC}xj59#;k^Euu7L+yFU1=bTgBs|yO@6`LBtJn}+E6)-e zl_E2v0F{0`9p!CKFb!I_lb+*)4sXLYdL5;r5+jn32bX1gG?+?&6A?+RIVQR6R*2eN zzbt{je)7qGi$`) zagO@s`-bRz`;#sOSJ2o(6d>$}gjoXVJ{#SSr%`7DEi_`qrhifmfZFu2g}w2jpmJ80 zCBi#^GzA^VK{Z?U?ScvbdBPuD;~S1DKCB5c?MZR}F&&LXC_5IxL2fJPq23Nu+?noa z&w`P0hUs;a@^b$C)9xm`VI3pKHHg<39&pxy;T@TuDv=;A$WcRs-r9kKCnJJ8e?H@^==??Ui0E z4L0Q#1q`J>FG4766!5GlQG3yfqq3Q7TXbJ<%7gz#bn1qV9WA5E%(eSzspBP%b0Agv zg7G62`ksoRc~+~r!op5+zm{|c%u!hgtw6UWzU!OQ*j{r!R%Fw!2j0~f#88VxXZqp{ zqD81VOAKOYAyJUIw{MYEz=KmW*2O)+*RD%Nat=l=>V}OZi8_%5JO^@dYqg!dPX5<^ zdQ~6HiAC{{JuIM+(cMett=p1u)D}(e{|RNY$Zs8DDINE-hl>RE>AY@q+7H8u17()$wynjsPqr@2zX7D?y$3DjHeYvV&hnXHcFm3Zi34JHL zQ^KV*jQ{8F1N}{8vx9KIq0&wYs}uA6^v0igntLDkjneUZT*J;K6@c&u!iehF(VUsh zzP#gxfkIGWBVmAr6JLeziXWZ4!)JE28EbF|8oLGZ?o_JQU9OAr{sGMS(0pl*v1)e; z3i}kOlnK$h@XrdKqOJ;5t4!$NA(|S)LRr}*=Pb?O`@fKm??07pW2l2x;}WkdA_~gA z=l(6o$)-O6`wclr+f&v~x!~qJ$yX`&VBi;Kirtj>e%TDuQptvPnu%vz-5jrHbm?(Z zrv|)mAYbADx!u4-)Jp`~FNY=6qkgRs9fa+G$OAw#2&uD2q3nTRfya}|DZJdTu(+(;+xLYO^G~plJMr$a_&^P@gvX7oK2wS zSo15PyuR9#z<24~$PcmBbZ)M#TM=A-iMde`*4ZUk85DujM{dZff%#w5+@xhNT(4KSQejOSf} zuKkeMFjco#!I*~7DfcVrQ##UJUZ15yN-045^SE^ds_}? znECZ7r{)E6UW2|j{&ek7Vz%7^H}c%)_NNPHn=%2d&CHYt#pBgZufeVm0f@_ zDAO<*40kd*Si=HTZVBE2bXfu%PNbNFK2DH2<+3oUc1vRr6^Z``*!%p9xX$>Laa?SMz!*Rn5> zXEL}|hB~k&=HflZV_aRd*2S}1*K!_UuL1ayxx0t9Y>9lnwZku#<5kckkZO3bAVW=* zyTdwAH#g9}YD(+C9f+kCubBhPAe%;=t52lJ({J6SnQyioL|y}uEFqG#+NYQOr4&h> z^kROf)3{Ie-nB*M1D}Zn1gBVGOlgPXpI<~TAK$&Y@3rnBMy;27)$rY3S>@KJ;4;R0 z)t^a}^8G6e3HiOG-~&Dg3t;3S4%B-C+sqEQ(#?LsUhmO269G*ihsnws<2#*9oP-3n zb4HYPjDQV|G}&fIz?$zfz4iX#esy)Iy*zT?wxFIBr=Q9FRftFs6JMa`*b(ZK)NlysbDB^FtRRx*C>?A!p!=5Dkn}Ug`0=vh@{HQLR$rtI<8a~=hoIBJVk$^)+q?0L%n0O5F4A;rr3pX zI;LBEp?DNq!+i^4!0j;907kJ0c1_ix(ds9*pcH%?^}MB+cXxGg^rBj;@Vbpi9{Va) zbu;dfM&e6W1{I&tsBHXvR-Z{-9qIhobe+M53e&Z|Qg4Ntoa&Ipj0Bz_2gW>&49=kE zU`C8-s%1#DzN|3Ofoh7tX;|sEixBd$?20P-A)+Bxehntwtpb^IEL)?Wkv-0c-N{K_ zB$qwQA#@=F_cgQ_J_4ipiW8}t8L>YYyqEAJlBdQs-^CytR|wLP^I4^lUY>hk-harr zRXvMuyNOL&*+hhFV#UC#&uQk~`|#jG-eS|7B!H8z7WgMcvF*622)=vW)-T9y=SdN2 z!3s(HadHW^FVnxA2~)=jm(os-<~X{N%V6mBRq9!=MwH9)(ESW4#-R+GI%i(AS3O?5 zHB|G_g`4!D6*vW7_HZjZns>=yuoA0%KQW^^7q=;9v#qex~lEp`9t-^LGj zx-)*|FU*k*q9N1qH_Cwf&=vR1xP8ZqF_@IwfdStWng3noIW;FDI;XI1h{ub~Ryrd+=7d}m!yy#`dWm^?&=d!%(%h?x1m7HS_T#kq5j{b-edkOa zi%8nltrVu6wQ-^(UW}Rgw6tvtj@%4(3kb4G4a>FFIr;0A)g#{v6WocwoME8WSGB&L zqgthtydyKNe`jt;O`bsAwc{S|aUNlUShHi5{@Y`cYyHA5L!$+))o91&FefXWNMBb! z2pE*I&KWkZF3aguY%IqCHeT)xOn*2O*>lEvqdBT4J+uZH`&vQ5j%Ik6{OrJ4(>a&V zUmK7QrLnb8K!pz4K>2RMQx{8=wwdl_vTfCpx?rBciVS7K6VWNUWl==*GgNNj8I?5D zq(5??_hT0MvwO+kf5QX=PZ^3CO!PHCGByS<$I&ierXLq&^%ogK(~ELC$ZmR7#am)G ztV&P9I0P#{mRUJsI&m*!xj8?Yb8{cJm#>ECzNx0W!=5n@fca5O0z=6zBtmO42Rta@ zN(ysL9jF0vGmZ3NwYk`Ek#qQ?O4Eb@-Hv5n93y5Yv0O{qBR(bKLEdi_JhAQ)#kG;z z<=Nxq5RdUHAsyOxbaM3_MaBp=MeJQ>a4733#o)18EkpB8R3U?AN!?iT~VGUA3y(rdr0{ata$9HnZ){{I1M zt)$lESHt@9(VnAkcy_#lxD3pF80pw>1p3#EW3hr3<20W9JK(prCl2T{7iphI0(Nwk zK}w}*bEa1+*dPfCjYM$j&Kc`Tt{pqK>_VEWNx;~5nW18+F8X?SR7veIj_tmsPA?Z( zuciQZm+BR?$&KJ?u2y|JPBbi&-eB&NIH2bWK>5|D(1>PGY=Emwlmk|ka}DFH|6?I_|kZf zjB3UN^<;`P#(s@3Z<+;3G<$=Or)#clQ1K_m!>ug-(DiyHfgA@!yUh5J!icjv2!K?7 z1^j=Z@27Ry&|li&fhLS?!-zUfa9dxJz1gQpts#_66>x_j{YE{9eZ`k;faC6pM@Ai7 z?DBv!V`R#Fp!R9Qk=+2TDWP?{-|rfXq?wCe$SSKp_m<2mF1DkOt8R;Gz{g@tdM;et zw*DjQT2vjkVxS5JY5xC*%eCeqXAAANWy$6`&apJlh$Yo9UiOgO%GJe#kfu_{Q&G|< z*lq{y6eg9Q^<(?)ka6wR2V0q~wZ+HLqc0nl1 zr2eQ7^9XYkl0Qzi1XJ2vnLR#Yl?Vd+>B2&{Iy@5@jhbtu;r9EUqG-k4G=t3DXocI7 zFy|<+DP=nP4(3kjkKtR>TQzp&cA3~I!GzwAoQWZ}o(1GGj-H8cy*pH%9%s(8)40WS zI?1n&+I&GOu7az{*M_#L5gShGU>PdsgP~cf6*H@raiD$M-X}eUTm|RmN$P29w>g1Q z2yn3QP&gBS(@31(-XSc8Ky0MY3O;Nqxg|heXh(CNOLHg?8_a*kL>N$ zD3N&)r&r9397Odbo-pOZP72kYC~DjB{e0aqQC7@O9eeI458kuJyQ6?-17T9aRxV)` z!h)EAU}qZ#lC*CLkTJ;?o%EP;7q}&zqgo4HO|cIMxN_%)M1o(^5hcJ`^*z=tLpbHj z@(b}6hIwSFj25gZCn9oxolsAFV_@rvzV1pF+}RGNsd^O|u9>44pL8RAjF|L>NY>g8 zx}{S8Opv>W!&c(duzI^Giv<=Li-P^c#&tj+F4NTr@RzqX;{8n%kvza6f^g zZdX3bb8rTI_)?9nb1>y$V{iN8Di_aWmeZ zkJ)%g#!RQWmW$0u(o0=?2w2EBzPA|P?)x;t2o^WEG~YDOw6~-i2aY{hC~Hw8Mhmp< z(@5uy4R6k2nKQ2w7-Xmz^tFQ9(ie0($$iJfH1Xhi|LZv-3#mrbTA}lQNE@)^u@X4x zc!1+a>5qJe%>xTo6AA)-8Td6=Rh{&{pqjRIaKdD&_zu+ygoQ1X?z8;F!8p4=CwIH4 zz$BH&Ar9^&9v`8lIM{-e?k9_`lw~)c`-Ao@yT{ zRjo62?lTxjY2p_6n%Pn~kquSIpG8UYu7zTogtECUNO&Uim*Si>!GJTGZPxfHQp1MCZQgcoQo0`tY1WM`I3Rj)uMzkxwVy#a&O$&fBGzl@f}4nR@ROl$c$)}pwQcmCk90VLXN z?ssgM<6GL-V2PA?k_r?YD2co&XEFpP!M(|Dvh-{TUi7HP)ga#cK8SjTZZ=jw{k zm868{M0j;Y_YMav`L*?1-9)5`+>Wq{_#ILjPC^}qQiI_(3wx+g*4zk%Gi9rk*>Omu zOseGc@5)E`DZ}u|E*=GPBeyGBmvIN^zi%uwM}Qz1@vy!P;mT5RbL; zIM21Veny% zzS1S@u-OpEBvH+C zq^FAb>%{#z^CwWVFi}XHfUB%0~d^HiC_@mn9hF0b;EMlZBqF zo0Zl&o{bB$iU!>?x@2@K&MY>2y3c5C5wNX@ehb8zxnOXt9u1wYU7&jZq+gVp&S#Rf z!t86NNU|Jq_$c2XJjmpVk2kf}tPCrAH4lm9Qg@qFJ#snm8QTs54R;`+eo9Gg{OB~t ziD%>`z>ct`HWzKeNg+&Orc>x;5w-bbA1L5Asp-NQ8hsTCSRWH@TP?`jZ9>x9b~Mt+ zv*>&wHVNu2mrAL`Bw>rv0!QrdijJ_4o!j}DY4-+_GUE@qZi1Prr$*O!kF7VWhXPL` zst`iJP%@{#sL05=rAbS&90MdQFl6)gI2Zz>Lbcw3Y36$socrgQ+2(X;K z^6zkB7biB zkx>`EJ`O=?8tJd5#rYutm^8m#q?E!ybK!Iwg|YI_p@fEN*4{eP`|<)a07J zP@5|Fv241$>?s~0uji0Rq8N32{7b^PKzYAP!;rM$_~y^T1lqVzcbE<-3A?}|x6Gdc z0US|VqGlFWHC3lrN`4mPd%P^rIh;=_QW|Omr0De1GCj+Xo8QouYoh`#3R-fb(H_*0 zmdmPlGZJbD8~yl1tdL^$l2m2lO;!ipiI*0Nxrs;{!y*ATfoXhP{@xG!7Dk{Awe@4fyI$uU4t4v$qr!VK}=Zsmkl@>2o$a$lzgq7L?s~l4C zlXjbm%1&s|Y@f%9_ndv_v1>}UKMMN>lhiKP2>JQ9_KXdOnLdIUR~Ksq#Hr;~xx3a$ z2K1@RCYvV<%UY9pyV1Z`+w>?QQcEt6-LWM9m9n1$u?uZUj<9@b_;0*@`BPyZ45PRN z*pF119E^*`Ezaa~qwH4Z*x-$-PT4|i`jb`w z>e;GINs^ZupmuuD*nEiIf`rEn_`!V#J}yR9${&7 z6Kn6aqFU&gWeiQ3I#-L7ONYG-`(e|3HMwmXWUT2VI267Pe5pkKNaH)Ubq%GVj z%YWZ4Ri*GJmZROX`0{Q15l;IrTzIx%yIdC1o16bx_ZTIkH$dxKU=vlK}{aEiH{poZUyiH7AdR`pPNQz)K+? zh!jfX#du3tKkn!Goaem5C5ME-`{-RXgd54DEwB-fz0U2#p^2b{Euzf1D5HX&PP$hk=y{v4i1l8t1R?h~>h zry_zlN*`^Gx6fcUUS}D2mIaYP-ufZr;KoOX-|7B}`&=>#IBlCZrQaa9Q2+oS07*na zRN8`;X@M;f11IrEMqJGMYxQPsFdeqPkzxbJXlQzr&tF=?1Y7fFdrX;qL9G9|IGQazPOTRtiD_0(%T3IjcMNuZ_o=`G4$p}R|i@rj2<)8N+PkQ&ulJ$ zHxG6Q!C>K*OT}31LvJeQNgYbXCILi=6TAOL`XozuYCC}vM#kt1fYz+N7ThEPIMJk0 z%b=sjZ8>uicoNem-EZ~Jvaxk+HBDjD9L}EQ`fEi5mFcst0$cbVbL+RlbBx-I zEkRN-C3mO$rpqHyi5RQI9 zA?5pYoZ6NXjHI{Cco9F^-qS&6+v)cvH0Zh}J1O`qyK22B+m<+6>@~(=DPGoC4xBM% zVLSjGzmL2Xx=@}mZW8Eb8X2;U0rk17eDfs*#3KCc4RO`FW6KIML0=@>;pCyewqS=?=qe z@WdoVnv>PHRn2owR{7hCysIeakuW+>K%$(#wa*?hU-XPV{9<@7p69J##Eu}>IB5gB zV!@#dU@@5be>bop2ZP%VjQ)otcfy#@hji#K`4?D&{d(8g`iEY0%NCjN+jIvoGG*(~ zeJkF}s_cx94w)nm+m-$cQrzPHPY1TTYjo!f-3tn)`GpcFY zh5@Oyt7g8E68f_oCp!K5#BW|mp%dMoXNvEY=Gl@EP3C5NHVdvc=5_x94`{I;^-Y^8U3|~exo?+3QpR3o7W;-P=&fPAFjL&Ii~|P}!QiBsiGHX02duea zwZ2VzA7z?tbyI2SSp>yk`p??_9Q9KO=U=!6ySE8Xp;dy5V{^TlY?a3DsVVgnTh^hA zYxrIa@!XfP1n~Dwp%!D?cJ!L+wNA*UnoZ6ehc#l2C4mvY!;#fCVI1}y zd7L}mh7+$7F)Gg{&BImDCf&(aS7u{LQnfN$h`qAC`y`Guo;P}VYbyGO(LC0B@`MP= zdzd()n}~3f$S`YW_LtJB0&C1jcJg!NSFQU0;k~|FoFWuhgG%&P8yLFMJO<0j@4NUh zR9(EOOf6Ar1*Q8c{kN7G@fP^U1$5((L)97d>L%VfUnO~lw$Zq^sr9Y@`t;TDtSn2) zvhl;m6U<@GzIsx}%ibu*%C=Kh-mwC(iEqq6*~6q`&mMbF1Hdwwr~L365p8v`{+P)v zMBXgcOEa-8OR_5?3s-KcZX|mUzp40)?ZnnE5x*S8sk&Vef!1spf2vgndpO;ZPg_B` z@d3X@=xXTTGFO(Yo!B|AlffhLy#-7nn9E2$mzpT}(!u?tJ@vuQbM!YEL<^;ny-<0q z-x7A{BzRR`EcxJPYu8uX>Ez8`@_k*Bb|Ut5MZF_=@~<|5&~}bOd*DguB*`dW6Jt!m z+uE2G4pd-FX;Y0^rchLj7mt|fD|@3PAL9)!QCJG~e{c=;e1v}j_Gd8rBD`iPa~@K~ zBuxgqIllZF7tw4tcVidRcEQ2N-oW?uB@NGsV}FlUaQ9-5F%~b;q$I{ubd|Xm3%$0O zth;QmF7^%wIt-LykQ3kB+j@+5U6rs745sNPGbiSZ%6o_nWPt81@k7o#=_t;B6(J)6r3I0k|rt-9l$z-*oi$vfKN%Z^HU13vNsc`?y zVG-oX)5W$=3KXVOs)=(MV*?mv0?}9>*z)^cj>e)Qpw;fg8XvHVS!_Fe(;=6Yk%0JG zBCV~=gWY)R!drHGiQO&uISW=U*tj2B|Pd8y}>7Cykr?VR2 z0h>&mh0*nBBGxX9O%i+xf(5AZ1{iD7iG2C|?Yn^~FnhASx~z_U!`nTnpl1)y1zw&p zuC-A5Qh2s!2f7KP(s69d5aMvFMs z=U+7P6^`mJmuKxx+|}Jf8`88E3>9MD|C|I6}O;B+@WWvpF)4 zde8gpKstWSk-x8P^J&lDTdk#2Q9b0Et>YajqPhJza1?;9FnG4;kafQqN{F_DK5^Os zfk10VqcIYUME_!>G>uq9GjI$6%5MN9R=>ZeV+@xdUyZWys}_F)vMOXTM$4Of0BqWD zXdNuUFJ(#?sF6 zUwY|IHuszA3GVwI0K+V6$Ujo03XM|CCYH=;SX7@CBVo+|Ngm`{jxdEn?yAHk7lN1xHuNofeLag)ZY*AeM@>8oHnLj8*Q6| z4n-jD2;(uEK;%=#xg5bNMFs49!%?+0W5FfA{HplT)dzUUJI_@TB_vRF)Z3TMl7MBx zAXLh&w{LRZC?i`?NR+}hca>Xkng@Hn85uKn)FT;hm$~p`xdJmrH6UlM6*F9|{h25y z6{g1YZS{?_@1HQfjtSybSbupf-c5``VA%!Y<*5$1urMV~^w(4j;12y9T@Bq=EuxDo zN%o4XtNMrYgOFQ>5L*^gUwq$j0vg_OCRt2ii?G>>e3G=@voQrjXgu+F2p}n(3X8TK zI8uc2X1-*s-+2DOE!rJ-rVP}?|Cdnj<-d*{-}l)VINv%8 zp`1c5!d^m6nFn+jm=As>1QHq2aVaN?i9`u$KF9f1FZp^Svdq2;Lp@Z95+caOg*ok} z_rCq^pAPcxg-2^0I^T%f&m?Z0k~!HbYrUr_lJw5&4?|m`^>FY2_NE=}u~cbw>}2CE z2gBlEoo<^~-E`a4&|08|8bKV;=&{`fBLHf(MX5N!*JS`ACIS-gdS;k z-tug8Z@=APtV%4gpFrGVO#C?dPycBdd}aAwqc=R>a3q0xqLl+M+KN}k=YPrNlLAXV zvila-A_klj0*WV~oKbtEy_hT4Tt##^dTSA8?V2-o*u-S4C^#b86u_K~=&mwv|GxKM z4iYvfx$&Y*(8D}iXHm2{}=2Gdg45#fcT z+w{Jw)Nvu98|`HSZ*KJhu&qn=-SJd~1Gdo3E#1J=Q0|3TUGG^z=?#1rX;gooMU)l>#t<;qk6)J?qd&sCa3D?T=|0*4Q1yPY&?S?ja!ai^bH#M?tfjTsfk?A ze0G6MLolBBqNe%j(?D&$~+W9}5z2j2V-@taM z_PIs;W}c*Xj4kXowWFGoumxfC9NO5DB~Tsb4XtB;#Hw-9i%GoZW6%h4B+glLse(BB zi>vdU?1=XSM~=@$ryRcTvhWZ3NapIsmz|b}n*erfQa(ygDQG%!=z71-8$|A3Pg1%p}9l!n|-I1oHwr~aq+)ohY zBF}=~sY3LC)eaYu`N*7fvN2-5dVe7>pBJinhw$bOSOf9*KRHp$vd)pFDM`|!kSalj zmGo3%jbctGB<7vxbEHEd#rvN9hI2?91XtEn!-&$IzUVResdK#B>)^SpxbRr`X%+f> zOV9ZdCmfGRV8ohv9$CA?%bbqb?)wfCwC55$&o58z@kLvdpJPm5+jH!L*D_*^PTVt- z{M$BP*$Pp%n9GD}k4151o;+*F3v+QYv4JR&QwbgJAw7THYx9==PKDT+3O{~v%@JS) zuxiDr#jLIdk!5Vf)oF~#X0DN-Z4cXP?nh*wOPdJkn`i3;_ACm?u?Se1JcTKb_#Usx zx6eMCxSKi@-#Yv-i&;~+X`y^?MSJt_Rk}oZsu`F?&ml4TL`|<{IVi5Z*mavd;}_!V zq_3z*%%sNf05J4ifY9b<+3B0gYESCd*PJpxu!ecs24D@*VJ$QH#g{|ekfSy*PX=WA z>A+w#0Am;AWG{l&5y+Jj4keM5V-=eptFxBbeQrDjTslh-0^=x;}nQ=6M%HR75$ z{l*aEG0vj|wrs__pJ{`(NDasdJSWtUVPWY+zB6E&e0W=jLDmTUBBuo@QJV~#^(Vw6 zhZpM?iw9`Wp&ur|)miD_GCaiMJ^>`#aTo3jP&o{+z% zP(5Yr{!GX@iL*oF3&i@wQQE=7lT32ph(GVK1L59y-1Dm!3%X_(+kel^d@`I9N~93` z0Y|j;aUbC&R(WhnB$}|O@_3_9N=o9j8;l2@)z_qox=C;CQiUl$1w})6MW6rp2p$|U?j2&I8t)*U?*+hg~ z0Wnw{R1B|_%=|S94{1HI?IpUS_N#rfg4E8jbQjJZj%E@~j1pE7Tjiu9uenkhn=R^L5X)u5{x(VhsiKhPeJ|G%jY|%^Ewh{XW z(<0&L%4bZyIyNn70U-tc>+`0w$Y7T+nO}+My$4?{(=Q`Sr!O^52RCGFQUg%P>pG4; z^0O!p?R(j>zh^%?j_t8BYQ1V!K1qTj>lEHr|GLJ4GAg3XFVkHY;N*7HclMoB-zCr+ zv3uKa((FovmU2=@u@ge3CDCDq9a^xQXmo=F6xo4yi+!8sgWu=Eo`Ju80chfKo?f3Y z%Ewfj?2z6I&$MHAl@K6#1y*&EQ$1p{(8`{7YeL6Ugjb2UgXo`bH3|x+X-RgV!8Pc0 zJ1A8vcfgri-I@BgFPyDCmhpNKx0_;u`XmJSblw9AM-}xRWly%9!_*)@jW-}4lETZp zd&!Xe`!X6B=XoXL{U=*5r;k8!6$46V$r|d(*gLc4)bI`3Sf(5vsZXI?p+c?Fy-}Gs z^*`=G&w@WpE=U?UJA+m{@M`tMh9?*^K&Tb*pygCYEKE=oabtspiIGxKRcB^ies=6X`~J`@q>^$S-xzw7r_8GAlIw+#qmYf% zNeWEj`)e}ul)YX3)93-+!sLjx5>e9-f;k;20z+b~`*JU%NGS^J#*yxRkjKEosbz9K zmJYnN+34_->TJ~d0>a*S7_#YN?Z&+_OwKJ6TdeFSZ!zey@0wG>+4Y5Ju(=zM4$Khj5$*3ze zj+`m9yKm1c>UpWl>q^lwG_D@)Jrm-n#TC|5wv-lNv)e7lGO}WM2M}8zCls?eR$yTx z!t?i5Dnd^$_;RhcS75aPly0Y*NxZ#xw6S-wSw=C8s*A7Un;I!*L63B~ajryY>7Cn{ z1Dz=EFI932a`A7TMwlm5yq>LW%rvu(o9TPOcjcDL||?&gy#q0}Fke zQ3q9jopW;FrTRt2h2P5gw`n5xI!t-*M^$m3)U=M`smn})L~KYGhG53j_HuSKyhQogxS;R;uIA4^ zkB)%>;li~Q6i)DP zK<#_y*8C&I`s=F-yOW1r${Pe~%60f5&&SipC8G9QE__82pALs}e={O9RbL6>pDA}o zO^bJjvEQ_pMKM1Mz;QgGw#c&Jl@Vy_Tnml_(k8GX(QCFw#9~7(mRC{iiqz^@eSI7 z$|<&)d)kaLWu*@gmLhG?3{52o+iu=T(8S7SqtfyHMBrZ=Om?Qk^+R|)6~zs72{$gH zp|mAlY=ksZ1RsOf&rI1wpwP^%64l9cWC@B#aail_eJ*pUy%GvXww{YCy%?C^@R8Re zeZ{2A>Gv;=G!f%ivvv|@p#%QX5&i4#fgv&8?;N=1-Rqt&Yr4=jzo8T!&Zld2%90O= z`T4h!ZN8+1IRSER=Gv3Et5y34FZ0!1>1#3VngE5qgNq*fAX2vAB<1`oA?4oh5y&F5 zTgWWX0tjk7L-$}HT&tfU-ze61Ge8+6PjYpx@C28OOU|EKlg4mo#M$$%ui@WIN5mC1 z(N30!a2iR`j`lJi*G<^R5)BnUi|h)xIfypwE|!ae!&suQlj0$@{xWp#a4{a1;Z@A! zV~woTqbQF(MQ<5JXMR@a%rj$Vk}cPgeN~2a3BNLB(A2Tu3g%v>?QYJqQRM|`dG3Wo z)5c0&IsWJf35f3d?vvzc%1D z>0%ZCSvi2EekOQhmcWoY0vv4XDs&^#%awGjw66HIw=n1=#3E~3=rV0^N5tnHHWknT zdkTd#iUj+9Oe0N0nIG21X5lOpA?uw_QuO04e?F85>E?2gzv_a|&j|j{rSsahrz>ry z`4oun-ykP_sP1Idl;I#-@;UD8@c;u4#MKyeC< zsf4HY+_2j+n4t`P4^#Wja1swwM^unm$jK+hnIpsAG19O(&0cFG_A>n?FNj5Tk~-(j z%|Snl+I9%;6b0tDE7|&FXEA<^HcA@%_wJ3;CKOT3+Qe<+daJ$Xmya5wii)$ys(r8d zA(`0C(D5{7oL!TIyy)`gyFTx|yr0CGz^p*O418@P8T>D4ec+ar<^lx<#ya-Wm$yqjT^P&$uLMMHs{8C zfpF75!P{0}om^H81(syUp8CZoGcY6eL} zTazrcgn!q}^Avtwi^d$_|9|w1JAVVc*G@*-kTit?q*hCYg>sH%$Pm%`x%aFt(oA?l zfN>g){@yS9$pV3c%5L(LoQk8El^Sy<74@9ikxI<|vfv9AWrlJ!h(-OHO7SyVQIXZ#P515R5YZ;wnd(5TKDmhI3- z8%5rx41Pek=1OZt=nCG*@$tizPd+nOV&^0iIR^>+=5i<4&-2NFHV=zF|2I+)HMazl z+rTD2&;ZXBfvS5d@-;yVx{!v*XE~@wKm~DC0#X(L)J2boo}O zrkO&~cS=;wFE3yXAIf^px(dw0V2)qJTi)_=Orcwa`;zaDn@#yUuvcM}3{-J#5rRRR zE6qS_2)#|Unw?N zfE=r3Rd0^u!_9$+YWErEZiOV|Oyvq1A4^C5UqI9afDg>nO?#(qm6Bq5I>4Du zqWkGe-seDgnOkqMgChEFs1juUE-cTS!?cy%!LCY6ex>M!tV!^6>D zsdc6=*Y60u5xk_zX_G1H!y&HRHG7^6nx)E-Z(rUYL3Y({T4I`Uc|y){ZZTQKjJ9Qx z53Il)%HAU;wOf|y1ar;n%?nwXuAOJ~3K~zhnF7sCB z%PtgzSTgJZx(Uw*%`=f6VS=5$v7zKR!W??|Zz|IuJ0y**fmpF*t_Jph^Thqc#z6Q|COe14(!CNV zH!YiKZ!Z1^!l`9*SS&?lF{8q;nWKe9(Ef?)&^#rLs~KZfbfK_@^qJvN-pDO;i-~3a zN?CIQ$yLJm>epqd&jDD`c7u1~vPv&9~!23xTb z0lj>Pv~G-q8t07r?>O4FtcbR&q~~_5KudwKqmE@5g+ppJ@1PRQdvGa{;O#H^8nn6jK>ZGJ5&D|HiKoWz2zje z-Z+UDACtNm?9N;2EBB{zNvAG+^EnKk-!l;4dU$#c&De2LHUcLXIoihtC+5^WKVGJ3g&cH9o#wMJ+C7pG|ic1BD<{1vI@hT zFnAhR@9^3h`C4yL+zb%g&`0jkfJu>{c>6Lrb_5d}E@90FjGwW9GcRAc%+>$zO+3FR z=K1{{9y6u8gCJA>akfP@cr1-mxCsWy(^&&Kz+>o#E+CA@qv&c|(a}~@n6E{Q3GWX1#j7v~2+*1dZ`6rVNGo;z?H8vKwT{77;6@${vmb``+*ur1zVd6< zNAcv6-jxnSb0jg2%Y&7e*y+^~irEx9LI^wu4IM5POA)j(e4%vX?-nQfNv#H%Llc%P zT1z=|kJtdv1o$dUAfaWRdFw;r2?M%zKWGmIOoabg2i7l{kQQf{0Fg-{3X13WD)?NK zUU~oE5*x~9@T0g_d2|V5yn^;ZyN&xP6a24oO-!11LOAOi%_XY_PkR&OJo%`hA`@3m zitaF=Va?5xV3mo2n1LDVq{tR5;)vIihtma*=o@E+vpx{5$})?XF_c!(7!eMRnD*a_ zULn!GHUy_ZoQ7&y`@r#efiRAfM|dGwaP1Qt+8PT&R!4ZVM4@EOpzZg^Y4(9KhDfhe zy$6r})46NIQSDTFljE#iE5qQ@ceM=x;ZlC{SZK*vV<$0d{B~9o@kbUU3Z6#| zGM!GOpp*l>g2UPz##}NhrnqGt<=Gl4XryCrP1^RUh8m^qwVkBI{7kLWQ7bTu)ZcS$ z!#qR2)CMb?VvA1NTGy}(t*StF^2zr!zNwKVpjQ=GW=dCc!L%dX{u+? zX+}}hK=HsM=jjhU$95%d-|!z!Tw<_E%UjjKPVEq+)1@>tkEd3-CX~Uz=`&nKiV_B6 zXe5MT#=zt|>+tgqkLFCW@u27KjXZ(Morn~pg=v(aXM9NTqgI*=QX~R@AJO0=CC6wb zy6zk1vphtUmNL}whKnxx4n*@De zx<~&HC@W)w*onGJ&L$vBp`XN@V1nwz3BH)wl|eUQzbyX@7?L@q@}~hX^{#rDdrUEy zzpf_5+?kmHZY~aom2jK{14CCa?xZrEa63iK-#38C7=QLN?TWYcWFsJeqX%++D-znZ zp+%iH^8a8QCEz6|sXGK}STfmeY-1jK98KiXN$$*i3%UGJ$TNnroLiR>?##15A&h5~ zuT2-jfTV#QdI|@&ZHB9`kj}Gs+4m2%Z~({IW>nA`8_V#80wv9vN~W zgc_$sS@9d1Uh303C=HpICyK-Ay@AMAU>uX{#{Qn8vk*lOpY3%uNVIZ6hWeM1V#$B6 zwN}G}fSu7{{G0*ZOwMXkKy;t`)bB~<6~Tw;#J~K zd5`902H=CO&lT=(U`g9{#Ec1zl02_Q%RiSx6||>Z`(59*;68a}qWPjb)+K<$i|$Ei7b2SV3T=%oO!HIsEFtk-&c*n2|8L-1~mm5U!wZ-O|X_ zJH>(xYS>;nUzd;6+s}?{+$3tnmQK2cU{h|5H7AtqFffzfl(Dc9Mz)g;ip&AqPe&*o z=!axN%e4UE9MX&mmb#q=z|f9-KqA7ZPJd$FWxYxP#Fay*hGk!t14|~trN+;I-}A5r#$x?YN7N1j6M$g)vNl1NG$0T+99|1H41OJR zA;iwdKsXP%@@!$f^;0~AB;!1n!$P2RYB3lX7Ypb2y0{qzJhIt7VHdme?Cq^Z3#P8(w~pxy1N&m%-iTa(QL&QED(9CqjQ~oYXtaA zf6ODNWf9uz-YMe3i7}y_y~uF`Wg6FjCZU>9$@s{;9iY6+4(19|+L^1Me$+wSCSh>C z#(+!yew$&vEX3B*fEJe0XkAo31kA@>#->w9J?QnAW^OZ(YOFzxa;NJs@j4PE7Z%lV z5y(Ou{hl(Kh|37R?#KKa~X~JW>7pauV1$Yoxi-&6RZKNm>rmx0` z(2RFE?kXGo*6J&0PsS95_M*Gq*LG=2L8tpV)&y=P5;PK#ZM)k3 zkW!!0o_Dg-@viL+iWii3$Cwg8qtkq#{K1B|rmgQ}?CXdrkvlh1i7^V0gEnHwYLf3QE zEJ{B0o3H+bzHiU7>_t2Ih%LfwSuLMHQ(jXHYk*aM&d)A2{@PP54g33v;wRt1vvp+l zxS~e~Q9`cG#lDVyHHknbg4t|vMq!RL4rcSXWRLxe!y}7JxfF7UN`?zr1k0;1_fuFv zdE&S=F6Pqi3J-p#z9pS(p4@A-$&wtnf9tBC4fDCdw&u2gbq1{ zvdy8=ne!_^4iHYipm5C9nm}fMrAIc7L5*!z{Db2M677gD=gCex0dThtbEbWc_R@4u zX1QonXwS)(pi4?iBxUAIB(4<|&M)CVq3g=29N|Riws&VM)Ij;-<<(nNe^!LQE#bGj z17!)bNfoPjkr3fweF#&Btvh3C2pnM8*PHpwjfigXs@E8SP*$NQtDMla8$Z1=3KM@_ zgRb-4%&T{SAg;cZi?ds;84nWJx%G)O8nVD7vTC7!%vA*K9en_L{wjRapji;iZk-PI zZpU~Uc1FA?SWJu?Nu0ykN##=;UZBWb>I)31Q?g?xdNpA&#6UtA3_k)I&RwNlELf8aqnf&E%^)Lwmf%<;kcqUb_fTg%E@9e(9%>Qu` z`z7Oa0vi-Zm#a$Z>1Gc<4!n9rL&(j>3=1vl@A_K8(PdSW><>!>r} z4IXRakxz~uZH3rN3gnP9EL6v5{FvKr6qjIe#dpLVj8~gN%!#fPpMKfrB-~b%y&VqH z3b_p(#vXUUdRlrsq`m;LM;Z#-8pq+3^Za;(5navt9mjPV)7Y$qE7+M~By$VcqTokA+>x@o!s0x_zcF9@7vNxJWmzv@V=E;lDm=-~2s+8& zjVUi5HU1+>22KoPz;x6pz#cF9s1}HAXj8{$gGn!`iroYRj^5W!qM#1-{^OOMWAUzb z&YDit8358dWP2h{Z{>gQhYM3#!AS{yCJjP$p%uO-ExJK1d8HFBtJI;Di}wl@Ja-o) zS;J?hw(!W%s#K&_((a~i5P`tbv12pHM9z8fd)cys9yjE9IN>-I)KDjcIS{$y0bhk$ zNhz>br7CC{1-ycj$g!^mx_UI02~pJ3T|0`vXZvuhQM9 ztf$Al>u*9*ZVA)T5&>N=72W56cpeQq9pb7XDw9bL2hg7Dtu@8D$gYYpBEzaBv?vV5L+;x?7?IsgA@%pV;ctst}tas ze#mnlibp3jaaU>&WKn7zGv*P<`CkkStTVsuNbOOP;cIoc9kAKX)3T#w9)=3!$13cU z?iOL|iqaK0UYYQ=;&Kw|TXEvaR45*>AdFzU`BuW=rLSNZ{5&B>1X9TxuM#Eq*h3 zI@89|6r2KN^XkUQP%dJs6*hnqMHvjT7eojJy_}{QCyT{**OsDbfx`4e^0P;yG-H#z z;n4LRy1Khqbv;CI?CnLrt=>y%(H$Z}n7)w^eZ>dOQ38c?T8~PuuE3nDl8dd;I=Ffp z`XE2rRAlnmV)PYZ@VuhP{bx(K{z4K9J`c*{m-YKmeLNC+acg9}e*8E=MvZ-|K6w{D z0$Z!K1;)904@w+D<(5<@R1Rs&AR8k0^3p=B=a&hXe0DRRDa(I7u%3P;86KjL*0g;N ztd?Nj&oMJoDTAV8%-q*{#lK18azr5}DP_rNyK9?`%dSwa;&J=z1S z!}J9%M|r{J__zAs@F0IQHw+A1_zxyS3Fp6;29HAu`xbuBm0&53RG|&}F`C`sZixk)TgmWD;5^-dZHYOKkS+HmgM?61zLe@V zySv30*d*M-mX%=;&=Omjil=d!IwAHQ7Rz*^!~DPShJ&Iz>y`8M%A$xIYhwfzx|#{L z&ZGZpmpVOjMJ{I&Pc)YV^>0o2sMI&-27c+l9Fb;3LRWNeYs@bbB4_Gd6e`5DsUHa%J}v zUNbS1?#vb>iB?`zl*V;lV@=FXZWhA&IBC`%JPJ&vy{4Cz)|+Q1Q0;h1Xooc(S9 zi3ICZBQm(I9TWO0XpL62g=1}k0A(==%rHlX;HL5bd+3>+0ACui^_6{`E}9tsAg6Y_ z-YJW-JMJU47}^fHqdUtB@lEGt1I0SonxuP_KZ%4LJ;gMmkvbe$E(Wg9(vXEGq1;k-CM=Kb~d+z%-h{h{tO1zr@+_Nu2 zdq*Y*!tSNYK@qB$CraULO=0<|T6K7Pa`on4lPk{L*U0M`3j^|w!@A)xL}0xW{O2jc zK}b;NU4`7r;wE9Tf`&eMBXE<9oTqc}RLrRh?E1)21*bKmIfaru^klA2ZoJ^06X5ZM z>lweqMhI$(;aI;-P0O398^5opfqa?%ku^N9b!YK1S5JRJ(50BhJ)jQbP}EXSxdOyT zI21_tdv=Q&flZ73;yuf$m%|b>4;V47(~u7Y6HREMe%`u?FdfLc7`W`cuU%w*P69{HkLlW4v4jE+UImL5{#}RB9 z5P!LO(>oJaulwKxMWwEgiTppd&MjAtB!{8;NI=y7H#~Dod?-8vbfW?Lwq2Ev2!%u* z2F^5Ur*q=F;+j#gK-|o>kXNNk$Ycj&m8QJX2tr%xOzVD&q9$)TRaq{Q%Km-390?fI zH_b;r02j?VEzbw+`rJ}PhhIwhr5@BYQc$lNROsh$m<LHwC1o<>|9vR?y%5zkF9yxPK zrc7e{mp;l8Z0UU65tdVN04i@vn*HMNOE+O;P&Zh>(?c>S6-XebWZqmbcDJM zK5C7}F3!v4fR$hL5TZ_w+@4}%o>QVU<7M3fYutM8wo*w;soJoP?@ZORy`ZPn@tbXX!sX!0py5Pnp_iak8 zi@e_+${b;ZPf5emVVLmv8-Kee>RGcx+bv#UrF ztn3=YJSv>8XBy4z4mnjryoz|q;@+=7Nud(N3zh&vlurxet{-;O!DLvUSk*%2D*-dK zc;jFrk5*_dg*cJt(IoN(E#*8X6T=3B%*h=xqfR3z4Pv440Y{mp#Up6sEL?9?y?aNy z1g#x9UvD(0m3h66*dqBq`Pi|ONvDo@{O15fUze5A%vc0a7RaLlBQ(-(vI!`>%wxq_ z9qzyp%K&!1@Wxl-ZM720lY*xCnCy?KY;7U+KO)%nK(9}>?G`?|Ewri_qU6J=$8jTp zos_$ww5U;$0~~y2p$U*B_jD!_m&xddBDUAm1%M8q1zV|v#G24Au6@B>=~fWsW6^gq z_i2nb4x)VI^j5eFh-*&*Ajf^X{PRsvMsP`+r@Wt@G^!3-)t6X7!4jYp!3<)d-SROK zjx!5&-Vm77EO?JyUHgeqG4xV~k0)s`zTqWu4OLs>8VhDG8ZYmSM0-hS$I%v)9)(KZ zVw?`(Fd&tFvh~6xfxMsLrSeAtahj(Hqpd3 zxe9BtVZM-!U}Cz(4ECiMaRFaW3wHnjAOJ~3K~y(e0q4a zs3{eGDCbwOw1DstL}HvdKXr#QF^eSkk+RV=J!PWqc7xwWErkd3n5ToMS22CBQ?~lw zOt7F@Istu~Yg55DL_g9(d*DL)ARbOZ>XUf znzt=^Y&UAC^Bj-1c2s!Ftv0|(s!13>O|%Nh+}y9V0Px)kAK2%|cD7?(NP`>gjIX~_ z!WFmw-5R+HnVREpM-?y~wVE6IBkv;@is5Iy;TYru)EV2mQ!#g~l*foZAf%0Dz}DH~ zB*@`8@#`dwP*J8{rpktL?2eqvS9{kPy&9PhoSyYWaX4)Tf!VjFUm?{On*$W#McdhO zR9Isa%gzbn!9){kGpt2|k4K-8v?9X%b%_rps-G+c*ey3cr)u|oH2y0^KZ(AGtV6!D zaVkz5aVLMLOUX{K3T|q>jhN@K+V`kpkhaQ*bk`p0?zBSyj}wAaRH11saeGJn?S8Vt zi1i3eIRe}f*L+Nbah|+%_&Sd(}wU&K3nIAb}ZRuP*sh zAZUUQyFIxbx{8}PEEPk){xqR z$uwpG#u}_6YlBXwW-jkpeUA6ClP1z0k9&XP{ZSK+R(B%Tx}&7vT+@zO20Tx>YeFWm z*lg~Em+6x3Djdn<)WNq@rKJ^132e^i=?Z1^l^qxQqKL)Ks%9UCo-n*$TUt=5P7Aht zMLpm)D2scv+NYy<<(nfTx8!*UF3Zc0vc$P3bzR`Blbcg13e^yRv<>54lj;~-858s2 zu4eebK?lmG@9O}afJWt)RaKFuw`%IpxcH5xd>W<@f!6GY02C?3936E6D}z|$bn!&l zSVkM+kc%m#kJhKFeQH3ulWsn!8jCtErzDhTss%vT2#gkV+VL3Ii4%DgJ~|U9h+U5y zBV?VeAivl`RA7BBwuTj!_%dGHe5IydKf&Q|+CZw?C-B`akAhn$-Rf}s=D?w1JvhkK zxRMVGK@g~49W|%*9&L{oG0)-QH(}iI_)B5L4H|G)j2?t56EqrEY}xUqPU^#{0Lq{{ z@5tI@wK+Q>F^w!`Q6Aq&3OZ5PQzTg!$f<*EZyL=zzCqt?Jd=__b3ZX2DWRXyW^G3I z$GJ24ZFIfq$8{);My%=#80HNLRjrxpqun^?{nnfW4>(=br3%9NZLVh6Mi=utHQ{q@ zbU-Tiqe==4Wl**+u0UNx0JGKinQnnc*7>lQ{3I!gx&``Dd1_0gfh>K>%EUv1z%6IC z^z#g`J~I*0?al*;(Pba{NewQWJU}g3GXmW8bLO6Cr}LWUH1GA;VCC-hanEX&l`Gzd z%)Ww=;A{a62LCw?W=cqxF;5bjoeXz9+EqDMgHcNe^~V5GK&`(SKst5QV;`ZxDD+kn zWi2qt*o^1s@c_NA%#LU2M~W;nGgGdl4t6(W98~P?!#S0QD26^Ra+_e(x&3WH8xgPu z=!(|`@pY=WRqfPz2UYr zM?xXsa%1&hh&uc?dw^IfneD^O*~k8~zJ{`7KGQPQ>C$>4wQU{DpQic!+#YL2kE%c# zk;+ka6W=wOp4$O)=@;oqDL*L~etHNe`7Q3~IsdIZ+(=HHctg5_`?pPbprd%77&1B0 ztw$KGcs=)PSgy+=21(q_AspBy@nuSg|D(iO{lnMRQhsa$)OqWz72=}!;TrtP&Qlem zs~3_n3h8)3Ka*VH%B(^qmFcy#jEb!$C*J;~8n-9)=op92>!|ahtD0w*a)=>3O4bvn z?kEhO#I$#aq;^ch=ftH((Du)j)j%Z6 z`?0TuS}sg|))?^r+@W7;8CX2C(-^>Jz-#EYNAVI zlFXP45p4~u!t8vjXHb^O+sIalgNnB$$c7f&fMM@4E@`ymM6jOOUnX!W_okCeXYCMN z0@9xXiJ3_iwMwGTcT{D!xQ9)2Kqv&KJ7D-L{7q=K9zzG!D*1&1#94YQBs(IiBZZK0 z8nVmC8xlbXg(y>Ysel1F@D^y3O(q?$4n#^nJiK zaVaDG!@0U-Q_0#jA2bRJmD{}TG1b4M&gbtdGYQKk{EF7+KV+RpvgAe#M6u`y0QbMJ zVu*D0wbRJ7+K?=m%$G!RLk{81ABodE36^O}EyfT!AO@B@4NK1Q5`?g6t`UlA+JSh^ zrsIS->S2}zCZ#%9FDn=(+6G$&!ci7k-@v^f$NFKtSRx(U*k9_53$8^ze0jr}sOVXZ z@ITQf&L@yYYF{OO2DyJI=y&~t6X_l^^|Ozsk5 zcPpGoau2Y$RjcEgbK3uB{ldTwRcuu$uc`!?(8+RFm9}}1lQX=TJHh%h}ZvmwI@4tRSxG4s;|3XJcy%S(B&Dm-1rmO}B1Lx$u&) zPu#dKhfkz}DnGvU9H3RCgIo>DilUB3ynoBS+|wxn%zqkM)C}$6s9t9k#ofsl@+=KC zOI0X5M=uq#a=H)yH6N{4kiLY0$F$^cylw0#Vy;A^3x|mGnYf05yizjE?Sy`LdKvLB zqvb$_r?Fri%#17pqZnx)pwghnUr#K>XnW{>d$S+Ow&Sx+iKi|eiO-79 z4ypkeIIT8@Qr>cgMGGuloxja8SUK4#Zu~+%&b_X7VuHY2Uo^nPETAIg*f(;Qw82>e zn&mwdsS44i`p;L;YK&>uy(Rt@x&wI_ARip2rH_RbMZ2&O6uBa0)jpN$`6<`iO{W9@ zWUu!i3i-_S^2F&^;sea#J&JS%_k)=+%JN~hh+2|W;FCldHBPU!*>Y|`i_Dw0=kOKF+#xu=3{B|Ty4kVE0 z4llD_tdhXiHqH>^0yW^;;V5@WX_>;Jos6Qg822F!7op`XxHy^_2#lZ27v-RlW8gturvMX9@c6O{@#Acalr9S|Lpmz8ovy1>FXcBJbQubRc#$?D zf|57-0Xi$1dBr1iXd)*@q_I%%zx#;k96sBf91~kg-o*K|zydGu6RW)cY!dv9OfX|5 zk1b_3w;UzHAL3kwsi!C}q3@^tmpgiRcU`{HQfrWLCBV+-~Em zy(**LG%6%zr=2R?0JYT15dWtT9nG&o*6v1td|=X53B)jUieoR9NSg^FdDcsKy5%^ z4e^8nuOpojP&AcO;p}n3M z-X_FewR#?0I}^6TDgK8Q!feWFbW2i5HgT2~7)FVM#+FN=1{Pb)c z@#*7yA6Imgb@WO?p+9YFwylxHj&8!|NI4*}4~HtTD)9;>Fk=pj87J%j60cPyiX)^= z%o%1NSf7ij<7=|5w;w$;#0CnWqj_DaZMQ?Ah9>SpAYZG8pEY=#}X`)VC5>v!xaIm$6^R78fRsJ~=(KbhJ3uArn0QGVW6YR{Mu)r!oPanb;Hm4PAx z|8K2qd~hT+)}l@a4ZialRvNAr7T|j4`%q$2tGO4qP-3)3Q@}F zVw~FS4J`?nDETGI=XlZryYK34UITD5WH-(wCr0WhmPHW=) z){ur19DORbz8A!$C;(KfrQL2BH#U(Gzfg=)+(fk$_M`G#^7O|6`XxNC;O_mze0^ev z29|o2E(GT}^Px7I%n$$0;220>mo>#j#v3uZjRyjbec#>3LsNEW|O5j z(ccR{PBe;JFJ%PxQ6yPU?(kh$LvtMgqap1x^ zHk$*(7!R7am>}^osh`Hoo$>&;A#o@T04vf#c1W!i|Ce*<4U9q2)8*Ggylj!IBBKT* zA=H^r_aRs<6_e%ymK;;K8diKK!ZjkgteV-4$aY=TLYq@+j=KQrV=I5djIsY~;MD94 z1p~rST+F3xM;$*`+hC$l`}_p@=-GNEBA;Ii+v=oP4awo_ z(f{U>eWQart>}+`^}jlcE#VIUg*gw_zfwqn%b^Pxy-7}7B!O`boOECk$nm``A~vvw zC@CD}+qfHM~L^(%00Ark=KZYLZ!!Sx!=mgE~X5g)6 zsiHnB0I$3_P-s*cEk!!vR<8V&dO};UI*rD|)X$&^;yK|qd&3LfhWl5EIMsTXKj4J0G)3IYzh09g15C#Rj zXU8mHfaVwLY{Q$!tO_^HOpHyG@2*4IMLe{D@WTiv4Rk(0{&J*$d?J@&Y->l4V#R`y zLCjpIlxuk}u?7zK^zOHCS>zgMM^K&YAgap6b23}kho;Abom^h{9gz79D=tF_V`q<%Oj7m!AE z$J{vk@<=IHm)=}Nl`?Cn!b2hf11ecsNyqUtq1WD{!TdWXcGQm2M(?~e&=iWt@YJ><21Z&ISe z^E@qQ#T7_8i-!0She;0H6UQMh4sbjF6s0f_`WsvD!u>E!x2{5EW4;YV9L-6bVA5wQ zT^=&%25}+=Z8DLEktF7G9Ku1;LPMa^*^kpQ@|Cgs?bUxdAY*aObTs%JA9)`qyPGl=Ho3A2lQv1I9SHDU1pBr|4KHK31kj+tp@%H=u za+Jq+3>zJo*3GBwI*PDONY$0@Q#7F>W0b-)2!GHi5|`f!^mf~drljAF46T;yZ+;Le z4E1u?FekWo`)4X{hw8fJ66btLx;XOz$)XDk-X#zR3{guu#+Y`!c1ifGVd=8Bl?Ztz zt$R8MEw*-zAIx}AUN2T=9k8u}apuuR7p(RNI+6|cG7I5NLuEFPhu1o`GI{yFPsjy$ zk)bQ*hc|4`oR!cX3QM^W!7e#7VwQn`iq1w`E-W_rSk*8st3$=a32jpjmuq-QI>ag{ z>*^IK60QlD9=NYgM#&3Vn=3S>j9IPOxJXtn02lGhzXNx(D4kJeq&*Oq4R#Lal2MC0 zrqgeqplAl9JE4Bl{W|(9SVe-OCC{iFJ$AWyT_Xv1dIcxo?jPf2_E$Gui+t_t8Y>%w zKo438NP;E7?7`#Cv4i8d19h~rJmT9rs2k>sEnAr#mvRZP=4F;H7>aP3OUbOyJB8b3 z@9+aE6s9{x>No6IVq@Gj6B|9n&0P6Dk-(v-6LM3c+c35v3^>|agBWvNx6^OU$2!6h zj9WLuqdf-2r738u)h`e7t|NY2QZTNj36vgt*k(MAQtF(-Fa{owGn=hkegwvm|075_ zYV?FnTxsm&Ap{&D(#SOpKg zxi+$Ja^VQdyZAJ}WZf4r8r!!Sg~T5I{;UeKtWX~(Z1xHL0^~^E zd{BG8|6MjXzyC}?dpc4H{`bARcr=%E(sJ@llDIzdXMSjhl_gWc)fmTh_0PI`NzDC1 z>yTu)amaG|Eomp^??2Vi^_SCIBc7fkcJFM82M*HF{ffZYKl#VY%-H0 zw+tKtOL|hw<5+r*FDvo+Qz`#3SwQ`vYj0GAx~z_$#h_>R&J+{FK;bN;Kj`$Jo;{HmvlD#s5%dgJj+It&r~bqTeEU<_38Z z`%dD5&gChg<(xSlAtt&|*bw4C?rs*opw(XjY8ETjNYuRIGNvSyCZg9yA$nkeBzL~c338)O5Z9T$!g?&M|8b;I2E`4+L4O59<>V0%T-=`vur zSC+xZFUxNr-bgbv*#~7kyB|ZJe|lJvH-A^dF=z1zC+;K8(@vUuph1~c7^u(q+Ky%2 zTLjwxP<`S#msFqfi?(HgK&vqmexg2o;K7QQ3p-Z?@{!msYuv4s!1*~Q&E-U(eqaeD zVh*HeI7)acOwljAZCp#>!pG7ZgVlIn1c>>IVaq+TWa4H;CuyR0iJOzIoClSSs(#LB z;;Phl`=qHI6=aFSRIRQN6|n;xQPJ~ror^C!8&8Iq3J6FFaVa}|&@~xVjC+9gR&Y?6 z`r1K%o9knGYz9iHB5OLkE=Z2A;KIh~I8cmeq9*clfg&b28wMptZb)4?v{W5M*aAN z^o@av)yfQyfV=F^1VwOSWDd@uqI!)&t5svA`MH^z)8TDwAyw%>y(mgv>xa*(Z>$&c zxcvi^*VEZIuP^$YkKy`DU5~Ii`jNaC%X34vtQ9f}xF7o%+rEG6j5vtX4;LT*>4gIes#mGRHDJ{ zFmp7gECw!~3Qp+MxVK(5KFvzFsf1V`6@~F25njqD@oLiLaN+T$K%J3UDIr7<;{^4` z)g4K?Z^Bg$tBQcZ>y}dpHP;nA;{+(s(dA+eO*ND2U~5RIoljwZvwHmdLRg4J$03ZNKL_t(q7rQg3Gpd;53(PcBts(n;l9I6>e)5|c zN2`1|IrjkM;;}xja}4>j$H+=)!HFQ5$zPwR%x7+DK%v^ij!Fb{eJY~qr4JK(aQ-YI)24hk;}O?gr} zaAfh*8A{eg$U-`OCjEfj&VXn!(?m0@C$FZ2IX@ zQNVca3>uc_^2}y8<@YUv@ZNbFH+20{d{`Aj_KJ+YScQk@Gr z19^qeOkWRR>mkKbCtb}_z8+C-QI5AW@(hD8N>|#%uR_!#%=cRT(C_R52N9E(3sjuk zeojI1F`Dg?Q=61szfdbh>E)FT7(WyEWqZeKs!}Ys>t-O zneZLbEa@-`9{Z{8I7g3XyQ4^N(l8>RchcJ66wLhuO_e$})p_waeWu!cEX4OdkR;LZ z>pv}kGIqaq2b|vbBD-Ey9!^qxZMR$MAsm(SWo)F%Sc#Vef}Pd zkC7!JldImOG=OK!rIAC_&lfjbdZ#9XB*@5<9dI{a*D0h?LEhtd4Kspb4J%>Lx5%Ugx~~|x ze6SiQUc3TL!D4OBkJTO(ULg&eBH0>VZW>c+Zt+$z7Gbi|`Q=4}MmfcwZCP=%Rw!)F zO^*tu=H!W)Q-sqrwE4g4qJ~H=gzE+g zwp!tLU)?3{8~(Q%UPXe#<24*Jpsh3@yPX^1H{FKa_0TwzRAGLudl0Hd`llc~kq<#3 z$WHAUDxlp7oirI+w3UKQL&7}lXlOOh==rZ(LZFX$NlB2a*G7*@YQyHHIS$lwQrLCR zbYo9rmcb)nt!6?Ih0cDDK=hGm8mMVDZYChcQ^{BfY=1}BO6B=-+J<+V`)emc59(Az z{bcMS;$rE-0dhnNkIb&VxDCtv0tf99`7#oogaD!IDns6{cdv5D82Qo?f{r4ihOM<$ z)b;g@%nL)rszPBFNTrs)ms3#7krs%_EA>D1sN@*#%dFy-4_J3W#Oo<&+O0V@7<{6U z5TnAJao*HDW z?Nc_IIDMXyX~AXL6xRflB|R6;fI>_-|L~5qhyE+<9D+z(F|M;*AmZH=I8`#h#+9|^ znTk7;+P#PUz#zt1Gr;me-h#$7m-Exi;mHBZL_Sx27~<$rb|LRy0e4&qD zO!|eu>((5jNIT^$k~JK`aUy-K&vg6!DvP9(gqK8y5Sx$L9MZII#PG&H5D@@7bhI}- zVq+IrD=&A_mcENXl-;VcS%9xMCD~&!okifdB*Vl!MWTD?ej|<&gTVGk9`QV(aU;9B z25mUd8pWLoeE;t^#)Dt6F)vgK%c=^goZT#6mXuP-ew`tZSM_y0P#ac6w3Doq6Eogu zFS=`qJ2Mc*9};1NvRNRh%%|O?PpA9d-$!-!Q8J|>(rhEC0Ly~hYybn3=Sh^{JvZl8 z+1@h0#|bs8I3HCKd}J!?)Z_c&fOh?92IR!&N^!DTk3VljCB>@kD$&+v&v3&+lp5cU zee6}Q=zo{d8_MZ%q(g-0#}M75hOh8TF!X)=emjx$;Q~3Yx;&2tpos z^=1dVQ?Gmj!1xX>$xa3>%pi4~jjL&5gyHxGQl>mOdH{Piq_b(VN|Mcu`iz@?_xSUj z7I1_l2e@VoT!tI&#kM)a~A^xcuM!P(xPD^bF{N|jE%cf2?%OUD|UQn(v9XnxJ^OK z2Hmj8fIgx-WKQ4tGv~=-UM?=*5I+ptKavM$xB~+evdL(wFvbNPxS=uvs72DqEbw!a zNGMvVWl%O@P`aQ~WTkDa%`8*Bs^izPxAYd_cyM8tQ9*qW9Ogc`Rddj08R~IGkLg@I z25_r-aKRhq@xfI%tm2m#7BTrY=d$ z?UdsvNo`o8ijs>NM6DiLN#2u7ANm*|b)|4xeo(VUZfluyEEkNQFOf zTeoJ$4t>ixSP6M>1gnY3);a1s6wTQrFj)r`0D#&Up<@)L9BpXUzD8n-Z8bGqr?>z} zh@~5^&qtqdyd%W2cRV6=jig8LdQO?g{!p}El!Yq^rV^sf$st%gorFkgec*@#IFB|f zt)hSby0zX<9>Jxx;h#4W4sUD3*(nlK8exV2P^2Q zs|g%Zdhr~D*e`Ro#$WR@9Kh{d&a+1h?!=t68*&2d=_vMgHBCnsTn&>-THC~|7nXH} zBwBl_zvk~Sv`8qwFpp2MZTLr%hP1}|@zBv-LhO%;w6fTKEJ^Lr^cr{IsDsvXhu;U( ztZ}T8IRg}Oo&gCtQXQBx_e9}|A+#P3T7Gx2nagIRlv$eG-{W?zeA>y!CY^`#H$W5E zDI>NsKab=V$3%!RsJZqE9|d zSwEzhy_k%;0@l~+T%aftxa3#8!ksASou&$#%jd3PrD>I>N*b7_86Ak_5vM80&Pp~K zsWc6V#FKl5)Fna1j^BCT)CJ`G87d?B6+V#6+jYNFMNEUm<_T*q0wIxJ@KvK{SN9P9 zb#JdKGE@f<5?WG4aMU}V)z=Uc#)zJx4o=C}G`!&P?~@hM=dBH8T=^pJl+)0DR=%tJ zVb8+I@_S0eD1+#KA%*Kr8Fl@pBM*-iA1 z)Raj50mGLSHhXLf(<$zW6sIP(vMe>#YN$`ry04mWTWEjspl*q_*uu;iv5qgQ;5R-% z*XPAA1JT*`)VOirRWuF}d=)1mI~sqId9Dz^C5RP zcao`Q+R!$Rac$?#C3W9{oS2E8?TQknvy8W_hH9xE^ws*a(_Pk81Pkt~mk#S!At|R> zrejIPh7QJ6w|Fu>WOtLW_A=`Y;#&q6qGhN-&t7a3D8R^~>2QSy_ zWJPtOopmum%ikX%1QdKd%%FY{@uVPopkU9mb8W^On3`d$fgQVS_g3$ol5kWUZ|9N!bb(wQQkn#AbHio3iHRS8!UQ~uOG5WQz6N! z@2*|4)_h7_y@0#Zyv(?9199q4!QO*AGcJG~57>u)^$z8$v@7T``c{#I<8G0A_fU?< zDL{#kkz6)x#Z6(Tpi^L~ZZb<3%^5_$BsEVq`h2y%*P)M`>OOOwJHs-!@A9@J(?2!%G7e^>FRjIwlHQ5(>&jK4hU(B@CJ3h-PK>SCtz&t?ay{7~<_am$}yv_bBca_vk>T4=e2aeDYiL(JRGv=$*hW`XA6;*_t70pw%OaBFNHU_J~ddIDc2S-Tz`0=nP(NL`IvmZ(W=m`+lP*KT)r~}Frrt9j*{s|LB$=co=w^c- zV@$m1Q02lgxI&N*Z8#0tB#Pqu1-}sDs2cv@j^a&Phqoocx1LQ$$u%{Mz^1?Bgn5T2 zj)LuBny3m?jTkr{9Nh)(*w#%RB`vJG3?4_GqK1Q_V+||s7l?hlc$q+3EV@=?V~lWx zXRj@M0VOLWL0Y!10s)y|Y^A&?3x@i^<<#VQkC5jBIrQn>-`2nI!+LI}^}mgoS*br( zUo5`>+7o4kLpH42th!UeU-m31v5thJRRo^G`RRu2ek^W*F=^UHlqvZmpWc z8krKV}>Cal;Zk4jNv?HT~km1$V>xJ|T2Rmz8b_!Iq6g0)7&Loe%7W0zZ__4(39bZV;VX zWkfavQxT`k-Y_&yALW_fwa$EYY-iRN!SAgN$#afEXAA5Zl%%WseT*Q z&hJSRs=fu>#Ad>R)q~B>+O*PB-ot6juK3`jtt)Oh>d334g6WvMiAM>#^jJA(dgL87 z?@lEEu;6r_eV5P`)ILwnh;{vm2AupO$Zst@ixBn{>gvR5JK`e5L zWnf?V$sOqTR(?NSZcip14@r0Yx7GY}W1-VDRH|9=i6EyFa|vNG*d27=qwQzPkdS1H zz=U-y$Ou4?O7WUV(Y&SOPf~v}cR3p<(#B0e`*pan561IwE^j$8_8zsn%1!DIHg?Ah z|MumrF*D)NP|T^);=PD$ zJWwV0{~tcbBi04YWIv@6J9baES_0OsZLNnKKB&%<>`hAC!4VGKW{Si0k@y+`p!&E# zB@p}?)RRpr89EG@SSafmAwYw^AVuY1od&TmZ+1$%7R#&S_A(KVMK7aEASh%qjG3`T zSkPJ>A=wZoomGdfB!ZPHF%2zW&NKd-K#__rWCMbM{M3l=>XuXnNnb}x)qYhlaE4tE z$QLnX=&3dyiBTiENmU5=GSW-~A_Vivq6EE{Jt2vaLH$R#9~#;PMyJknbQDOqtG7IB z<6*|ivLRv|7T_J4wbWIGcBevV}go(Bk=r5FYmR-;=`{*Yho_`t4jf3>?-ojiqz|qH{(lFm} zXi}AdP`R7VM!{8NC^bX53lblPg#YYx) z)an4vyOQCM-wc6Z=@lgRaVW(Sca@fn5$q|e#cNNyK^Q%h=H#C_G9f#Xtc{`6W^{g5 zu)P^%_6N~T6)ZUp8dlDqr9z_JW%ZZ;bLE~a4TW_EFEi+p&AIu@1SlF=%UC*d5`Edvj+bpySYP$G?TYuL;a*lZ^ObQT}CLPRcS*=7KC+9vkM) zK*E3uR|_Ci@x`sUZq8mE_x?@ZFS<2ASwMuR$I!;s-o{jBGyaHQK8rOcdIJfcSTG`8G!S2?5kjkd*OH_8G0Txp zD+J&dCrnwIxhb4I1&YoI>zBm-dpAa;y|&HaTpq2kaAu75Gye23p4z;XT#D>Yf5$mN=IV7 z!QavthB$wG?~q&v+*EaF4x^)shiKE-9jB7N%3jWQWcv?JZ;N;Bi5BtFBIZKN^zMa= zYyPpTY)l`;#W*`-BYaC~MRw}dO95>7u{A|tY2WqnXlp?@NkjX^bO}U|LOkF~+)^1?XN zqqcrFG8`F9S(pPc?>uMpwsm_dS}CKqPI&89zNR4j==72gr;z3fzOek z*S`q$J|-7N^f<=nT_=BZYL2xcjb&1)AudR*NpEtq5H||X@y@6z z6jz5b7LPG9V=Rpbk zWOzG1{gI%h-&7~~5j(>pZ%cUUK|;edj&r|rl~B&nik*!i=Yu98lxmfpf%cpZt=66u z=UPy~@W!L2a!hv00J5-9tCqXfY*QUsT2ij;@@iaJrk3$Z9X8}`X=z*)Ppw`^knz64 zI&@|dOx+bW_HBD5TKU@LF$0IP5 zY`Y&tcl<|qnawp4sXi%V#GWFlB^sn1z%kJM*ZYjtn%d(Gl z>(chy)ElyqSkF$7j^l&SidbM>Bl!A`^F{j&Ff#F-@)2urn40pcA4y*hJg#BKF zk3ENJ>@F1yHon9|L8=U|xv!^?f~u^Zk*&cLE&b-HuEMw$8^U9kjnQ{Z3lp~a#-55! zA4FBQDpU^V96A>P>v~U`X(ye}1jwUYm-_c=I<9^&zt7NT8Xo?^V}EM1dTd*^NBG0n z8d%599_1b$c45aL`yicQXcqfe9W@}vxB*nJvGZiYh%z^)S>3| zM9b|{;8eq%%_lY$t=WOhMd@3Nw!6Y(Ggo0M6UB3s1FUlD(nQ*sQfrnWRK3sh49WD4 zXC_w{=n->eO(loAdlRP|5QMzPMx25w?Zqi`xv;vuj=Nx=mq*>Kq8kz<_z=rBRMvCw zIhH4!mHO$^)zLURO2n#lf>d+%xx09X5*`auMVRVv`^v2vV7pY802yPrbBxb=;6h&| zEe)091LH&QFl-xI>>_kMj70Z_P^k~1iC!=$QIS1}9iopyQwI_*pN&;4!T>_8kOj}{tv{7Et zcu*F33jsPr>jejgxwwZTQr$ZtshB^)K4-A{32aifRSy|>G%ZciV=c%zAyq{Q%?yh! zS3}X-4&$yn(zgSyF5rJp2$@`HCmE+%%IJfRHR#nE)*nS0Y$zZWCX+J&EDXUbkSy2q z(z=vp=x-)i7WD`<5+(zcDQhWQEbhSYen#pKERa`x=nfRxjT2{;jFOdMR+i z67$B4KVoMK;w<5Mf`wF(^hSyMgj&?uEym|>ZRm!#QZsqaGM@XVGM7d%*$mBH%+;pl z%CwI5XysdEETC=Ngi3JXSsc4ihUVK0&)fv;jNUU16hNe|xUr<1$w5BWUXqCc$QGoEt-`HbV?cZLs4uRAd}Q8eR0{7m%Jr zVOE_!3)tHLyU*G(E{FDx{xToUq(!>z$!0L9S$WICdoOqF<^{!Og~&_zUAByPipjAV z2_&aRM>*VgE|g=o*EWFHW!-b6GX8WP;A$#6eCfXQsO8v5N~#bv`Eezu&aHL@jaxCixB=@pVJodgA`s5zq^6WYFnD(Oxqsr z&V~9+OYJ+-7%Y+JJ2Z~TvuB0vBh^}FI1j_&_OiSwowm1y&5--2T9@;?d^$~CY92zC zF~I5P(Pr3q2JQO%yqKwUMkzpT4;a0ixVv4A5S94-J{Q(15JRQcbGiS$Vm%K#nY?}> z@j2H7K;pgA|**t`kSyVQG%Cyl$1Fzy&D!hd_U(dkdK0+)}{7OD!Q2oEQvGlsH z*0t34+Dg-^T?0!4_WNNTX1WwSHB;XNhIj48`eglFJD?fSZCTjVmEdK%3 z2=)1{G!) zsLoX4p7t13^i0VZj=gQy)73Qt}t|-#Od6}i% zAeP@6w%&4(6b9_T77o+OW_w$qCCBCo+_|2XT^}-9%QZOp>xcjJGlh*m#!#X=mCJo| z@i{a9fp5qS;NN6OQvUme1+FDfJ*Pxx%QKca_Ud)_Bi? zOwP)7VC5dRV{X&n>09zBm~>*#ZT&2);K22VhAa!!b6(azkoL){1Sy3|l7XP7olq9w z$o~FXu_PB$@$Ec-bBF+v-Pq(2eC7`MsV}>|$vOKK%nggb ztMmOz98*H2VbrlV=6G4-M_ixuNncs|>==#-wI3~99sSnP;@$-`<}UY*6sr+5l@7?S zIAlQ!91mPP;NCMAFENWYeKd+|tp8PXt{Vww^}_J8&ITP`7AQ@yCiuFL9FXhzaE4bu zfT7P#>r_nH!Lg5VeJ-tLj{zw@UVOok*d-<}CDh=3gcGrr_%K;mVYuKT9EpnfiNeMr zWPKl=>4(3yv9|XI6sY3@TAF(@CsWs0&ih{F*N`P6r60Kg+L+D>D+aOiGic48sOQ9E z_G)l8&CgxPF>|<1IagqlqX?jjNw0aq{|LLbWjS&X=B-i{@c%!&nQ1N(?n(CTY?3)W zZX1Lo@SKqLy*YKFADR`H&c@S*ttl#d3`5r)Fu6Cs&u*7@Mul^t9Tbzc5g5g-k$^>AmCD?MH|f0=u`p4 zsOhSn^*-@iIEz!|d0hnvGA#o&W3{I||W7WiE&7Jk24mMh)Wps9@%P{?!Ds z!6^Z?*v$jJtW6yMLM27479P@JhYQVj>L&RCBwLgy4XX9~3Y$14I5(fVc2XVSH9M>Q zZ8eZCxLiN#FF^esmxT7`90V@oEgdGJ4Kz!nn;o`sQjaY6zPf_5xkFS=LIU++?kup9vJ{wPK7^W7PX7R@IKD6D`dj5@x4K4}eG+|!rSCeig zoM77*{0J=Lu~%sO7?Mvri>Mgh4KHvc$CMB!9EOTnHOy4 zYNO`;q30mtWfws(GNX*3Wvk`z{D6QqZV{(esXEyd_c!7}-obNPk(a=LDPTlg93Ez! zdeL3>LDca(wwhQf%Es$}$>(5*(*;dlN8#PW(Nphfeh$W}1$+KpgMPhiZl=^G=?AxQ z9i^!84E>>b*?Pv9ysOeDl$M)|GKyt%Eced$T_Jq&;mxhngk-O1xLBcWA5qA=zz9~xP3@xP()ez}kQnsD*zFLsrw)ZZa>mC+o}SA}zoZBjJF4ma zxP0!pbqW4AunTlrH)~^jNT@gmY!pnNf|c>If;k)Z-(m!`_&k3jDUAj&y=kbnUvzB2 zWPaU?51Q8EXzmY=fP`jX6p=vLmq9&MX=R7kssNH%DqblwEhp4ZNqDj;WQFJUu6EH?MPn^5F}p%lRKcmSz> zWGG}y@gMf%(0N0dTE8RQjcCK~QvH_!s~x6J6~r`{*`diE5(m&O<{0A!f)hxOgdlyo z8yWAkWOrQ$K~JP#>%k@)e+6Ke3ev^B02IBW!E7ZurdT23`4I~5HK3gP$YUMS#+M9P zV&|%Nq8)aF#jBXq6c@xLen2sBsq43buRp$2bsVd#;>rO)^~3j0xe8nn6T_lr8y}Jl zl1#5(AL2Q9LozR`4=ro=q0~6~VimEGpP*oq<8Oj2fqYQc4^SqD2n~}ISb0Cfs@SQT zh|Lb5p?u)XJvbvYMW$s5<5qyUA~vmIcEh$Nvh6G@#O&U%+gXF(2@;Bc(MR!5fQm{? zjm^JBAYY_Px8|e_`qV3JdN`A?xQw8(o{cizBLYEGJ7#cT6B~SkPew3VjOulCu0U#z z7g5KkDtU^R_VK%-RE+&BBbKmTO9fzkugBD&_~728;}!AirtKIQvW`w=kD&8UMVK5D zsm_*s0R>|-Om>pML7`{*Yhku4K1|7iyS(u!*T@#5t0qewb5k#$Y^*6~IE=a=5VO7r z=wQ!QqjBHt(IT#M7Bjtpb;&_#G`+C6vjHSeJ~0eC6e`xOENT+(L6+P@|B6Z3G;`w4 zAh12{Y*iWc^fAE_le5Qg=U=JYyZa^O-9!?&yk6L_(%DsaA|HCr)8Mb{NW`6CuxQNB z1Y}IX7eZqcP6eSHCf&Q;g)|Z`p=Hc!md2Y@IZRw{e&*|$OEHaA3m`Am+SIU#3o!33 zQHw-V71?|rXSAxvN*V28&EXz#8waERO5Gwq(o$so(rya(-2>LY->90RdmAnrQh$Ze z7U93ke^H~emaQYNeZlX4TlR~Cevx0^{Yg6Ad(@HWgiM~?pL{~|JF?pkq)`S{yV2f+ zHGl!bw~$0{rZV~4%zDyh{<(s< z@A|s&%amn%W5)O@XXFJwVzn2+k@-x6`Z$kTWraT+en^Sx4CHoe(RxCw!Tx7XF6EckB)eLoto>g@7|%~^l? zh!0`Du%$nJxQMDj4jQ#S~z2NmB_{z3Px(bK>VqzGfv+}x93WNL9Ry_^nYA=^m(*SeqmSdAz>)QV z?93DLI|Ah@XuXPenyt3h`ZVlHkoJW}09+Is@o#GiI5P^Q7=+jYshI8^t7RK_-PxYA zmV*Ayk`41SVx^hBsJ;Gmq|1#TK#2(6Kr!cV-k;6*sL?*M2;-;fz9&KxRKh9P1-B*3 z;WOwFrb+C^`;^XSA2kQRI!5uR1i(7s;i6KMz39XSpG7J%hsV^UhD@+=JNb&=})E#%l^Jx<=qaYSEyniV-z&F;4s;@~=gr!A@=0gr^#VRbt13~DW z=?u8Di%>;}N%87BWGov^#`10uZiOuv63MTDOdQIwc}H;L+p9b+&=kDryrP_K4{&#j zax^tgF*`FewS@}@U)|mK$xu*xZ49?22X8_Z^Nf>uY^gaKIKP3*Sv|A7@OiC&4}A#! zP_DRx+DbuA8L31V7tZ37N0Gm>U6JHb7a_IZla2ba1aA}JCRpsZjfJ9PwyI+x@E<+@_W^Y4Qs8z|k z;>Ec|(OHE367{fkqgrdHwA!8b)yJ1ZE3i5XQ=ZIy*JLPI!OT{H%48w zGM9)^XMq|M5PS6x*L(SnS@anRv*!k66m?=2>kjRwZ5NM61WuGSe1dKF5`J|nK-1XQ zG727$18nw$;A#vMZlLzv?ardk1SzAv>Xq99zpRl^=(Kq*+*KWJ-UUL8nB<`GJHu8M z`#Nl&22;W>v&&Ar*j@D-npCSl+6V+t;i8|a5raMD^)=&{zBPl(yrFbw)Ld+B0s-JPod8Qpnh4!Zan>>9Dt^b(lBX$)2$0dU_=O#qEu3!w8Ld z;IkPbs}Pebj9j%T5`jd!4dVRl_ej$*Z6WSz$w9D_!^DkbRI{Isd!%p@5-6PU1#EcY z%an5c_OXB)5eX(_SG|Pb;*dxavO!2OwlB{R)5M+`0+|T{rQTNXB=sFrxfwGa-xWXD znb7`by8r2N2j8@&UX9JNLu!>*MlhtJViGAf7FODei5ObP%ZCI1y@x6V6G}8X`d5!k51mQdKD*e;L3) zlo&^O7BBjL{$JU(Ew_%tFnb0wp#J}dcbixe04ixuUwTU%Tb3yj7wWPl{16&cLZ{U4 zy43?+h~!#2mGv?`w#zIE;Xp45R-W~kV$JG_J)N)65AC!Hu8e$=v5_cAfby=HrBN=h zBw7iOJ?Qn+eQ0>*8xtr+7#{>BAW30-UN0z`9M`Jwr(@0kA6O3PX7qnNmX<4KNpT|4 z2d0p0l(P13)8rt%z}UL+J*?LxV02ziorhANwyO}^Wz2N7c(S}qK`GMtu`v~^abTD- zlFY4*YWmXS{^J^Qj0I&}ws4V=vQr@sz?QO56Tm`=rm(;1yW!H!o z!1qb{xmi^-p4=4!U@Z0f!FZDYYDBWq<0XtAdlR`hTUg5x9mEp};;Ko96LydroWv!k zS`G)@#&~KWA=ZYY5Kw0v~s}{Wd_P zMw#k9ewBpKNMt`U6CSV_O=xs_y= zbYWwlvZ5RF6-CK<+{e;MFGOVZju_J}*WF-9wnhuoS(7^^bDi}T6mIu`IjNHq*+iym z5!Qs*Wyz~~E_QY&#obs0Lzv9x&wJ|vt`3zr+l($D6P3#L{*pLf;ucW#fN6mCdTMmH z#!2(gSE+rKfWcVULh>9p!yl}|A4lJlUFEXGnI8g&E8Rrxn+icd_but^GCfJ53a-_D zq-fK}^VE%l3_Z?B8^(d+x)fqS==mdu`FZtaWTzvDo_McNF*oi*nYClE7kBI_ot1}y zuaNGjk(o$QIv6q~YL$as!hnrso*FnHz_yMFM1js9()MXY?2et(UJL2=7`i%e_+sU@ zjFS>l8=Y4%g)Tc#j5aGRQA_?WVV~z#4eftnwhn<^K55ZH1|m$$ zJ|0j-3UG2gV$iERINySUPd|j4%*B*2;X#@xv-6X&@}q#iiVu03vJ!Hp?>h=y~e2@lz&B!8C66;&E_V zf?4h^ga{3$-xeF*LR?|3yxEGt`Mo0f=?h;LPdZcL*|n)pXYle-PPHlSSdm8|~ zJva~M2Y*@v7AOUp*5ctdj4poIlmS|!kuAszek45*RwwQFs@F?X84ODXt;Gbk6z2JL zxMtTy-cS+1nOydKV!S)+(Mt$*rWjMBq-^Wjgz#3AI5|2wHe3ng$1P)P-An%M^=1T#CE2@5*$C_EzNXzL==k*yJO6s~>F${O&5c z8fe>1cBp!Z1#95Qi}#)}7NL)Q1E9Sl=>n7o50l++U7B*`A%|*H6h9%-Gsb8UF$K23 zs;FJDAt>5wIt?LG09h;5wK}?@uxi<`=80YcuWoQYMfPh!7{=VMMg6spT!BKqjsr!%vz}_PDy(jL*)ZaZIYT7cY_3nGGW|!xA2+nB6fs%?~(5gn5 zlJ$h@x;o3>jLg9filxK7mJrO8c}AdH1^6<dw09L?1eWML$dC(d?eWpQN}j{ev#QM|4oagb|oMq@L`4j`~;-YX}?}Dmt45`Jw{| z@AuBRuLk`Fob6cC2pw@G>u@|38R(u0TSAZ82Yz(qOAL!)@P7pM73^C)kheG|u`Q)0 z*LAf`m~|z_5g{%^UUT>|`6&oh^tJ}9aFNNeiymuo`{ZetkUfqUH*2#vD6KQtP9_VA z&sUPsG&R!POHp7J6Sx_ap_q+w{H-jYBmKK1IT2BX@Lh_`%S(Ba**p$sD<_!dN6FyI zWq(?PJ&xi*hX}2O$WE4(WSb}RWR{m+3X)fX_8fMaaCNoPhIg&q$TW3F5PU=FPtU)4 z&O-%o=<17Thk)qHZ-S~d8W+;by!WZ$H8Ho9KwsMxerP-McwQ@{h=jBU+UGgQ1btVQ zTi*Cl3Wa8N(Z*x?C`c;-SaYB?%W&v4vbhteRPFuOq5zlJ{Sy(r5g+d3y$#uH%BJ;= ztXAd5i`MgIPMM*1ycmyoFa7o!9b zuGaS@i=1)`s`GWKXW|(NkK~oNEpWRsF z-Kl0s;H+>naPB*^%itorXu9V1y|ufMG&PJ7hipOE`H9#y2Z{()cfsZJtCSZaw2nD*dJNy{ zheU8_L7hjxpjp<#n-jF9m+GZvstQ9iI>_9>=wF1vO z^}C`&Hfpt7FGfvl3cU3(b(V)Bbq8o2x7L4d$D7_i-v~`s6B3Xg0GX9B-BC&2S<_{2 zE;e5I;Do#n9x^>%F;O2z5%v20J_2ALfS-fk51l6*JR^ih%rG&*BRs`3SjJDN)ZuUD z1kJ8iO~`%#%!)Wcn|!*qeLkID!rvpT4UK_2z+4ABwR7D3Kn|by{#27rgt`8ounfa( zxx?4YC6BT~<$!7i@QIU#M$z{~)0Zj}KWH9A9G<|ngj`#h-g;flGp4yw*&x2^lGotP zERB&A?P|hqyNCC2Uf%flb$@vke|y*c{RLk>7JoXxU;Ysf;87?_pf)G|9s4^6D0&%UltPk?{9-Lc&1K%-uWM-Rc+MtXF$XN0000yjj literal 0 HcmV?d00001 diff --git a/frontend-workflow/src/components/Image2DrawioPage.tsx b/frontend-workflow/src/components/Image2DrawioPage.tsx index 02ff7c38..be021343 100644 --- a/frontend-workflow/src/components/Image2DrawioPage.tsx +++ b/frontend-workflow/src/components/Image2DrawioPage.tsx @@ -204,9 +204,9 @@ const Image2DrawioPage = () => { if (userApiConfigRequired) { formData.append('chat_api_url', apiUrl.trim()); formData.append('api_key', apiKey.trim()); + formData.append('gen_fig_model', genFigModel); + formData.append('vlm_model', vlmModel); } - formData.append('gen_fig_model', genFigModel); - formData.append('vlm_model', vlmModel); formData.append('email', user?.id || user?.email || ''); setStatusMessage(t('status.processing')); @@ -611,12 +611,16 @@ const Image2DrawioPage = () => { + {!userApiConfigRequired && ( +

Free 模式下由后端统一选择 DrawIO 转换使用的视觉模型。

+ )} diff --git a/frontend-workflow/src/components/Image2PptPage.tsx b/frontend-workflow/src/components/Image2PptPage.tsx index 1230b844..47762405 100644 --- a/frontend-workflow/src/components/Image2PptPage.tsx +++ b/frontend-workflow/src/components/Image2PptPage.tsx @@ -221,7 +221,9 @@ const Image2PptPage = () => { try { setIsValidating(true); setError(null); - await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || 'deepseek-v3.2'); + if (userApiConfigRequired) { + await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || 'deepseek-v3.2'); + } setIsValidating(false); } catch (err) { setIsValidating(false); @@ -268,8 +270,8 @@ const Image2PptPage = () => { if (userApiConfigRequired) { formData.append('chat_api_url', llmApiUrl.trim()); formData.append('api_key', apiKey.trim()); + formData.append('gen_fig_model', genFigModel); } - formData.append('gen_fig_model', genFigModel); } else { formData.append('use_ai_edit', 'false'); } @@ -553,7 +555,8 @@ const Image2PptPage = () => { setModel(event.target.value)} + disabled={!userApiConfigRequired} className="w-full rounded-2xl border border-white/10 bg-white/5 px-3 py-3 text-sm text-white outline-none focus:border-cyan-300/35" > {MINDMAP_MODELS.map((item) => ( @@ -546,6 +545,9 @@ export default function MindMapPage() { ))} + {!userApiConfigRequired ? ( +

Free 模式下由后端统一选择思维导图模型。

+ ) : null}
diff --git a/frontend-workflow/src/components/Pdf2PptPage.tsx b/frontend-workflow/src/components/Pdf2PptPage.tsx index fe819f0a..abec48b5 100644 --- a/frontend-workflow/src/components/Pdf2PptPage.tsx +++ b/frontend-workflow/src/components/Pdf2PptPage.tsx @@ -222,7 +222,9 @@ const Pdf2PptPage = () => { try { setIsValidating(true); setError(null); - await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || 'deepseek-v3.2'); + if (userApiConfigRequired) { + await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || 'deepseek-v3.2'); + } setIsValidating(false); } catch (err) { setIsValidating(false); @@ -266,11 +268,11 @@ const Pdf2PptPage = () => { if (useAiEdit) { formData.append('use_ai_edit', 'true'); - if (userApiConfigRequired) { - formData.append('chat_api_url', llmApiUrl.trim()); - formData.append('api_key', apiKey.trim()); - } - formData.append('gen_fig_model', genFigModel); + if (userApiConfigRequired) { + formData.append('chat_api_url', llmApiUrl.trim()); + formData.append('api_key', apiKey.trim()); + formData.append('gen_fig_model', genFigModel); + } } else { formData.append('use_ai_edit', 'false'); } @@ -579,7 +581,8 @@ const Pdf2PptPage = () => { setModel(e.target.value)} - className="w-full rounded-lg border border-white/20 bg-black/40 px-4 py-2.5 text-sm text-gray-100 outline-none focus:ring-2 focus:ring-teal-500" + disabled={!userApiConfigRequired} + className="w-full rounded-lg border border-white/20 bg-black/40 px-4 py-2.5 text-sm text-gray-100 outline-none focus:ring-2 focus:ring-teal-500 disabled:opacity-50 disabled:cursor-not-allowed" > {modelOptions.map((option) => ( @@ -1692,13 +1678,17 @@ const Ppt2PolishPage = () => { value={model} onChange={(e) => setModel(e.target.value)} placeholder="自定义模型" - className="w-full rounded-lg border border-white/20 bg-black/40 px-4 py-2.5 text-sm text-gray-100 outline-none focus:ring-2 focus:ring-teal-500" + disabled={!userApiConfigRequired} + className="w-full rounded-lg border border-white/20 bg-black/40 px-4 py-2.5 text-sm text-gray-100 outline-none focus:ring-2 focus:ring-teal-500 disabled:opacity-50 disabled:cursor-not-allowed" />
{t('upload.config.customModelTip')}
+ {!userApiConfigRequired && ( +

Free 模式下由后端统一选择文本模型。

+ )}
@@ -1706,7 +1696,7 @@ const Ppt2PolishPage = () => { setApiUrl(e.target.value)} - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500" - > - {[apiUrl, ...API_URL_OPTIONS].filter((v, i, a) => a.indexOf(v) === i).map((url: string) => ( - - ))} - -
+ {userApiConfigRequired ? ( + <> +
+ + +
-
- - setApiKey(e.target.value)} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500 font-mono" - /> -
+
+ + setApiKey(e.target.value)} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500 font-mono" + /> +
-
- - setModel(e.target.value)} - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500" - /> -
+
+ + setModel(e.target.value)} + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500" + /> +
+ + ) : ( +
+ Free 模式下由后端统一选择深度研究模型、搜索凭证与接口配置。 +
+ )}
-
- - setSearchApiKey(e.target.value)} - placeholder="search_api_key" - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500 font-mono" - /> -
- {searchProvider === 'google_cse' && ( + {userApiConfigRequired && ( +
+ + setSearchApiKey(e.target.value)} + placeholder="search_api_key" + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-emerald-500 font-mono" + /> +
+ )} + {userApiConfigRequired && searchProvider === 'google_cse' && (
{ const { user } = useAuthStore(); + const { userApiConfigRequired } = useRuntimeBilling(); const [mindmapGenerating, setMindmapGenerating] = useState(false); const [generatedMermaidCode, setGeneratedMermaidCode] = useState(''); const [showPreview, setShowPreview] = useState(false); @@ -49,7 +51,7 @@ export const MindMapTool = ({ files = [], selectedIds, onGenerateSuccess }: Mind return; } - if (!mindmapParams.api_key) { + if (userApiConfigRequired && !mindmapParams.api_key) { alert('请输入 API Key'); return; } @@ -75,12 +77,16 @@ export const MindMapTool = ({ files = [], selectedIds, onGenerateSuccess }: Mind file_paths: filePaths, user_id: user.id, email: user.email, - api_url: mindmapParams.api_url, - api_key: mindmapParams.api_key, - model: mindmapParams.model, mindmap_style: mindmapParams.mindmap_style, max_depth: mindmapParams.max_depth, - language: mindmapParams.language + language: mindmapParams.language, + ...(userApiConfigRequired + ? { + api_url: mindmapParams.api_url, + api_key: mindmapParams.api_key, + model: mindmapParams.model, + } + : {}) }) }); @@ -148,29 +154,37 @@ export const MindMapTool = ({ files = [], selectedIds, onGenerateSuccess }: Mind {/* Configuration */}
-
- - setMindmapParams({...mindmapParams, api_key: e.target.value})} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-cyan-500 font-mono" - /> -
+ {userApiConfigRequired ? ( + <> +
+ + setMindmapParams({...mindmapParams, api_key: e.target.value})} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-cyan-500 font-mono" + /> +
-
- - -
+
+ + +
+ + ) : ( +
+ Free 模式下由后端统一选择思维导图模型与接口配置。 +
+ )}
@@ -178,7 +192,8 @@ export const MindMapTool = ({ files = [], selectedIds, onGenerateSuccess }: Mind setPodcastParams({...podcastParams, api_key: e.target.value})} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-green-500 font-mono" - /> -
- -
- - -
+ {userApiConfigRequired ? ( + <> +
+ + setPodcastParams({...podcastParams, api_key: e.target.value})} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-green-500 font-mono" + /> +
+ +
+ + +
+ + ) : ( +
+ Free 模式下由后端统一选择播客脚本模型、TTS 模型和接口配置。 +
+ )}
@@ -253,7 +267,8 @@ export const PodcastTool = ({ files = [], selectedIds, onGenerateSuccess }: Podc setPptParams({...pptParams, api_key: e.target.value})} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-purple-500 font-mono" - /> -
+ {userApiConfigRequired ? ( + <> +
+ + setPptParams({...pptParams, api_key: e.target.value})} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-purple-500 font-mono" + /> +
+
+ + +
+ + ) : ( +
+ Free 模式下由后端统一选择 PPT 生成使用的文本与生图模型。 +
+ )}
@@ -198,27 +231,6 @@ export const PptTool = ({ files, selectedIds, onGenerateSuccess }: PptToolProps) 需要向量入库并基于检索生成大纲 -
- - -
-
@@ -226,7 +238,8 @@ export const PptTool = ({ files, selectedIds, onGenerateSuccess }: PptToolProps) setPptParams({...pptParams, gen_fig_model: e.target.value})} - disabled={pptParams.api_url === 'http://123.129.219.111:3000/v1'} + disabled={!userApiConfigRequired || pptParams.api_url === 'http://123.129.219.111:3000/v1'} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-purple-500 disabled:opacity-50" > diff --git a/frontend-workflow/src/components/knowledge-base/tools/ReportTool.tsx b/frontend-workflow/src/components/knowledge-base/tools/ReportTool.tsx index 9c8bfcde..8ad99200 100644 --- a/frontend-workflow/src/components/knowledge-base/tools/ReportTool.tsx +++ b/frontend-workflow/src/components/knowledge-base/tools/ReportTool.tsx @@ -7,6 +7,7 @@ import { getApiSettings } from '../../../services/apiSettingsService'; import { backendFetch } from '../../../services/backendClient'; import { useAuthStore } from '../../../stores/authStore'; import { MarkdownViewerModal } from './MarkdownViewerModal'; +import { useRuntimeBilling } from '../../../hooks/useRuntimeBilling'; interface ReportToolProps { files: KnowledgeFile[]; @@ -16,6 +17,7 @@ interface ReportToolProps { export const ReportTool = ({ files = [], selectedIds, onGenerateSuccess }: ReportToolProps) => { const { user } = useAuthStore(); + const { userApiConfigRequired } = useRuntimeBilling(); const [apiUrl, setApiUrl] = useState('https://api.apiyi.com/v1'); const [apiKey, setApiKey] = useState(''); const [model, setModel] = useState('gpt-5.1'); @@ -46,7 +48,7 @@ export const ReportTool = ({ files = [], selectedIds, onGenerateSuccess }: Repor alert('请先登录后再生成报告。'); return; } - if (!apiKey.trim()) { + if (userApiConfigRequired && !apiKey.trim()) { alert('请输入 API Key'); return; } @@ -67,14 +69,18 @@ export const ReportTool = ({ files = [], selectedIds, onGenerateSuccess }: Repor }, body: JSON.stringify({ file_paths: filePaths, - api_url: apiUrl, - api_key: apiKey, - model, language, report_style: style, length, email: user.email, - user_id: user.id + user_id: user.id, + ...(userApiConfigRequired + ? { + api_url: apiUrl, + api_key: apiKey, + model, + } + : {}) }) }); @@ -131,39 +137,47 @@ export const ReportTool = ({ files = [], selectedIds, onGenerateSuccess }: Repor
-
- - -
+ {userApiConfigRequired ? ( + <> +
+ + +
-
- - setApiKey(e.target.value)} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-fuchsia-500 font-mono" - /> -
+
+ + setApiKey(e.target.value)} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-fuchsia-500 font-mono" + /> +
-
- - setModel(e.target.value)} - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-fuchsia-500" - /> -
+
+ + setModel(e.target.value)} + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-fuchsia-500" + /> +
+ + ) : ( +
+ Free 模式下由后端统一选择报告生成模型与接口配置。 +
+ )}
@@ -236,29 +243,37 @@ export const SearchTool = ({ files = [], selectedIds = new Set(), knowledgeBases
-
- - -
- -
- - setApiKey(e.target.value)} - placeholder="sk-..." - className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-blue-500 font-mono" - /> -
+ {userApiConfigRequired ? ( + <> +
+ + +
+ +
+ + setApiKey(e.target.value)} + placeholder="sk-..." + className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-gray-200 outline-none focus:border-blue-500 font-mono" + /> +
+ + ) : ( +
+ Free 模式下由后端统一选择向量检索使用的嵌入模型与接口配置。 +
+ )}
diff --git a/frontend-workflow/src/components/paper2drawio/index.tsx b/frontend-workflow/src/components/paper2drawio/index.tsx index 0d20914f..ef3d75f0 100644 --- a/frontend-workflow/src/components/paper2drawio/index.tsx +++ b/frontend-workflow/src/components/paper2drawio/index.tsx @@ -18,6 +18,7 @@ import Banner from './Banner'; import QRCodeTooltip from '../QRCodeTooltip'; import ManagedApiNotice from '../ManagedApiNotice'; import { useRuntimeBilling } from '../../hooks/useRuntimeBilling'; +import { appendManagedApiConfig, appendManagedModel } from '../../utils/runtimeBillingForm'; const DRAWIO_ORIGINS = new Set(['https://embed.diagrams.net', 'https://app.diagrams.net']); const STORAGE_KEY = 'paper2drawio_settings'; @@ -297,17 +298,19 @@ export default function Paper2DrawioPage({ const handleGenerate = useCallback(async () => { if (!textContent && !file) return; - // Step 0: Verify LLM Connection first - try { - setIsValidating(true); - setError(null); - await verifyLlmConnection(apiUrl, apiKey, model); - setIsValidating(false); - } catch (err) { - setIsValidating(false); - const errorMsg = err instanceof Error ? err.message : '验证 LLM 连接失败'; - setError(errorMsg); - return; + if (userApiConfigRequired) { + // Step 0: Verify LLM Connection first + try { + setIsValidating(true); + setError(null); + await verifyLlmConnection(apiUrl, apiKey, model); + setIsValidating(false); + } catch (err) { + setIsValidating(false); + const errorMsg = err instanceof Error ? err.message : '验证 LLM 连接失败'; + setError(errorMsg); + return; + } } setIsLoading(true); @@ -315,11 +318,8 @@ export default function Paper2DrawioPage({ try { if (generationMode === 'paper2drawio') { const formData = new FormData(); - formData.append('img_gen_model_name', p2dImageModel); - if (userApiConfigRequired) { - formData.append('chat_api_url', apiUrl); - formData.append('api_key', apiKey); - } + appendManagedModel(formData, userApiConfigRequired, 'img_gen_model_name', p2dImageModel); + appendManagedApiConfig(formData, userApiConfigRequired, apiUrl, apiKey); formData.append('input_type', uploadMode); formData.append('graph_type', 'model_arch'); formData.append('style', p2dStyle); @@ -368,12 +368,9 @@ export default function Paper2DrawioPage({ } const formData = new FormData(); - if (userApiConfigRequired) { - formData.append('chat_api_url', apiUrl); - formData.append('api_key', apiKey); - } + appendManagedApiConfig(formData, userApiConfigRequired, apiUrl, apiKey); const modelToSend = enableModelRace ? withModelOptions(PAPER2DRAWIO_MODELS, model).join(',') : model; - formData.append('model', modelToSend); + appendManagedModel(formData, userApiConfigRequired, 'model', modelToSend); formData.append('input_type', uploadMode === 'file' ? 'PDF' : 'TEXT'); formData.append('diagram_type', diagramType); formData.append('diagram_style', diagramStyle); @@ -927,7 +924,8 @@ export default function Paper2DrawioPage({ - {modelOptions.length > 1 && ( + {!userApiConfigRequired && ( +

Free 模式下由后端统一选择 DrawIO 生成模型。

+ )} + {userApiConfigRequired && modelOptions.length > 1 && (
{graphType === 'model_arch' ? ( diff --git a/frontend-workflow/src/components/paper2graph/index.tsx b/frontend-workflow/src/components/paper2graph/index.tsx index 62021729..bf3f57f2 100644 --- a/frontend-workflow/src/components/paper2graph/index.tsx +++ b/frontend-workflow/src/components/paper2graph/index.tsx @@ -405,9 +405,9 @@ const Paper2FigurePage: React.FC = ({ if (userApiConfigRequired) { formData.append('chat_api_url', llmApiUrl.trim()); formData.append('api_key', apiKey.trim()); + formData.append('gen_fig_model', DEFAULT_IMAGE2DRAWIO_GEN_FIG_MODEL); + formData.append('vlm_model', DEFAULT_IMAGE2DRAWIO_VLM_MODEL); } - formData.append('gen_fig_model', DEFAULT_IMAGE2DRAWIO_GEN_FIG_MODEL); - formData.append('vlm_model', DEFAULT_IMAGE2DRAWIO_VLM_MODEL); formData.append('email', user?.id || user?.email || ''); const res = await backendFetch('/api/v1/image2drawio/generate', { @@ -616,7 +616,9 @@ const Paper2FigurePage: React.FC = ({ } const formData = new FormData(); - formData.append('img_gen_model_name', model); + if (userApiConfigRequired) { + formData.append('img_gen_model_name', model); + } if (userApiConfigRequired) { formData.append('chat_api_url', llmApiUrl.trim()); formData.append('api_key', apiKey.trim()); @@ -648,7 +650,9 @@ const Paper2FigurePage: React.FC = ({ try { setIsValidating(true); setError(null); - await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || "deepseek-v3.2"); + if (userApiConfigRequired) { + await verifyLlmConnection(llmApiUrl, apiKey, import.meta.env.VITE_DEFAULT_LLM_MODEL || "deepseek-v3.2"); + } setIsValidating(false); setIsLoading(true); @@ -774,7 +778,9 @@ const Paper2FigurePage: React.FC = ({ // 当前 UploadMode 仅支持 'file' | 'text',无需图片输入 const formData = new FormData(); - formData.append('img_gen_model_name', model); + if (userApiConfigRequired) { + formData.append('img_gen_model_name', model); + } if (userApiConfigRequired) { formData.append('chat_api_url', llmApiUrl.trim()); formData.append('api_key', apiKey.trim()); diff --git a/frontend-workflow/src/components/paper2poster/UploadStep.tsx b/frontend-workflow/src/components/paper2poster/UploadStep.tsx index d4a9b96a..feed3312 100644 --- a/frontend-workflow/src/components/paper2poster/UploadStep.tsx +++ b/frontend-workflow/src/components/paper2poster/UploadStep.tsx @@ -162,7 +162,8 @@ const UploadStep: React.FC = ({ setConfig({ ...config, vision_model: e.target.value })} - className="w-full px-4 py-3 bg-black/40 border border-white/20 rounded-xl text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500 transition-colors" + disabled={!showApiConfig} + className="w-full px-4 py-3 bg-black/40 border border-white/20 rounded-xl text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > + {!showApiConfig && ( +

Free 模式下由后端统一选择 Poster 文本和视觉模型。

+ )} {/* 海报尺寸 */}
diff --git a/frontend-workflow/src/components/paper2poster/index.tsx b/frontend-workflow/src/components/paper2poster/index.tsx index d1e8a135..af00fd29 100644 --- a/frontend-workflow/src/components/paper2poster/index.tsx +++ b/frontend-workflow/src/components/paper2poster/index.tsx @@ -232,17 +232,19 @@ const Paper2PosterPage = () => { return; } - try { - // Verify LLM Connection - setIsValidating(true); - setError(null); - await verifyLlmConnection(llmApiUrl, apiKey, 'gpt-4o'); - setIsValidating(false); - } catch (err) { - setIsValidating(false); - const message = err instanceof Error ? err.message : 'API 验证失败'; - setError(message); - return; + if (userApiConfigRequired) { + try { + // Verify LLM Connection + setIsValidating(true); + setError(null); + await verifyLlmConnection(llmApiUrl, apiKey, 'gpt-4o'); + setIsValidating(false); + } catch (err) { + setIsValidating(false); + const message = err instanceof Error ? err.message : 'API 验证失败'; + setError(message); + return; + } } setIsUploading(true); @@ -279,9 +281,9 @@ const Paper2PosterPage = () => { if (userApiConfigRequired) { formData.append('chat_api_url', llmApiUrl.trim()); formData.append('api_key', apiKey.trim()); + formData.append('model', config.text_model); + formData.append('vision_model', config.vision_model); } - formData.append('model', config.text_model); - formData.append('vision_model', config.vision_model); formData.append('poster_width', config.poster_width.toString()); formData.append('poster_height', config.poster_height.toString()); diff --git a/frontend-workflow/src/components/paper2ppt/FrontendCompleteStep.tsx b/frontend-workflow/src/components/paper2ppt/FrontendCompleteStep.tsx index 3bc6e8c2..15c3c5fe 100644 --- a/frontend-workflow/src/components/paper2ppt/FrontendCompleteStep.tsx +++ b/frontend-workflow/src/components/paper2ppt/FrontendCompleteStep.tsx @@ -7,11 +7,12 @@ import { RotateCcw, Sparkles, } from 'lucide-react'; -import { FrontendSlide } from './types'; +import { FrontendDeckTheme, FrontendSlide } from './types'; import FrontendSlidePreview from './FrontendSlidePreview'; interface FrontendCompleteStepProps { slides: FrontendSlide[]; + deckTheme?: FrontendDeckTheme | null; downloadUrl: string | null; pdfPreviewUrl: string | null; isGeneratingFinal: boolean; @@ -25,6 +26,7 @@ interface FrontendCompleteStepProps { const FrontendCompleteStep: React.FC = ({ slides, + deckTheme, downloadUrl, pdfPreviewUrl, isGeneratingFinal, @@ -52,9 +54,9 @@ const FrontendCompleteStep: React.FC = ({
{slides.map((slide) => (
- +

- 第 {slide.pageNum} 页 · {slide.title} + 第 {slide.pageNum} 页 · {slide.title} · {slide.layoutType}

))} @@ -70,16 +72,16 @@ const FrontendCompleteStep: React.FC = ({ > {isGeneratingFinal ? ( <> - 正在截图并导出... + 正在生成真可编辑 PPTX... ) : ( <> - 生成最终文件 + 生成可编辑 PPTX )}

- 导出会将每一页前端渲染结果截图,再打包成整页图片版 PPTX / PDF。 + 导出会把结构化 slide schema 直接生成真实可编辑 PPTX,不再走整页截图。

) : ( diff --git a/frontend-workflow/src/components/paper2ppt/FrontendGenerateStep.tsx b/frontend-workflow/src/components/paper2ppt/FrontendGenerateStep.tsx index 2a2e6bb9..d4de9b3b 100644 --- a/frontend-workflow/src/components/paper2ppt/FrontendGenerateStep.tsx +++ b/frontend-workflow/src/components/paper2ppt/FrontendGenerateStep.tsx @@ -1,17 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { AlertCircle, ArrowLeft, CheckCircle2, - Code2, FileText, Loader2, MonitorSmartphone, Plus, RefreshCw, - RotateCcw, ScanSearch, - ShieldCheck, Trash2, } from 'lucide-react'; import { FrontendDeckTheme, FrontendSlide, SlideOutline, Step } from './types'; @@ -29,8 +26,6 @@ interface FrontendGenerateStepProps { setSlidePrompt: (prompt: string) => void; handleRegenerateSlide: () => void; handleReviewSlide: () => void; - applyCodeEdit: (htmlTemplate: string, cssCode: string) => boolean; - handleDebugCodeEdit: (htmlTemplate: string, cssCode: string) => Promise; handleConfirmSlide: () => void; setCurrentStep: (step: Step) => void; error: string | null; @@ -55,8 +50,6 @@ const FrontendGenerateStep: React.FC = ({ setSlidePrompt, handleRegenerateSlide, handleReviewSlide, - applyCodeEdit, - handleDebugCodeEdit, handleConfirmSlide, setCurrentStep, error, @@ -68,40 +61,29 @@ const FrontendGenerateStep: React.FC = ({ removeListItem, replaceVisualAsset, }) => { - const [panelMode, setPanelMode] = useState<'preview' | 'code'>('preview'); - const [draftHtml, setDraftHtml] = useState(''); - const [draftCss, setDraftCss] = useState(''); - const [codeStatus, setCodeStatus] = useState(null); const currentSlide = frontendSlides[currentSlideIndex]; const outlineSlide = outlineData[currentSlideIndex]; - const isCodeDirty = draftHtml !== (currentSlide?.htmlTemplate || '') || draftCss !== (currentSlide?.cssCode || ''); const busyMessage = taskMessage || (currentSlide?.status === 'processing' ? '当前页仍在生成中,请稍候。' : '后台任务仍在处理中,请稍候。'); const reviewStatusMessage = isReviewing - ? taskMessage || '当前页正在进行视觉检查,确认并继续会在检查结束后解锁。' + ? taskMessage || '当前页正在进行结构检查,确认并继续会在检查结束后解锁。' : taskMessage || ''; const reviewDisabledReason = !currentSlide ? '当前页尚未生成' : isGenerating ? busyMessage : isReviewing - ? '当前页正在进行视觉检查' + ? '当前页正在进行结构检查' : ''; const confirmDisabledReason = !currentSlide ? '当前页尚未生成' : isGenerating ? busyMessage : isReviewing - ? '当前页正在进行视觉检查,检查完成后才能确认并继续' + ? '当前页正在进行结构检查,检查完成后才能确认并继续' : currentSlide.status !== 'done' ? '当前页尚未完成生成' : ''; - useEffect(() => { - setDraftHtml(currentSlide?.htmlTemplate || ''); - setDraftCss(currentSlide?.cssCode || ''); - setCodeStatus(null); - }, [currentSlide?.slideId, currentSlide?.htmlTemplate, currentSlide?.cssCode]); - return (
@@ -153,152 +135,60 @@ const FrontendGenerateStep: React.FC = ({ 结构说明

- {outlineSlide?.layout_description || '模型将自动规划文本优先的前端布局'} + {outlineSlide?.layout_description || '模型将自动规划结构化前端布局'}

-
- - +
+ {currentSlide?.layoutType || 'structured'}
- {panelMode === 'preview' ? ( - isReviewing ? ( -
- -

视觉检查正在进行中...

-

- {reviewStatusMessage} -

-
- ) : currentSlide?.review?.status === 'repairing' ? ( -
- -

当前页正在自动修复...

-

- {currentSlide.review.summary || '请稍候,修复完成后会恢复可继续操作。'} -

-
- ) : isGenerating && currentSlide?.status === 'processing' ? ( -
- -

正在生成这一页的前端代码...

-

- {taskMessage || '大模型正在编排 HTML/CSS 模板'} -

-
- ) : currentSlide ? ( - - updateFieldValue(currentSlideIndex, fieldKey, value) - } - onInlineListItemChange={(fieldKey, itemIndex, value) => - updateListItem(currentSlideIndex, fieldKey, itemIndex, value) - } - onInlineListReplace={(fieldKey, items) => - replaceListItems(currentSlideIndex, fieldKey, items) - } - onReplaceImage={(imageKey, file) => - replaceVisualAsset(currentSlideIndex, imageKey, file) - } - /> - ) : ( -
- 等待生成 -
- ) + {isReviewing ? ( +
+ +

结构检查正在进行中...

+

+ {reviewStatusMessage} +

+
+ ) : currentSlide?.review?.status === 'repairing' ? ( +
+ +

当前页正在自动修复...

+

+ {currentSlide.review.summary || '请稍候,修复完成后会恢复可继续操作。'} +

+
+ ) : isGenerating && currentSlide?.status === 'processing' ? ( +
+ +

正在生成这一页的结构化内容...

+

+ {taskMessage || '大模型正在编排结构化 slide schema'} +

+
+ ) : currentSlide ? ( + + updateFieldValue(currentSlideIndex, fieldKey, value) + } + onInlineListItemChange={(fieldKey, itemIndex, value) => + updateListItem(currentSlideIndex, fieldKey, itemIndex, value) + } + onInlineListReplace={(fieldKey, items) => + replaceListItems(currentSlideIndex, fieldKey, items) + } + onReplaceImage={(imageKey, file) => + replaceVisualAsset(currentSlideIndex, imageKey, file) + } + /> ) : ( -
-
-
- {'允许直接编辑当前页 HTML/CSS。请保留 `{{field:key}}` / `{{list:key}}` 占位符。'} -
-
- - - -
-
-
-
HTML Template
-

m9$HbJ_|iZP-R}{R&st&tIpjK&{W) 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 z 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