diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index dc895e7..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,201 +0,0 @@ -# ComfyUI-Custom-Batchbox - -ComfyUI 自定义节点插件。动态参数面板 + 多 API 中转站 + 智能缓存 + Account 计费系统。 -前后端分离:Python 后端(节点逻辑 + REST API)+ JavaScript 前端(动态 UI 渲染)。 - -详细架构参见 `ARCHITECTURE.md`,配置参考见 `YAML_CONFIG_REFERENCE.md`,版本历史见 `CHANGELOG.md`。 - ---- - -## 文件地图 - -### 后端核心 -| 文件 | 职责 | -|------|------| -| `__init__.py` | 节点注册(NODE_CLASS_MAPPINGS)+ 20+ REST API 端点(`/api/batchbox/`) | -| `nodes.py` | 所有节点类:`DynamicImageNodeBase` 基类 + 6 种子类 + `GaussianBlurUpscaleNode` | -| `config_manager.py` | YAML 配置加载/缓存/热重载,单例 `config_manager`,TTL 5min | -| `adapters/generic.py` | API 请求构建,按 `api_format` 分发到 OpenAI/Gemini 路径 | -| `adapters/base.py` | `APIAdapter` 抽象基类 + `APIResponse` 数据类 | -| `adapters/template_engine.py` | Jinja2 风格的请求 payload 模板 | -| `adapters/volcengine.py` | 火山引擎专用适配器 | -| `independent_generator.py` | 独立并发生成引擎,绕过 ComfyUI 队列 | -| `batchbox_logger.py` | 日志 + 重试装饰器(指数退避) | -| `errors.py` | 结构化异常层级(BatchboxError → APIError/ConfigError/...) | -| `image_utils.py` | 图片处理工具(PIL↔tensor, 高斯模糊, base64) | -| `save_settings.py` | 自动保存文件命名模板 | -| `prompt_templates.py` | Prompt 模板管理 | -| `oss_cache.py` / `gcs_cache.py` / `gemini_files_cache.py` | 图片缓存(阿里 OSS / GCS / Gemini Files) | - -### Account 系统(`account/`) -移植自 BlenderAIStudio。`Account.get_instance()` 单例。 -- `core.py` — 登录流程 + Token 管理 + 积分查询 -- `task_sync.py` — 任务状态轮询 -- `network.py` — HTTP session -- `websocket_server.py` — 登录 WebSocket 回调 - -### 前端(`web/`) -| 文件 | 职责 | -|------|------| -| `dynamic_params.js` | 动态参数渲染 + schema 缓存 + 独立生成按钮 + 画布右键菜单 | -| `api_manager.js` | 配置管理 Modal UI(供应商/模型/设置 CRUD) | -| `dynamic_inputs.js` | 多图输入槽管理 + 紧凑策略 + 预览恢复 | -| `blur_upscale.js` | GaussianBlurUpscale 节点 UI(Canvas 绘制 + DOM 面板) | - -### 配置文件 -| 文件 | 说明 | -|------|------| -| `api_config.yaml` | 主配置:供应商/模型/参数 schema/端点/设置 | -| `secrets.yaml` | API 密钥(`.gitignore` 排除),模板见 `secrets.yaml.example` | - ---- - -## 核心架构模式 - -### 配置驱动 -所有 API 供应商和模型定义在 `api_config.yaml`,代码通过配置适配,不硬编码。 - -### 层级配置优先级 -``` -端点级(mode_config) > 端点级(endpoint) > 供应商级(provider) > 系统默认 -``` - -### API 格式分发 -`api_format` 字段决定请求构建路径: -- `openai`(默认)→ `_build_openai_request()` -- `gemini` → `_build_gemini_request()` - -### 三通道认证 -- Account: `X-Auth-T` header + 数字 model ID -- Google 官方: URL `?key={api_key}`(`auth_header_format: none`) -- 第三方代理: `Bearer` token - -### 动态参数流程 -``` -模型选择 → GET /api/batchbox/schema/{model} → 前端动态渲染 widgets -``` - -### 独立生成 -`IndependentGenerator` 通过 `/api/batchbox/generate-independent` 绕过 ComfyUI 队列,支持并发批处理 + WebSocket 进度推送。 - -### 智能缓存 -MD5 哈希 = `model|prompt|batch_count|seed|extra_params_normalized`。前后端统一由后端计算,通过 `params_hash` 字段传递。 - ---- - -## 节点类型 - -| 节点 ID | 显示名 | 类别 | -|---------|--------|------| -| `NanoBananaPro` | 🍌 Nano Banana Pro (Universal) | image | -| `DynamicImageGeneration` | 🎨 Dynamic Image Generation | image | -| `DynamicTextGeneration` | 📝 Dynamic Text Generation | text | -| `DynamicVideoGeneration` | 🎬 Dynamic Video Generation | video | -| `DynamicAudioGeneration` | 🎵 Dynamic Audio Generation | audio | -| `DynamicImageEditor` | 🔧 Dynamic Image Editor | image | -| `GaussianBlurUpscale` | 🔍 Gaussian Blur Upscale | image | - -节点注册在 `__init__.py` 的 `NODE_CLASS_MAPPINGS` / `NODE_DISPLAY_NAME_MAPPINGS`。 -动态节点通过 `create_dynamic_node()` 工厂函数从配置生成。 - ---- - -## 开发规范 - -### Commit 信息 -中文,带前缀:`feat:` / `fix:` / `refactor:` / `perf:` / `docs:` - -### API 端点 -所有端点以 `/api/batchbox/` 为前缀,在 `__init__.py` 中通过 `PromptServer.instance.routes` 注册。 - -### Widget 隐藏(前端) -```javascript -widget.hidden = true; -widget.computeSize = () => [0, -4]; -widget.type = "hidden"; -``` - -### 节点宽度保持(前端) -修改节点 UI 后调用 `resizeNodePreservingWidth(node)` 而非 `node.setSize()`,防止宽度被重置为 ~252px。 - -### 大请求体读取(后端) -```python -# 不要用 request.json()(有 ~1MB 限制) -chunks = [] -async for chunk in request.content.iter_any(): - chunks.append(chunk) -data = json.loads(b''.join(chunks)) -``` - -### JSON 哈希序列化(后端) -与前端比较哈希时必须使用紧凑格式: -```python -json.dumps(params, sort_keys=True, separators=(',', ':')) -``` - -### Multipart 字段过滤(后端) -排除内部字段用 `k.startswith("_")`。**不要**用 `k.startswith("image")`(会误删 `image_size` 等参数)。 - -### 密钥管理 -密钥存入 `secrets.yaml`(已 .gitignore),**不要**写入 `api_config.yaml`。 -`config_manager.py` 启动时自动合并 secrets 到内存配置。 - -### 节点类型识别(前端) -```javascript -const nodeType = node.comfyClass || node.type; // 用 comfyClass,不要只用 type -``` - ---- - -## 常见陷阱 - -| 陷阱 | 说明 | -|------|------| -| `node.imgs = []` | 空数组是 truthy,ComfyUI 会分配空 image area 导致画布冻结。用 `null` 或不清除 | -| `control_after_generate` | 可能在 `onNodeCreated` 之后才添加,需 `setTimeout` 延迟检查 | -| `_isRestoring` 期间 resize | `resizeNodePreservingWidth()` 会被跳过,需标记 `_needsPostRestoreResize` 恢复后补做 | -| Monkey-patching `api.queuePrompt` | 执行后必须**立即恢复**原始方法,采用临时覆盖模式 | -| img2img 共享 base64 | 批量生成时预缓存 `(filename, bytes, mime, cached_b64)` 四元组,所有批次共享引用 | -| 端点选择 vs Account | Account 模式需通过 `Account.resolve_model_id()` 将显示名转为数字 ID | - ---- - -## 测试 - -```bash -python -m pytest tests/ -``` - -测试文件在 `tests/` 目录,使用 unittest 框架: -- `test_config_manager.py` — 配置加载/缓存 -- `test_adapters.py` — 适配器请求构建 -- `test_errors.py` — 异常类行为 - ---- - -## 热重载机制 - -配置保存后的刷新链路: -``` -API Manager 保存 → POST /api/batchbox/config - → POST /api/batchbox/reload(强制后端刷新) - → dispatchEvent("batchbox:config-changed")(通知前端) - → clearSchemaCache() + 更新 widget options + forceRefresh -``` - ---- - -## 关键 API 端点 - -| 端点 | 方法 | 用途 | -|------|------|------| -| `/api/batchbox/config` | GET/POST | 读取/保存完整配置 | -| `/api/batchbox/reload` | POST | 强制重载配置 | -| `/api/batchbox/models` | GET | 模型列表(支持 `?category=` 过滤) | -| `/api/batchbox/schema/{model}` | GET | 模型参数 Schema | -| `/api/batchbox/providers` | GET | 供应商列表 | -| `/api/batchbox/generate-independent` | POST | 独立并发生成 | -| `/api/batchbox/node-settings` | GET/POST | 节点显示设置 | -| `/api/batchbox/save-settings` | GET/POST | 自动保存设置 | -| `/api/batchbox/account/login` | POST | Account 登录 | -| `/api/batchbox/account/status` | GET | 登录状态/积分 | diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0376511..14644b7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,6 +4,7 @@ | 版本 | 日期 | 描述 | |------|------|------| +| 3.2 | 2026-05-06 | 新增:用量监控系统、图像压缩、图像拖放、图像标注编辑器、全局 Lightbox 修复 | | 3.1 | 2026-03-09 | 对齐当前实现:修正执行链路、并发策略、接口方法、Account 登录流程 | | 3.0 | 2026-03-09 | 架构文档重构:整合去重,changelog 独立 | | 2.24 | 2026-03-01 | 逐张预览 + 生成进度计数器 + WebSocket 批次推送 | @@ -331,14 +332,22 @@ ComfyUI-Custom-Batchbox/ ├── batchbox_logger.py 日志与重试模块 ├── errors.py 结构化异常类 ├── image_utils.py 图片处理工具 +├── image_compress.py 图片压缩(PNG→JPEG,防超限) ├── independent_generator.py 独立并发生成引擎 +├── usage_tracker.py 用量跟踪(JSONL 日志 + NAS 同步) +├── dashboard.py 用量监控仪表板(独立 HTTP 服务) ├── save_settings.py 自动保存模块 ├── prompt_templates.py Prompt 模板管理 ├── oss_cache.py 阿里 OSS 图片缓存 ├── gcs_cache.py Google Cloud Storage 缓存 ├── gemini_files_cache.py Gemini Files API 缓存 +├── crypto_utils.py 密钥加解密工具 +├── encrypt_secrets.py secrets.yaml 加密脚本 +├── vertex_sa_auth.py Vertex AI 服务账号认证 ├── api_config.yaml 主配置文件 ├── secrets.yaml API 密钥(.gitignored) +├── deploy_to_z.bat Z 盘部署脚本(含 Cython 编译 + robocopy) +├── sync_to_z.bat Z 盘同步脚本(旧版) ├── adapters/ │ ├── __init__.py 适配器导出 │ ├── base.py 适配器接口 + APIResponse @@ -357,15 +366,23 @@ ComfyUI-Custom-Batchbox/ ├── web/ │ ├── api_manager.js API 管理界面 │ ├── api_manager.css -│ ├── dynamic_params.js 动态参数渲染 +│ ├── dynamic_params.js 动态参数渲染 + 全局 Lightbox │ ├── dynamic_params.css │ ├── dynamic_inputs.js 动态输入槽 │ ├── blur_upscale.js 高斯模糊放大节点 UI -│ └── blur_upscale.css -├── tests/ +│ ├── blur_upscale.css +│ ├── image_drop.js 图片拖放 → LoadImage 节点 +│ └── image_editor.js 图像标注编辑器 +├── tests/ 单元测试(25 个测试文件) +│ ├── conftest.py 测试 fixtures │ ├── test_config_manager.py │ ├── test_adapters.py -│ └── test_errors.py +│ ├── test_errors.py +│ ├── test_usage_tracker.py +│ ├── test_independent_generator.py +│ ├── test_image_utils.py +│ ├── test_volcengine.py +│ └── ...(其余 17 个测试文件) └── docs/ 文档 ``` @@ -1140,3 +1157,88 @@ def resolve_model_id(self, model_display_name): | 端点不切换 | 检查 `priority` 设置 | | img2img 分辨率不对 | 确认 multipart 过滤用 `k.startswith("_")` | | Account Unknown Model | 确认 `fetch_credits_price()` 被调用 | + +--- + +## 9. 新增子系统(v3.2) + +### 9.1 用量跟踪与监控仪表板 + +面向多机房场景的学生用量监控系统,由两个模块组成: + +| 文件 | 职责 | +|------|------| +| `usage_tracker.py` | 单例 `UsageTracker`,记录每次 API 调用到 JSONL 日志 | +| `dashboard.py` | 独立 HTTP 服务,读取 JSONL 数据并提供实时 Web 仪表板 | + +**数据流:** + +``` +生成完成 → UsageTracker.record() + ├── 本地写入: {ComfyUI_root}/usage_logs/{machine_id}/usage_YYYY-MM-DD.jsonl + └── 后台线程异步写入 NAS: {BATCHBOX_SHARED_CACHE}/usage_logs/{machine_id}/... + +dashboard.py (教师机运行) + └── 读取 NAS 上所有 machine_id 目录 → 聚合统计 → 实时 Web 仪表板 +``` + +**用量预警机制:** 每台学生机日生成量达 800 张后,每增加 200 张弹出一次 Windows 原生 MessageBox 警告,防止滥用。 + +**仪表板功能:** +- 总览页:任务数、API 调用、生成图片、成功率、时间线图表、模型分布饼图 +- 机器详情页:每台机器的成功率/生成量/耗时环形图 + 可编辑备注 +- 支持日期筛选(Flatpickr 日历)、机器筛选、状态筛选 +- 5 秒自动刷新 + +### 9.2 图片压缩(API 上传前) + +`image_compress.py` 在上传大图到 Gemini API 前自动压缩,防止超时。 + +``` +原始 PNG (22MB) → JPEG q=90 (3-5MB) → 若仍超限 → 降质量 (85→80→70) + → 若仍超限 → 缩分辨率 (80%→70%→...) +``` + +- 默认阈值:10MB +- 最小分辨率:512px(不会无限缩小) +- RGBA 自动转 RGB(白色背景填充) + +### 9.3 图片拖放(Image Drop) + +`web/image_drop.js` 拦截画布上的图片文件拖放事件,替代 ComfyUI 默认行为(加载工作流)。 + +| 场景 | 行为 | +|------|------| +| 拖放图片到空白区域 | 自动上传 → 创建 LoadImage 节点 → 设置图片 | +| 拖放图片到已有 LoadImage 节点 | 替换该节点的图片 | +| 拖放图片到 BatchBoxImageAnnotator 节点 | 委托 `onDropFile` 处理 | +| 拖放多张图片 | 创建多个 LoadImage 节点(垂直排列,间距 220px) | + +**SHA-256 去重:** 上传前计算文件哈希,与 input 文件夹中同名文件对比,相同则复用,不同则加哈希后缀。 + +**LoadImage 预览修复(`Batchbox.LoadImagePreviewFix`):** Hook `onConfigure` 在工作流加载后强制触发 widget callback,修复某些 ComfyUI 版本中预览缩略图不显示的问题。 + +### 9.4 图像标注编辑器(Image Annotator) + +`web/image_editor.js` 为 `BatchBoxImageAnnotator` 节点提供全屏标注编辑器。 + +**工具:** +- 矩形框选工具(可调颜色 × 6、线宽 15-30) +- 顺序编号标记工具(可调字号 100-800,自动递增 1、2、3...) + +**交互:** +- 拖拽移动已有标注 +- 角点拖拽调整矩形大小 +- 撤销 / 撤销所有 / Delete 删除选中 +- 保存时将标注合并到原图导出为 base64 PNG + +### 9.5 全局 Lightbox 双击预览 + +`web/dynamic_params.js` 的 `setup()` 方法注册了全局 `dblclick` 监听器,解决 ComfyUI v1.36+ 前端绕过 LiteGraph 事件路由导致双击预览失效的问题。 + +**坐标转换:** `graphPos = canvasPos / scale - offset` + +**Widget 排除:** +- DOM 层:通过 CSS 选择器排除 `input`、`button`、`select` 等交互元素 +- Graph 层:遍历 `node.widgets`,排除标题栏和非预览 Widget +- `$$canvas-image-preview` 类型的 Widget 不被排除(它是图片本身) diff --git a/MagicMock/mock.base_path/1843070445648/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl b/MagicMock/mock.base_path/1843070445648/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl deleted file mode 100644 index 863d6c5..0000000 --- a/MagicMock/mock.base_path/1843070445648/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"task_id":"688e376da4fe","ts":"2026-04-07T19:30:40+08:00","dur_s":0.0,"machine":"WIN-D2FSOVOQSOP_C5259B","node":"independent","model":"model-a","batch":2,"gen":2,"saved":2,"ok":true,"providers":[],"err":""} -{"task_id":"20de9dfef90a","ts":"2026-04-07T19:30:40+08:00","dur_s":0.0,"machine":"WIN-D2FSOVOQSOP_C5259B","node":"blur_upscale","model":"upscale-model","batch":1,"gen":1,"saved":1,"ok":true,"providers":[],"err":""} diff --git a/MagicMock/mock.base_path/2720834608016/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl b/MagicMock/mock.base_path/2720834608016/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl deleted file mode 100644 index 0599470..0000000 --- a/MagicMock/mock.base_path/2720834608016/usage_logs/WIN-D2FSOVOQSOP_C5259B/usage_2026-04-07.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"task_id":"023bf721e507","ts":"2026-04-07T19:31:34+08:00","dur_s":0.0,"machine":"WIN-D2FSOVOQSOP_C5259B","node":"independent","model":"model-a","batch":2,"gen":2,"saved":2,"ok":true,"providers":[],"err":""} -{"task_id":"aaea8b22cdfa","ts":"2026-04-07T19:31:34+08:00","dur_s":0.0,"machine":"WIN-D2FSOVOQSOP_C5259B","node":"blur_upscale","model":"upscale-model","batch":1,"gen":1,"saved":1,"ok":true,"providers":[],"err":""} diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 150a6ef..0000000 --- a/README_CN.md +++ /dev/null @@ -1,80 +0,0 @@ -# ComfyUI-Custom-Batchbox (企业级 API 聚合与并行生成套件) - -**ComfyUI-Custom-Batchbox** 是一个专为大规模 AI 工作室、机房以及专业内容创作者设计的 ComfyUI 增强插件组合。它打破了传统 ComfyUI 单线程排队的底层架构,并原生集成了 20+ 家主流大模型 API 服务。 - -本套件不仅提供最极客的**独立并发工作流 (Independent Generation)**,同时还内置了专门为影视级图文及视频打磨的“摄影机控制板”与“无缝高斯模糊重绘”生产力工具。 - ---- - -## 🌟 核心理念与亮点特性 - -### 1. 🚀 零等待:独立高阶并发架构 (Independent Generation) -标准的 ComfyUI 队列是线性的,当生成长视频(如 Kling、Jimeng)时,整个工作台会被死卡。 -- **并发解耦**:我们在节点级绕过了官方执行引擎。所有 API 网络 I/O 均下沉至受控的线程池(支持最多 20 路并发),你可以在生成期间随时编辑工作流、甚至并行开启额外的视频运算,主界面**永远保持 60帧 的丝滑响应**。 -- **自动防抖**:无论是上游 API 限流(HTTP 429)还是网关假死(502 HTML),内置拦截器均会捕捉并触发优雅的毫秒级退避重试(Exponential Backoff)。 - -### 2. 🤖 零配置:AIGODLIKE 积分直连服务 (Account Service) -嫌挨个注册厂商的 API 麻烦并且充值有门槛? -- **扫码即用**:面板直接内置了基于 WebSocket 授权的 AIGODLIKE 账号系统。登录后,可直接消耗 “🍦冰糕(积分)” 驱动各种顶尖大模型(包括国外需绑卡的旗舰模型)。 -- **计费透明**:面板中内嵌了实时的智能阶梯计价表,每一张图、每一秒视频明码标价,并随网络自动刷新。 - -### 3. 🛡️ 零阻尼:云端预缓存与密钥轮询 (OSS Cache & Multi-Key) -API 调用的两大痛点:图片 Base64 超限与单一 API Key 被疯狂限流封号。 -- **自动 OSS(对象存储)缓存**:当发起多图图生视频或高分辨率重绘时,底层机制会自动(无感)将本地图像预压缩并上传至最近的云端缓存区,将大体量的 Base64 改写为毫秒级触发的公网 URL 供云端拉取,彻底规避因 413 Payload Too Large 遭遇的阻截。 -- **热插拔密钥池**:API Manager 允许对同一厂商提供一组密钥池。引擎会自动对合规的 Key 进行**轮询负载均衡 (Round-Robin)**;一旦某 Key 被报错耗尽,将自动拉黑该 Key 1小时,并无缝切入下个备胎完成重试。 - ---- - -## 🛠️ 主力节点说明 - -### 🔸 Dynamic Generator (全能动态生成仪) -整个插件体系的“万能播放器”。它依靠 `api_config.yaml` 动态推导并生成 UI 控件,支持文本、语音、图片、视频全模态。 -- **交互特性**:一旦在右上角的“接口服务器选择”中切换厂商及模型,下方面的滑动条、文本框会自动坍缩、生成,并适配对应的 API 协议格式! - -### 🔸 Gaussian Blur Upscale (无缝高斯选区高清化) -专修面部崩坏及边缘拼接瑕疵的杀器。 -- **交互绘图**:节点内置一块可直接笔刷涂抹、也可开启矩形选取的交互遮罩层(Canvas Overlay)。 -- **像素级工业安全**:运用了全新的 `3x Sigma` 边缘延展缓冲算法(Edge Extension Padding),彻底消灭由于截断导致的边界拼接黑边与模糊晕影。前端基于原生滤镜渲染,后端则完全转入离线队列缓存。无任何内存溢出隐患。 - -### 🔸 Camera Control (参数化院线摄影机) -用纯代码定义的数字摇臂。 -- **专业场记面板**:集合了 `镜头类型(推拉摇移)`、`焦距模式(微距/超广角)`、`物理光效` 与 `运行曲线 (Timeline)` 等10大电影控制参数。 -- **剪贴板联动与隐式注入**:点击面板上的 📋 复制,可以直接导出标准化的 Prompt;当你将其与 Dynamic Generator 连线但却处于“开启独立生成模型”时,系统会在提交 API 时智能抓走这些摄影参数。 - ---- - -## 🕹️ 使用指南 - -### 1. 初次启动与 API 录入 -1. 将插件拖入 `custom_nodes` 文件夹并重启 ComfyUI。 -2. 你会在主界面的右键菜单,或是顶部的工具栏找到名为 **BatchBox 配置/API Manager** 的蓝色按钮。 -3. 点击进入管理面板,系统包含有 **Account(账号)**、**Providers(渠道商管理)**、**Models(模型仓库)** 三大金刚区。 -4. 你可以在这里: - - 登录 AIGODLIKE 获取冰糕以开启全模型白嫖; - - 手动新建诸如 `智谱`、`火山引擎`、`Google Gemini` 等厂商,并通过拖拽排序设定他们的权重; - - 还可以将同一家公司的 10 个免费 API Key 一口气填入并开启“开启轮询”。 - -### 2. 构建独立并行工作流 (以长视频生成为例) -传统的排队思路是:添加节点 ➔ 等排队 ➔ 出图。 -** BatchBox 独立生成思路**: -1. 建立 `Camera Control` 节点布置运镜,建立 `Load Image` 输入基础图像。 -2. 呼出 `Dynamic Generator` 节点(分类选:视频生成),并在面板内选定如 `可灵 Keling` 模型。 -3. **关键点**:不在主面板点 “Queue Prompt”,而是**直接点击这个节点上的 🚀【独立生成/Independent Generate】 按钮**! -4. ComfyUI 仍可随意编辑别的区域,这个节点会拉起独立的进度条为你静默轮询 10-20 分钟直到视频落下。 - -### 3. 使用高斯模糊实现完美的局部重绘 -1. 放置 `Gaussian Blur Upscale`,接入原图。 -2. 此时点击节点内部的“选区”按钮,你的工作台上会降下一张透明幕布。 -3. 用鼠标拉框或按提示进入画笔模式,精确盖住需要进行羽化、过度的背景。 -4. 在右侧属性面板调节 Sigma(模糊强度),画面中枢会即时渲染出模糊预览。满意后点击节点独有的 `独立生成增强`,送入大模型 API 并返回高清放大或修复过的结果。 - ---- - -## 🔐 内部工业安全等级 (Technical Security) -为了满足批量布控的机房运维需求,此插件经历了超过 10 轮的暴力渗透审计: -- **纯粹的防泄漏**:Token、Key 仅存在只读持久层及受护内存中,**前端界面即使是设置页也不能获取您的原始密码(只展示尾号截断)**。 -- **严格的反向代理防雪崩**:应对各类网络异常(502、解析报错),具有 JSON 保护衣机制,彻底拒绝假死崩溃。 -- **模板沙箱封闭**:拒绝任何含有执行代码的宏,确保黑客无法借助 ComfyUI 工作流中的 Text Box 给服务器执行勒索命令。 -- **内存防炸栈**:模糊计算缓存内置工业级的 LRU 队列(最近最少使用淘汰算法),高频请求下显卡与内存利用率被硬性框定在 20 张张量级别上限,决计不会越界吃爆服务器。 - -**开发手记**:BatchBox 从一套简单的 API 聚合脚本,演变成现在的拥有自建调度、自研高层级UI覆盖及内存级垃圾回收的复合型中间件体系,旨在为您削平生成式 AI 最后那一公里的操作割裂感。祝创作愉快! diff --git a/adapters/generic.py b/adapters/generic.py index b81a26c..b3fad1f 100644 --- a/adapters/generic.py +++ b/adapters/generic.py @@ -170,9 +170,10 @@ def _build_openai_request(self, params: Dict, mode: str = "text2img") -> Dict: logger.warning(f"[OSSCache] Error during OSS upload, falling back: {e}") # ─── End OSS Cache ─── - # Prepare base64 images for Chat API format (if using _chat_content template variable) + # Prepare base64 images if template uses image variables # This converts _upload_files to _images_base64 data URLs - if "_chat_content" in str(payload_template): + _tpl_str = str(payload_template) + if "_chat_content" in _tpl_str or "_images_b64" in _tpl_str: params = self._prepare_images_base64(params) # Build payload using template engine @@ -186,11 +187,17 @@ def _build_openai_request(self, params: Dict, mode: str = "text2img") -> Dict: # Auto-add parameters from params to payload # Pass through all non-internal params (keys starting with _ are internal) # Frontend handles api_name mapping, so params already have correct keys + # When payload has "messages" (Chat API), skip prompt/seed — they're inside messages + chat_mode = "messages" in payload for param_name, value in params.items(): if param_name.startswith("_"): continue + if chat_mode and param_name in ("prompt", "seed"): + continue + if param_name in ("endpoint_override",): + continue if param_name not in payload: - if value is not None and value != "": + if value is not None and value != "" and value != "auto": payload[param_name] = value # Merge extra_params from endpoint config (e.g., response_modalities) @@ -424,21 +431,23 @@ def _build_gemini_request(self, params: Dict, mode: str = "text2img") -> Dict: logger.warning(f"[Gemini] Files API import failed: {e}") for field_name, file_tuple in upload_files: - # file_tuple can be 3-element (filename, bytes, mime) or 4-element (+ cached base64) if len(file_tuple) >= 4: filename, file_bytes, mime_type, cached_b64 = file_tuple else: filename, file_bytes, mime_type = file_tuple cached_b64 = None - if not cached_b64: - try: - from ..image_compress import compress_for_upload - file_bytes, mime_type = compress_for_upload(file_bytes, max_size_mb=10.0, mime_type=mime_type) - except Exception as e: - logger.warning(f"[Gemini] Image compression failed: {e}") + try: + from ..image_compress import compress_for_upload + new_bytes, new_mime_type = compress_for_upload(file_bytes, max_size_mb=10.0, mime_type=mime_type) + # If compression changed the bytes, invalidate the old base64 cache + if len(new_bytes) < len(file_bytes): + file_bytes = new_bytes + mime_type = new_mime_type + cached_b64 = None + except Exception as e: + logger.warning(f"[Gemini] Image compression failed: {e}") - # Vertex AI: GCS gs:// URI (natively supported) if gcs_available: try: @@ -571,14 +580,23 @@ def _prepare_images_base64(self, params: Dict) -> Dict: # file_tuple can be 3-element or 4-element (with cached base64) if len(file_tuple) >= 4: filename, file_bytes, mime_type, cached_b64 = file_tuple - b64_data = cached_b64 else: filename, file_bytes, mime_type = file_tuple - try: - from ..image_compress import compress_for_upload - file_bytes, mime_type = compress_for_upload(file_bytes, max_size_mb=10.0, mime_type=mime_type) - except Exception as e: - logger.warning(f"[Gemini] Image compression failed: {e}") + cached_b64 = None + + try: + from ..image_compress import compress_for_upload + new_bytes, new_mime_type = compress_for_upload(file_bytes, max_size_mb=10.0, mime_type=mime_type) + if len(new_bytes) < len(file_bytes): + file_bytes = new_bytes + mime_type = new_mime_type + cached_b64 = None + except Exception as e: + logger.warning(f"[Gemini] Image compression failed: {e}") + + if cached_b64: + b64_data = cached_b64 + else: b64_data = base64.b64encode(file_bytes).decode('utf-8') data_url = f"data:{mime_type};base64,{b64_data}" @@ -628,6 +646,60 @@ def parse_response(self, response: requests.Response) -> APIResponse: images_data = self._extract_images_from_path(data, response_path) if not images_data: + # ─── Fallback: Chat completion format ─── + # Different providers return images differently in Chat API: + # - OpenRouter: choices[0].message.images[].image_url.url (base64 data URL) + # - 柏拉图: choices[0].message.content with markdown ![image](url) + if "choices" in data: + import re, base64 + try: + message = data["choices"][0]["message"] + + # Format 1: OpenRouter — images array with base64 data URLs + images_arr = message.get("images", []) + if images_arr: + decoded_images = [] + image_urls = [] + for img_item in images_arr: + img_url_obj = img_item.get("image_url", {}) + url = img_url_obj.get("url", "") if isinstance(img_url_obj, dict) else "" + if url.startswith("data:"): + # Base64 data URL — decode to bytes + try: + b64_part = url.split(",", 1)[1] if "," in url else url + decoded_images.append(base64.b64decode(b64_part)) + except Exception: + image_urls.append(url) + elif url.startswith("http"): + image_urls.append(url) + + if decoded_images or image_urls: + logger.info(f"[ParseResponse] Chat completion (OpenRouter): {len(decoded_images)} base64 + {len(image_urls)} URL image(s)") + return APIResponse( + success=True, + images=decoded_images, + image_urls=image_urls, + raw_response=data + ) + + # Format 2: 柏拉图 — markdown image in content string + content = message.get("content", "") + if isinstance(content, str): + md_urls = re.findall(r'!\[.*?\]\((https?://[^\s\)]+)\)', content) + if md_urls: + logger.info(f"[ParseResponse] Chat completion (markdown): extracted {len(md_urls)} image(s)") + return APIResponse( + success=True, + image_urls=md_urls, + raw_response=data + ) + except (KeyError, IndexError, TypeError): + pass + + # Debug: log response structure to help diagnose parsing failures + import json as _json + truncated = _json.dumps(data, ensure_ascii=False, default=str)[:800] + logger.warning(f"[ParseResponse] No images at path '{response_path}'. Response: {truncated}") return APIResponse( success=False, error_message="No images found in response", diff --git a/adapters/template_engine.py b/adapters/template_engine.py index 491f4e8..8c18359 100644 --- a/adapters/template_engine.py +++ b/adapters/template_engine.py @@ -85,6 +85,7 @@ def _get_value(self, var_name: str, params: Dict) -> Any: Variables starting with _ are treated as special/mapped values: - _chat_content: Build Chat API content array with prompt + images + - _images_b64: Return list of raw base64 image strings - _map_size: Look up params['size'] in value_mappings['_map_size'] - _extract_ratio: Similar extraction with mapping """ @@ -92,6 +93,10 @@ def _get_value(self, var_name: str, params: Dict) -> Any: if var_name == "_chat_content": return self._build_chat_content(params) + # Special variable: _images_b64 for raw base64 image array + if var_name == "_images_b64": + return self._build_images_b64(params) + if var_name.startswith('_'): return self._get_mapped_value(var_name, params) @@ -125,6 +130,27 @@ def _build_chat_content(self, params: Dict) -> list: return content + def _build_images_b64(self, params: Dict) -> list: + """ + Return list of raw base64 image strings (without data URL prefix). + + Used for APIs that accept images as base64 strings in a JSON array, + e.g. 柏拉图's image field: {"image": ["iVBOR...", ...]} + Returns None if no images (so template engine skips the field). + """ + images_base64 = params.get("_images_base64", []) + if not images_base64: + return None + + result = [] + for data_url in images_base64: + # Strip data URL prefix: "data:image/png;base64,xxx" -> "xxx" + if "," in data_url: + result.append(data_url.split(",", 1)[1]) + else: + result.append(data_url) + return result + def _get_mapped_value(self, mapping_name: str, params: Dict) -> Any: """ Get a mapped value using the value_mappings config. diff --git a/api_config.yaml b/api_config.yaml index 71bd358..62d5093 100644 --- a/api_config.yaml +++ b/api_config.yaml @@ -102,6 +102,108 @@ models: content_type: application/json response_type: sync response_path: data[0].url + gpt_image_2: + display_name: 🖼️ GPT-Image-2 (旗舰·图像生成) + category: image + description: OpenAI 最新旗舰图像生成模型,文字渲染能力强(最大长边 3840px,2K以上为实验性功能) + show_seed_widget: true + dynamic_inputs: + image: + max: 16 + type: IMAGE + label: 图片 + parameter_schema: + basic: + prompt: + type: string + default: '' + size: + type: select + default: auto + options: + - value: auto + label: 自动 (模型决定) + - value: 1024x1024 + label: 1024×1024 (1:1) + - value: 1536x1024 + label: 1536×1024 (3:2 横版) + - value: 1024x1536 + label: 1024×1536 (2:3 竖版) + - value: 1792x1024 + label: 1792×1024 (16:9 横版) + - value: 1024x1792 + label: 1024×1792 (9:16 竖版) + - value: 2048x2048 + label: 2048×2048 (2K 1:1) + - value: 2048x1152 + label: 2048×1152 (2K 横版) + - value: 3840x2160 + label: 3840×2160 (4K 横版) + - value: 2160x3840 + label: 2160×3840 (4K 竖版) + quality: + type: select + default: medium + options: + - value: low + label: 低质量 (快速) + - value: medium + label: 中等 + - value: high + label: 高质量 + advanced: {} + api_endpoints: + - provider: openrouter + priority: 2 + model_name: openai/gpt-5.4-image-2 + extra_params: + modalities: + - image + modes: + text2img: + endpoint: /v1/chat/completions + method: POST + content_type: application/json + response_type: sync + payload_template: + model: openai/gpt-5.4-image-2 + messages: + - role: user + content: '{{prompt}}' + modalities: + - image + img2img: + endpoint: /v1/chat/completions + method: POST + content_type: application/json + response_type: sync + payload_template: + model: openai/gpt-5.4-image-2 + messages: + - role: user + content: '{{_chat_content}}' + modalities: + - image + - provider: 柏拉图 + priority: 1 + model_name: gpt-image-2 + modes: + text2img: + endpoint: /v1/images/generations + method: POST + content_type: application/json + response_type: sync + response_path: data[0].url + img2img: + endpoint: /v1/images/generations + method: POST + content_type: application/json + response_type: sync + response_path: data[0].url + payload_template: + model: gpt-image-2 + prompt: '{{prompt}}' + image: '{{_images_b64}}' sora_image(文生图): display_name: 🖼️ Sora-Image (视觉先锋·文生图) category: image @@ -374,6 +476,7 @@ models: api_name: seed api_endpoints: - display_name: Google 付费层级 (优先) + enabled: true provider: google_official_paid priority: 0 model_name: gemini-3-pro-image-preview @@ -496,6 +599,7 @@ models: api_name: seed api_endpoints: - display_name: Google 付费层级 (优先) + enabled: true provider: google_official_paid priority: 0 model_name: gemini-3-pro-image-preview @@ -616,7 +720,7 @@ models: api_name: seed api_endpoints: - display_name: Account 稳定通道 - enabled: true + enabled: false provider: acggit_account priority: 4 model_name: gemini-3-pro-image-preview @@ -639,6 +743,7 @@ models: response_type: sync use_oss_cache: true - display_name: Google 付费层级 (优先) + enabled: true provider: google_official_paid priority: 0 model_name: gemini-3-pro-image-preview @@ -707,7 +812,7 @@ models: - display_name: Vertex AI 正式版 enabled: true provider: vertex_ai_sa - priority: 1 + priority: 2 model_name: gemini-3-pro-image-preview api_format: gemini auth_type: service_account @@ -815,7 +920,7 @@ models: api_name: seed api_endpoints: - display_name: Account 稳定通道 - enabled: true + enabled: false provider: acggit_account priority: 1 model_name: gemini-3.1-flash-image-preview @@ -838,6 +943,7 @@ models: response_type: sync use_oss_cache: true - display_name: Google 付费层级 (优先) + enabled: true provider: google_official_paid priority: 1 model_name: gemini-3.1-flash-image-preview @@ -930,7 +1036,7 @@ models: api_name: seed api_endpoints: - display_name: Account 稳定通道 - enabled: true + enabled: false provider: acggit_account priority: 1 model_name: gemini-2.5-flash-image @@ -953,6 +1059,7 @@ models: response_type: sync use_oss_cache: true - display_name: Google 付费层级 (优先) + enabled: true provider: google_official_paid priority: 1 model_name: gemini-2.5-flash-image @@ -1029,7 +1136,7 @@ models: api_name: seed api_endpoints: - display_name: Account 稳定通道 - enabled: true + enabled: false provider: acggit_account priority: 1 model_name: doubao-seedream-4-0-250828 @@ -1098,7 +1205,7 @@ models: api_name: seed api_endpoints: - display_name: Account 稳定通道 - enabled: true + enabled: false provider: acggit_account priority: 1 model_name: doubao-seedream-4-5-251128 @@ -1251,6 +1358,7 @@ model_order: - Seedream_v45 - Seedream_v4 - gpt4o_image(文生图) + - gpt_image_2 - dalle3(文生图) - sora_image(文生图) - flux_dev(文生图) diff --git a/encrypt_secrets.py b/encrypt_secrets.py index 480d6f3..bcbbd60 100644 --- a/encrypt_secrets.py +++ b/encrypt_secrets.py @@ -38,7 +38,9 @@ def load_or_create_key() -> bytes: f.write(key) print(f"[✓] 已生成新密钥并保存到: {KEY_PATH}") print(f"[!] 请将以下字符串复制到 ComfyUI 启动脚本的 BATCHBOX_KEY 环境变量中:") - print(f" set BATCHBOX_KEY={key.decode('ascii')}") + _k = key.decode('ascii') + print(f" set BATCHBOX_KEY={_k[:8]}{'*' * (len(_k) - 8)}") + print(f" (完整密钥已保存在 .secrets_key 文件中)") return key @@ -60,7 +62,9 @@ def cmd_encrypt(): print(f" robocopy ... /XF secrets.yaml .secrets_key .auth.json") print() print(f"[启动脚本设置]:") - print(f" set BATCHBOX_KEY={key.decode('ascii')}") + _k = key.decode('ascii') + print(f" set BATCHBOX_KEY={_k[:8]}{'*' * (len(_k) - 8)}") + print(f" (完整密钥已保存在 .secrets_key 文件中)") def cmd_verify(): diff --git a/nodes.py b/nodes.py index dc0482e..a973b6a 100644 --- a/nodes.py +++ b/nodes.py @@ -1568,7 +1568,7 @@ def INPUT_TYPES(cls): "required": { "blur_intensity": (list(cls.BLUR_PRESETS.keys()), {"default": "轻 (σ1-3)"}), "repair_mode": (list(cls.REPAIR_PROMPTS.keys()), {"default": "直出"}), - "custom_sigma": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 15.0, "step": 0.5}), + "custom_sigma": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), "aspect_ratio": (["auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "4:5", "5:4", "21:9"], {"default": "auto"}), "style_prompt": ("STRING", {"multiline": True, "default": ""}), "batch_count": ("INT", {"default": 1, "min": 1, "max": 10}), diff --git a/secrets.yaml.enc b/secrets.yaml.enc index 9be7b40..176953d 100644 --- a/secrets.yaml.enc +++ b/secrets.yaml.enc @@ -1 +1 @@ -gAAAAABpw-KDPFOKgn3mKNhkHZhkzRHU00GX0MeVsL3hUDLgxk0W3EBzL8OrPi2GKN8AKN0f68kwLyUFIpNdl6gRVdwacmxELfdr7uVi-5-W5YvF65uJLtFf7IezCY6NN63NvkjlY8es4TwsM4dVlCzdtnJdgeiUMRM0t1WFWzQ3w60PgveCEB_OEqdZWqIA8kjiutLQzCIXMwzwTau6OhvhadU6YNiPKCQ-6qIEcJhPfW4Dn-rLaErKUOBcEDAx7wkWaIsyFA8KlFp0DC-0xmN7JKLsqP-JKO889nwLBg5IHu2omhWZxAlbZPlVASPLT562d9x43iVu9l5IMPoqAiNK6BQWnYw12u1U6_hXfOaErrxFijHkbW6raZ0KypiPqgT7xVNsQtJkQ4Tgmtfc2VGs_LgVKXkbmUds7_vK0kFBArxcwGTx20esISxaJSGStmg7ZvW6zuSai3_yPZi_DSDbChFwakNE3XETBn4b2H2Yb3zqLMoqBdRibz_-naKc7IDVH9WZ2Z4bonLK-1MflXQHVYBTVjYi8TINAKqb6jZi4ORqP2JiahYhxWIjHzfuPsxUBaEFOtBRxAfpHPsapHHbR_UaZE7BCZT5p8bjLddCaWh55E1sMBb7840qzPdpvgKjZDaht_AZV_ccK7bq8NNc5FQ8GnvJ3shrwqESsqjATMFio9KXX-W7jPx3EmhPOdp7pzc7tttR3TfqUNQRVq8Hu7D_rkaxNuUZHcOB-coXHi2p66EEG-Rc2U_PAAZ9uRPhjXBT3APGue8XNSd068vKecgAFH1YofF0HYrfjW9wbm8HURWSg1TiSfOKX7XSG2gcUg70rP-CjyrZTpv65uOMq9EGeZ0E_4RlELLzZS6wdj6ZxU1XhlhumB8NftLVccO3edWImdPl1YTgftYOHirIIxOMd5KGoCppz588mx_k25daLf2Xy5Iio3WAERojQ9mbwZ9j3mgUGsfEt_CJJfAd3ERfIYnOBZW1Q_mmszr9UbMfOl6vWoUUAf4WwUD_yH6C9JG1NjkA_VLccStMddWgmDBixVQSjtpexAUAVUfWAJq-J5Ko-nCoXUDERWgkeHJrVcM-vXczv263E_7XCGGqQZ53ZX4iHyJ4HO1f_b65gCZXRHepwXsooKKbqgHAt2DruCk2Odpaqs5oKgifW3hWixIkDDnRmSN6Yg8JO95WMk0Blxc7QhbrXv2OpPvpm_vbnWl-30b3eZz4Ih8nuMJSezieA-uVM6nvkkKkeK7p3Vr9tlXeWdrNAtG3lVqmtllVGTV0Rm91uUphKgJGQnPMSjb67nPesOwzKVKG2-aQcRCc1clE8J8TmegjeExXihoTK_1A22NTbf6a9HINS-Iw8VCZ4a0MCBY2EsAVXT7woHVhDbRSw9RziNMARHBJ5LxihS1soamUq036e0NazntmcWLFu-OhFr-wQxa4VAhVm8RDMRoUmz9Ovceo4r3LsqWU-dLTNSKbPCISidneqb9-4nK_sl2sAxAryP6PQqsk_ORxT5nMlCRL2SvcT1cDlXQpq-CZYEGRuTvHFOfXtLbcbCCnJvek0G2PojXrzr9AKEzpTg4NQVyjuWro4b9JxLkiBNX0unj-cqC9p5OkYtkqsZbPtcZqI65hnmssctenswv2WzgdS9QSqPdgV4qhT7tFA25QwazjJeJniExuJO_LgngXfs8MqeQc5z-0EU9B0h3QyC5x3aWqBS9SPvrS3WCzwGEfNoe51eYKHoKro2a_XgFae4fqZER3_6N8nFhmg4lt8t-1qobMA8AoNMCVkFp5fjfB5z2q-iUZtZzSs0Rx2RffLHzBe-tRULLvaELqSbwAhqrvR1AJXH8C7Onv7bY6Pw-J0g-0UHHg8AYRG6wEL4536StUiwhqPDlJ4XK7TfTWzvAUHKPvywTJ_QcEyD_DmnOJbT4aJhwy4eI_qaoOwJrawR-m66fuMYONifmIo07UHydCM-idS_hFLPsQJzgQULDvCbNLpaU62ozOf_4YHNSmwt7mJi-Gq1cQaYQF0inO8j3ETQh1rSkJwLmSeFxKJBNm_GGZmbtcrpzjytN39gEB7QU0VsALQw8yRsZ8SfR2nbk5CMcVxvnw9SAQYQ_71dPfOraBQkbDYijeSk4-8McR2KnbttOeEzxu3MJP7iJviQfGlHNosHjrMdwNbNvmX-3umdCUHjekyAE063tMmbFkCkP4_4kYb06zhnzKdqK8aXkvPAfUDl2dm2onI_-x5YWnSOv0jqTgnm_MKAyBX1RbDJt09-1FQteJMdV7LJQQjhU9Ml-ekrGQvpKN7hjfAdBlLIPRrI2YQrEHcYtcNGaIHcnm--IfbLomHybDTFYn6_BYg9kpsYlVQcvPQ0eH0gNxHL_3I71DG0Q8RKoSlsT8YwS5GJq-oYEbqwiq7UWWslAnmPRTGnvhNJ1eqh-jFQ25dJxxIzp89zU0CVK47jpuflgW0dT4Wqn0UhxB9NW-AmmeiBje4aLBXG8vZtkbqwNf8oFCBWL-Q96YkMwEkf1eLgTCk_o76s-b6QEhbokkgrz8qPx1fVhm_9p3CCTt0lEASHbmO_2tO2lDTyCKfi5rjR4jYvYlxZv3I3OHXxr8OY7h23HKErSgS0QM8qcdGazyI3crGGvc0qkou5Hc1HO8VJgQLwu-yfv6mBYR4-yfp1ODu4-3bhATR7IBiisHiMqoqoHmQwE_7kXoy8yXsH9ah9D6yCpefmCmM5jsQZCUHG0fdPMBYXjvjJHIsDkFbAldlt4W8bmMUs1PJFURvyu3wB8iR6IAdCx83QCq7Cd-liDv0TwETfsLpDjnfKvItX1NIcbu7PaoKQ8cRjPv-XkW9Bz6SdrncEPENTst2bZnMlweVRXkWe3TSf5rkwniC0Q8KuUrjsgSskpAA2Tq6QenImcz6fyXk5Bg69euXpGUSTcmGNy2vWSFx548cGCRM7EFNBBaIWtTpYBgNAXDQ1sfNHbgAc5PIet0QysJmRlJeNeE_4ZhNTbfNrhiJ6gnd0mlNpgz7szkoOqTQnNCt1Avmdv430TdSNmAeGJ5g2ZlhN8fhCS0Ym5zw5jO0NXLnQU9-VQFlG6MzsSoT-qD_Fst1xOSDWyacAC00HBHAiGcMRPrq8eX2XNkMtRzroS8VDAEWrUejnC4Kr7kaHATg9lv3YxtDffrR8HMBxLFXUT55D23ba-5VT9uuRv-FpE8vVQrrThEqlPitX09oaPK9C5O57RW9aY-O_fPeBNIGe0ftEIhJV_dsJybPyV5xjt9HNXmLLoDTDz3nsvwGoiz91m1194o8zW7vDQhF3HVja4jAHexXZpdGxHO6JeH_8hhlQiB71SP4SJTcgt5wAwNqz1HIvCT4LFfo8eILpifBj9WtzKZ0Ew4C0aH2VkeKzY0O4sxXwqRXHrkmR9zyuELSGtOCulY_0XMh0YZEHbpKZxz4yUCOHaRFowKEatXXECS0rDCif_ye3toIDP5zdgvCOHYRBd3arfIzDK9Gk_DuY73kxA1uNdZO2kMYfw_ymf8EsIzlrJtS2hTEy0VDSOwK3-Zxb9eZR5GUEoZZUVcOCKaf3yYrkBAGFbB2Vi_gBMnD9RKsNH-YMt3p2S8rJoCUogQN6DsI88ojFu2ENHRHgR3WYvsheKH1tVsR8_w0hPQ0MskvPRP42UbQcCZoAF5dEk7CeT0fTTJyS7gkJDgpu6eE-ZKyVU5VRVuhtG18c8eU7S5Yikc5KVyqgPrm2zSzHNHiuK1PSvHCB2cU7MQ1ZmFMdrQNHTv-L8PR_z3CZ-qpIyGs1Fnqb_HM8YRc1NpeuuGImsk5TsveAHa_PdnronlmFzGmSFErEvH1EgOe23UM7njC0UvZuX5KuVmrfQxkhjHLEKDnUbk3dxqzADBl672frXmanbsetBcXJnxGcjl_yJ5rOZw9vgX5VTiOT-HQ7Y-uwGnHQqfqC1Q76kgt_-AuVr2FUCzWP9Ozwmt9makdt0IWNiSvWh8jCyc95Diw_va0e8Cn8u2M-5hr44Xrk-F3nQTw6rwllheS5K4pL3yS58Iz_R7RDr4DhGxVcV_ACX6Bkut1ob2z6txor6bhEsoxx9InqFxqrEnyeygABKPu2EkylH39D6THnkvgb4Tg53VegO2aeVxVX0CNefWkOySKOKjlEXRGlluPTSkF3bVT3Z-uab2nDjRnCNLIbF3fkvOQzvm-4N4rl4xZ5vMkV-sREcoIuS8P80CV3fqz9VQHAeasMZqoliDhpNSrxWyVdJGlmHMtYV5VmxH629qCMg_-M-EA3eZPfSKgcKC7mNv1QqFHF33iXOoxLu9q8nhw4dZFNetTf-MKIFdvIavj6Bm8L1s6QE13EBzCEocn-yYUNui2vr9jdy7lcOjkp5q2uZMrmXWJ8S-mp5JCmG6Wjm1k16XEWBhyZFRiuR5Reb6lXxpH6uhYxwROsU4kVZaE5nPq7B81jZ74xVcoGc_Zqf8EP4Dllkqls9WfLqQ7uWLQTli0iepEz91pNFbfzjfeIMTBfe3KdkE4o7Zs7K9Bl4Ss-YfORKddDYJ9fMnCcTZfNgzOYJqt9YSHVbSfU4JqDHuSm2nyM7iwmHZ_KMZG6auRsPNi83TfbDcur3cLYAEu2l8bs1P_5teyJaKjawn7_SV82s6-_SC0UQGMGU30lWuOCwJJXMiSdHhVqEawFhhNWKthjuI5WemkIHukDXpwG6EIOj9xdsPvs7cBNDfbSsrB2F3UICfFcTgDxeRpwugUM12NZBmkmIqiOJXH7eThNjZfd3Ce4DWkNPsWAhChLTpn_qhYBo58CYE7Y0-cwqnQuIbjvufsJCYgkgNaXlAj5Wu5vvrIkIqym8Uaklakf_Az1Z0MNAmJGSiM6S_qlOQKI7JQAEIGTtOven9iGIXcFHNc8DtaWznZcR-ZVnC-VZrs3_j2BW8xRL048NBVLURtQKUfdCuLn-sINpfn_KYZoDN5i6PbBMpFG8JfWe1OgMLQo1Ykf_g0Dl-iVfMvezBZQoime7ZZSJ0P6eRxwm_rvvTPSifiz9pLCyNrOF-9FqiH7jz0PuWhUUZ_fcDRhoj_QeVs4re3HLCwX0b95-7uIhogsIW0JTeOREmjqX7IH0_EOGR0mIKgdKzeLMWvjSFq1rcvD_JKpIZmcbn7YCN05tptdgs0yhVOarPIIGmZ2Gtx4ulXjVVilMVpsYPy3n_HHG6tmWfPvJGfJGg6VR5YIzT737ganJhHr6Rff93INSL9BiqmuPmOhv40n103mmUQsthMZVgfnd5N2YK21KQArwp4ZzymRg1t_paIGr7psW8F7yK0ubxUj_sKnTrOXxE19_1Aakvg3Qq6GtAv_wUJKjp3HKGYIGeh97nV82YKIGmMZfYgd-XvIhxRJI-BosZTGqwI1Gh-obr2-tvQrPtAABpIXEdY4gWXRX31APf9omrSVxPYxEquTjkASSpSA-S_RStcWY2DdAFNk1B3mdzxK-a6eFudn8jlbpaAokOplo3RALE4bpKr8O5_AwZObsV6UoBsK54jJ0mQddfnTim6JzF24UM-hKaeAgKoBEnl2561ZwhZV85yJWiOoaXtwt9Y4kY1fibdqs8CdChqevPDjEwIXvvyKqkYDud_UY3io9Satfy7w1d1RzMiFUI86ah_Xj7iDQJEfIT9wiwIGuTf6hfOE76g3DrTsXEoQ36Il4KmEiPJJp99N3gxvplafsDaR8xwHZ6hXT6NVq2gn1GlZUcWoN1vpz8SVOQ5BaqayxiVgowXx58rirxpxbrgQGQRvFRRO9ppSeNr6cYzmQ2ruGNjFDXOu4sz1y4jZJ_fDB5CI0Q7_G8_5nRVaEil2qnJzeGfyPfCmsOCKVfVrbYR647LbvmNxjC3TFcsx1_uwSMRBvV6YZ1L8xFgEEtbHo8ay6C-ghFeidB62LMyIE4X2GUWZYqDm7SopXhAkalblX1wlD2ciUzuCNdqQSeleKffxCsOp2YYyApTxNbIaeu4v5tmN616RxMW9jCNpo4AoJ_3pzQCJ2OTMroMAhtb5YylmGd0RUb2q8OcD7AH-W1BEbF5tXDe9TMDC1GPGMKHaYG4H94nuTHFqbAbvqiZ_EbWAVm6ZQlCp7rR7V05EeuzvtKpwouoaMwzyWJXHnqelELFwO9II2CA7sJrjftINtLxXMVwLOm0zS443Ylh_ztkcnW3JS3iVwhgfdxa8H-3qthTNVBW6nxmSpU-I4o8Unjq3gn1w-rZtXsTTCwMeu4EmdnfyXmv72Ca-V5-gvfTLsJIoyeOXV7rgmQWx6WX3-gQNLs5OkDhukXlgEENI6u9TY0LRONFfqtBwf3Y2Da2B39IBy0EItQ49c_FrRX9fAT76x-hmzTpAnYgt9ADszoRx_UYXF1c3MdwftePu32JGP4aiqvgenXUt2vip-QS1VIMWtpccZSQLm2CAHe5duyILnQJyVGxyhZLiMBq-7rzTVefGtU8_mm2d0IXjH9G9LVwoPXRm0QFLSHMzn23X_9_59IS6ro8kElfv1kJoKdbZcDYYS4KLOXTrJE1_cXABpv-bnp1rXIhQA5fT08ply9zuShv9Jwphi-sBy6Gayfm6Fls-qf-108-wLcOCEGVODjgRfoLHzHVSk7NPIiLRQsdYHI63qzIm62DQvDPVOEkIgYnQ4_yPt17DoHJRpJhHR36Ub2gOvmHa6GJI4Y8CO5iM0amxxeLqbd7qjOu8AkFpHb5H6FXzmF27VnSVrd6DJPw4dKsNmjnkR7pli9TY86zzdgsObI6BIPy8sCcnSPEpp5qW9ksa7LtBDMH0bkpy9yDAgJzWiCp_yCz4DqiCgD6XNKAEtmwqEEAUFkIOcISf6NPVou_gzAGgB5RYaNxalsBZPKQYGH1S-zJYoh7EH8RdCX1Mf8tB2ewhuFgJNVoWgU-hcopnbl0k_rS-NKIQWPLMyNn-WT5dk_-_lwJOgi04vsZQDOQPK-SF_AHURH4rVw2GVE6CcCdGRp_-tkl8wqdeTdJL8OAYQ6biYsLEfkEQY2WpdzZH-hDEAG7q5p7u2Ne5MoMsnknXcEIVoGYncKE-Ndxa8LAIObSssHGcLUdcU6cYlqOCOp8aiUYf2fMlpMK4thLx70qo_i-9pjgj7DL7yo2H5uW8vOCgoi-G3nheGGAD9sjg51puIzaQwSeLZzasi1__TrANKAdCiY8bRfMnx7hnuUGfDbuMkJd1rYJuvJUHEeNQB66nlT2FYeSHraTt1RAcdzO0gRdGtvAGkWo33eRxr_Cj6ZxLui3tzOX0YOiB6gSWDm6UKjHWoNH9znd2w0SWah3suDlequ0PbmpRmPkk8grP24Wss9ht-gAoGE7BL5bU4HJXgFMhg9xEjVmY0Ha2YOu_tdwwB4lCd4HA22mMMkdY3Z_q2wQk6p7JFLSpcgylKwUWK1yrRelC6SdWrb0jiaeymZQ0EErWcnFQV4yGGAE4PXSdf-GnsNfCalUOR-JUqF5cZHC0VAjtZXoLhnnuzuEu3L9ls2BUNPsMMDMp5X6E6V1vegtiv9w0Ka3uRu16_SypzDJJdGe8LpruiDV-I1ZjewuS1pJI4NPJ7qQit7c__bO82wQGabRPAd4okNjFU9j0qkqTh7FtK9Isj7JVkMjZ_JR2uO3cJ3Uu9avy6wT4XnE1wiSzHpTB_FIQtP4KGhFXI1fRXVgS22YHcplj5JJnogGuaJsJqg4mXQxSLt_bvDgczK_HSD71rLcB4U2avmmR_6g0iDKQw06aQrmzpbuDGQG2znKX62sbZzGWcLYzws1EH1B-TXrlBzsyU-cIivQRWoQaDUpiM_km0ftFjH-qvO_ceCUmWyiBccQcWa4cLZ-apwcZ4wMwKet49UVnribnGcgMjSyjlHfZc-Chwq1K8q1LWuIuKkt1OKl8XFlS-1gKjz9TCsiKmEJywQuVF4Vyfh2ef5oxn9BXDHEN6nQRkNFNMggNjowI14Lu9WyRw2M4YbvgVXmnsoPK9Skz54m2Xn0zDeb6VQ1-hU0X-uRNHsuhGqinfz7zO6XuDrHZt_4GdVUeqnb3l3xL2OkElgyDeVZJSWBX_klsWoPetHyk2_pw_Y0xiIR5YL1coklBuRWIIfFVNyDKd9fQmysK8AMA9J75mScDy5rFQzQbUM0mf3cnmyeFswotkJjSqrZE1CmG91H2mbwUO4Nj1KxLJgIULt4Tm7avex3s0MmUykpkK8IjPcn5hSyoHAQocUnfYCl3Rv-AobGBl2aMZThg6XLttCQUG5QHGtGHag4kSj8Zf-6mI39ywH6vn8DUHmrJnsIzscdUAHjXpr6M67nrvZS9KSlpHxk4k6HdUS3YpU_zXN13YmkAxZQ5u0MKImBpSKAoo8D8m46j_-377ClX2fA-m5s03XrivLp4ZB8Xc04Ayw4ajBDjsXJPFv7glo-Ua4evefVW0M4xDgSvkEz0tdytDi5cc8EJ1SrsA8TT4zhYiExmAiigzYdqm3V4-t75jLAcQg81a5URYJOJ0CLJvzjSUCBPRULw4gapRJPIDVX3PzJlNFNxAbBVtttB0DG5uynwKI3CSZ_CFprLu8QJkEtdrfC_HiuufvJhm4BNW3XTNR9kJHY0LbAGWmsj8EZgx5Yj0jj5LkWyMD7vsfJfbJculmG6SuoPF6YK-E-bQvZ6T5hgTHcOP6MT7coaUhtQfXUyqAduPI9vfl-G99lPEV7CMEsIpSJ_HicZFQsQB29Ck3_i3I8P5sV099VlWshA84D3K0iKl3KK507HzusMIS1qdcbtDuzlmgJrCxAwaAkI4jV7r3_kWLLzfCeYG8sBtAOzTqor_7Ox6CKZ2QaLy8KNGJxl2ijjyOZYJa9x1talx8N4X1iIRnz5Eyz2DsBQaJwlcjAbcSs-I91bswEn9gvoMeIeN_WUeU-1Pahl33omPYcSr5lKt1C_0Fhfn3fxYLIf0Ul7paL65jNLq958L6HwLHiL0YVZ1zhK5RHQBnTa-GUxm5BZkfbNn5eYKY3P2rh4DuN4nyCY-5k7pYq_z5FDnq6hMehaVbdt94IDzACwkrRo6He7GQuH3xi2VQidMWaFpCLiX3xE3HNJMpuCK-OWrnU_xgYb98qPl0AgEfLjYer2uBpRXmZGFQW0wRpqQC_UwylrnTqACg_xZcJfXe5za5Cl9vdg12mxmD4EK_BtPdddzIIkvzdoK1-vxGvMnLJByL_T3_xsedJYWpWINHzO_NVsOUwAZ7e--DbEwX6nzBjvIGb8yeQpGRdZgsZpLDzZm5orjDfiGNXZ4qtiUfiWlSz30MfE4k0_KkYVgLuHlYC-5h-nPWcspKuBiFPgJ3piVOCLbOViz6oS1PiigbyJLBCWxN8vSjxwKwebBD4TOtQFCai4E5kHwskp0LcZloRxoxBZDgfgBNSqHTa28PHl0xQsf4R4_F1vK_vMVo_qKtxtyUiRoHW4Qs6WlTqgGi7XJ50RbfTaBgmGcujsUAav2fn1LkG2UfIsouwIzZ-TvABUYWS6jpyhGbdMjzVvCKU_ia6L9N9noQaBfPJ_4ruIUToFl2UH580_5_Kh-jgOCFw-2e9YL9nR2uIGQuhy-DOj5_uUXPIZM4A7CA18ELdCPAurHJqUqBgxpoqYNmtwHYx9IYZHehzASRJEIiRK9YleGKCMl9o4AL9qMdzMKvOjH_jXeQ0n69Ka4VyDQD5hQZMMr6MdmtlLneNV9BwB8pskV_i94irngTC6mipMEaU5Beo6jp8otbL5oESKgyJYRcDHaqRGqHkBZe9KDxRt-FYpNuIW1bwyz1i281hbSdMx3y28huJKQXm2V2RKRj1uNM_q8fgNAusJCi4FmUE3VU-gvIlHdtqtbA2VDHRsZdnGl40fXuIm65SwFoFYahIBaUna91HwDXY-gmeqi5ebyp37Q-D-b767B44nl-On4s0NZRxwUJjSH2L76pkhuEJPbT1r21umLuqhctdP7se2BveKaeM0iyQEvCvnpq6gQHn0zqDpVpcsOVyzQ7xcFBhGlmR2cD7ymCkqJbNII9kan2Gredpgav-Dt1TDhsNA== \ No newline at end of file +gAAAAABp53HglM4WDRuXexIFQPDSUXSuxqcNi6txtY2cjLv-FasDnAOY3p1pwjMj6DpGudGsgFTkh7DBVNR0oP4rvion0IeUZK_0Gr7oVcStcNia8inVr77R0mVZj3N697VohVC2JBVhwvzRm3a4cY17hzwjqGY6j2Em-9QAAX16k6BvorX8vm0bwqWm2pboJOO0fcj2dMK_8TnsVzFZDN_H07Fs-2LulQ4F02VEHtaI5Pryv5cVNrG291q9G3s8XNlDuabKmziLCK7Iw0ULgi6B1WVJgJkwvofH1NxN5E4ZAD4ZnCRfHmlpGjjognhSe02sunfx2yXA-BLo2795_2ziEMKsKC_42MjAnWNxMTK3c9px6sylvuV17XN4WRZiANwaxnmrr8cMG0z4DCnWQrcbmX32R5wN7M1XNJSv-2hOmJGUxjXwB4y-vpRKzO1hsC5AA4nOyqKDdevio02uurkg1_z_9G1i1ybPm-b_g_XSmWxpkBNTtxaoEQxDUaHjkcH8Rri2vfGjdWT4LKGnKTkXvTRnkcUo0yxNmNC6lPrI-j9neAKo3WQhhrrYOuXRqOqio4DRvMMD1QrX6hPYzhKqBOK0dPHWU0kxhhKK_TYt1SDYCW6l1r9S15ZmlYku5t7lFyBYOb2_QiecsF3LP9YEBD2YW2YiBIEkWU1Kb1w4rHh6jDOwLdvc8wd-KOUvEZn-OWTFXwEPqrvNeTxZhPgSEeNotywcDrwniG13BdP1tv9v_UG7tBdZBhA23w5L28T8_UxKCnLY2D5sEaTn8MxZtTVTSVZw9I-pBdfABAqJuXUA5_2PnDJVvYGkEMSTrtyxMJZRCzREw5c5XePhtLBLFZiLoO-LzrQk_DjCFKIakuq-1MsYo8VxsqRIm8ZSsPGe6VuuP2hhyYMMDRx8xe-SJ_tFEwjZWWQ7vCycQ0EfUQVWE3d3PxgIyzPYO-Q40F8tnPYLXqJLBHwZj1728bVOIAC292VvzDRVlBCPwaS44wuPKlI1pqnGvm-7qZ9E-QnB5xSqyXDa9fI0Y6mQ8Pru0ng9iivmcAEjOzVqS_FzdW1rbmLE3pBwWZ6Ii-lwnaHXtNqHoBzZgLWrRgeY74IVJ_uHlTQ3hV4grgrBKXWE49La1G9YrYqwMk-DMrdTcSzxobcZQiIzHalLM2EuhFkAXF4m7MOq_xmis0WDo2meNdLP5dvgIxADh_v4r8DcSn8SvO21GN00s4UzgMRvOpyZN6plAq-zdIpzNHKe8q0CCDtCC7BJJaMUNcj974OedQEqPfkvxHKDIIfthQ0WIzHmgowGEHr074p5YrUWgyzTU_852YaAZzYd5Gxt10pVWH_DUS84cPVZ0bmaOrLEBw_NpldL-Z37Pz6wyR7iYA7CIyCthO0dudt7cYZlGXmF3I1FtXS2L2YiEJxTsPk8yiewr3lRCu2cct8WBeETtTC7y-tguDb7xwMHFNvMcB-hBGHjuGGkFodPj5PZuDCr4fOHTX573hmerDrmTupwvTfKti_zpeYGYAJIau3etg28KEn1Feoc18tKUSDwvvd29e5CEYi9jUNSu3NtvW9GpUHnfHhqruvGVfAR-HEKaZqIeEnHzxcsGOt1HGih4VCpdZSBiKCGOFpXYj_1NDpH1pia8T5_Q2WXWCXUBM20b_ZheIA-Atj4YJWNl1BB79G5td9IQ3m1u0Kk1Va95EwvqKCOAeZ-Wa8bT1zFZQMBnyQF8x8353NlnKXSqmKwdoylUDnQVsnzlmXcRgu59wc3cTT5S9-iS1mSVjrF9o9DW5KdLuTh_QqC2SJOCos5p7mC2CMAJ1ZExZhuXHzckMZYBfT3nzcdMXfMx8zFxrCMFNfLI61B2nQkcodmSQlUNIYSOFrGRKXIdZr5CLBjfoGW8-xXZmVhzys92Hfsu4Qi83g9Ya1_f3s0_XXL9FoNjYG988F3fCiRn8sRtw1EJI2bP64xQx1w52atVlJ3wgEooAKO4YngYPTJs-UjKPpt2aW5dD1PksbQ37zXwSIzmKx1-GCmfhbIOIWZm0t9Hs740SPnOdFWctk97jJjMyhaGMepxWVJ1dv6oPWmbv94o8lOwFeXCFpKr2PfVZXCMoLPRCmbw2NBS2o8-AuVMmfAqag9ArXiRL2lNz6-FqG_Ok7qD7d0bkRhgGNyCXlDs349MT1O7mEpeiD1LZClXTxzEoQdV5awPm3qjQO6XtlceIYdwBhX2hRJWWYnSiSvGEkz3wq6uLHy-QczQ8ta2C1lwWfzcUyaggNOLURo5XnHjmsFMDf8TuE-4fSU9bnh8glTd_n0QdiOMPyVUuEcqjvNj761C53KdZ6dU8CMw-mEgaTikPE9fLb2RZCmf-76SG9kv0FuP2JL-LMI3tBLi0oGIMWxj9uw42lIoGA_EsWSdpvFRMyfwCFQusw0eAkPCDZX_Gj46iTiC4JTE7Adtu5Qy-Y4tUt_F5N8-Yysmc2NnpisRcWQQ1_cRxGY173aUb6MQmgqE9UdCxYTZMkWFHCsarbVOuW0zWW5u_NhPY6wqk6xvpDydqFh35l-Nr8_VyRVBksiDpwWXIEePt-UyFZeoLaLU5_go22j7fpX3gug5VDePdbthLRg0L_d2Dl-uJtARUDyncU-9I9537CtLoO5aGwa2Ukzn8bFoPb_XJ7wfpdGi-pQocyVvpTl74Q57sfESSiKlmCO8V-AUuHE9IJAi9SL9kAlLPaVzbr_dRl29e2ZPqNpvr9uCafJIo1TyoJzm94Bkww_X8eRMqG42OH7iYtcdWK8EqnZ44MxzG3aHDIspCDZmUQaCOsU_ZAFkPdQAgg-YW3vjyD_GWsyGBLilWZKadMBcMn1qKVDBmmBwYrM7Atzlut0vHJBUVkWEqo5GX1c9VD-ToCi3NWn9nyrcCQ44ZZriVSWt3PPWOz3d2WGnMpS5tbz0zAa-63JPSLr1AvUdcgp3yRg5YWhzKkIxsQSEQjhIT1A7q5McnESepdsSDKgciqTvdaNgrnTf1DfX39ZQXjyEYzVNUN7i2o1hxLXLa__cGi-LseHOJUwh-z3Eq_WZh8UJVyeGVYbZjuJiPhZdpqngxmMkTGr8pP8Ba7jvEQbbks7ABgbqCGuUFSrDYoIsQFmw04QQM5U5pANiY41I4gAU--zQfBlvH0Ri5_EkPF3Ltzd6UGOjIxPQSD0zetF_KDiE1fIYrpiHblP-WWWC8_6MzvLGZZ0OKucH3giQGXV2QEChjSgWaKYRiJINerE1cEXS_Hy7JhjM2DH7-_JsmuEsQXGU8w2t3Gs3ElfkM4OPPQd2pESPrFS5Qsa_Lsfhdj_JTUAIVngz2QIfjBpIG6SwpN90IN4QMVsPp19Jd7WCa186W7BmgLWSL93tx1genBfRK7wJzMV1D83_3v7fPgcAwQf1wOBVFPmQYE64dtxtTt0ozyYu5UrxXTo8By3Jq6c-m3dtO07lrMFOsH_5U5ISfiIggSJIKkreV3M7d_ZW7w-LPxumcnOUhIzfcTmmmRrFvNjYtWf-n3JV3E1lR8LZiwbKvkgd3B9YjqZkfyApKjmuers3ueQHoRdWKoF507Y7LKQjNN0ZBNz9PQTiUgtaLYbxMqMN0QeKQv2J7pUYCSAkEKZiWhsvP1Ir8LchC-eTVjskyuQvOqcGE8lcJxysDOEuxRLGKV4HQ4Yih2Nz1CcVnnM9f2DZbEg7TaLG3DjUfpo6LningB6_X6NNMooh64A8tpXeech-_3t78g6y3YCC8Brz8dbfXxlAojYumV7IlPQAOqnQ3e4t9NSEuk4Q8lyr-CuOv39q85mxd29-SFty5KTGBSDm1HPO6_bHIzvgKiwl8xDoO6cGNLMpD57uRRD1bEOmgIzvMxR-sYu14NNDahO7EQ7GzWCYCDIc7l-wUmeBNHF3XW12yBmlTcC9XS27e4f8GZp5nzt-G5JFeAQIsYrivDgJ0jUxmmOCENZ8XIsnqHUmIKKKRy5cfINBk75396icwGPiTpsm6FTZHEHcwZF4Rp3MJcrkM5AxZi-ZkZ2iPW4hHkCi5F2HjAj3v35aAoPQpQ5Kj79jq9h63RbfMzOKdSBZXPtDahf6Tu0ueL5oGOWU1UlmKgU3hoFs912_3mKl5xlqyoSYbMVbJVFuvLRDPpuAB4Xu00K-hW1SStbUGmIgHZE0F1LwGn0fq452oVo5AvSzapvLKK3oM7KhSxWJvUfC9Ik3gKAli3Qg9DO4cEV8f9GTknf06T1R1aWk-utFb51xfmWjKYyp_JNzSSOXsqSG-qAhp5rZg-uCGjnFIEsTqnfMQekyiub5OZ99cXN4sbBvkJPPRijcQEvj15Rmy6fAMarM6c8Dq1pPEhdlb5QlOM3tBquipYd0erTk62bmoTNxZMZPgvS1TXXPLcqBpqIjeKRDHnAMUgVdYtSCtpWqHGCjextptrJD19GDX90xnTWxyloWmOLgG7qmlLlD7DY5tse6HznjozLRe9CQGZnBJwSogljW6kDGyY4MPRyCHY7l0ZI7hXA1ll0eqTjJ-XvRSEaCHTVFPeIdgJKqaXysK1sCZ_mmfNFMlLob6ouyGmqsCRiDsyStcqjUlDsf_T05LoNgD0CQwvzPqbRJAqqN6prthm7TZ0q5sPeym-ChuDPssIgXvZfc9luCq0GyowsONZSZTKf4Y_nu-FKZ5FbhhQfHax1k3Wk5C-ILXy73GhMIdvF4HyYhTQ5BxYC88SGpRAtBrPP7Y3TGTgHIIDhuIjBE2UPhm3JhZPfMms94TDvikd5iwYwdTEoWMpTSFOznH_ZSNdEyEPAalxYRTSOp-PBwt0qwhsfbBaQmtfFt27ElShKyf-wPZLhJ28OsvEMctGTHyJ8794r9nXUGUHeT6OD7mDGjVMS7sGAyjPd3zl3E7XpIhvXIgMNM9rvS5gNNLuglpKqco2inSVWUTsYtDVazrEYvnPIoweoKt4LHqRFGkzV9PRs4MYQDX3Ms-VHzpFdCg5e_-aUrbvCACung-3X2ASzxPF5OmNJHMMIoIhPxbieKAnTzXw78ox0s6BSypJB91dnFTzhjinGhwnCsAIk6JoN-lrftxG0c6pK8svEC85Zi4Bp3glFnnhP29tpkvEeRBFfuC-C-GF-oI9_GL5b1X-lsnqA7KFPyWscJ2dl0kaOp2ueIkjAtwZfvzoMccmfcD9JvJHzc557jKseYX8c32qPpbulNoaxPVIptGY_hZAa287vZAW4TBNGJNUFLWOzpExk0YSKOJ0SXnjDW-rWRyowSnbi0N6zKKlM3zlPeaulzmI3MMOCPuU6cybSvX8gVs7yxPUdy1rdo0oXHIMMei_C6Gz3YQflxdd-qVTLw25QGGcb7VWzZV-SWEbqa2-ppPynLNSwO6yYOKqhVhci2nBCg7l1nGKsgu0c1NAN_vE43ez3jWrnKtt_JpCti_27DszhTzkIWJp7aaWvEjuMGhZNGrfnDH0ke55tH5tqPBvZXmWPObb3Fie4tL6VCGkXcxg4HTjSiXYznfTHYxUAEuit3AlaKVlmp0BOzbtsbG1g-UZ6XZRPLznl8Svh4MuqfiCjMEkGWNjkKrH3hB5Npzj1v0Q8c9BiKXx-kU_ctqgpG9OTC2kV26HgraRc5GWXlnSbe0_DGt54qe1elOh-y9cAa2TGOgmuGcO0yhSuAGbJ83ndRnMKMq0dMjp2rHAd5xonU-XrcRM66oxZcY1V3ixQXqoWjZARSITecR6EHMMI-eJOjW2i7cThU-y9fcQf7MtwBJNdizDzYzlZD6bJ-5rO6AWdAz2WNHI8UWurjU-kbixHvgtaqOzuPj0CHXtBicvD_VaXkb_G5MZ6t5B-HdspRIBbhJoZ99cUxJktMB2Z0ChWzBj5VyDMbkPj7fub1LXBO6P002OeZfSuPXJ-NkV5bqVf_sFWUoEwtSUm8qrxrmFIY1TOwLfkPjAJfwLt0wUbGA-qlI5DA3xZlNKMUaNsHIVf7qKERpqYble1nuo38FQjdjmDk-Oq3ITK2mtTOpvLcGyTD_x56P63kfOXfACeic10VGKiNl3WVDVfRrEB4yoCkw5NnI0RfEIqczKDi43Zy2hLE9zFtlfZaw2G7IxeP0fJEVsDukM7_Jp0GEj4gyjqP8roJ_S3dtgH_pS7trob9cBYs-a5fdxtgjWWd-VLV1XwWpylTOT9t5KLVfKaVs-7tc32vQatUBdBGyf69-EEgw9meE9IZkLi91e4qQ3bFjRct8VB2kpem8GQbNjctQ9SbvytM1jNtKdpbnAxwIbFy44GXNqtS7Ati4ZTMf7EqnSTGPhFz9coDw9cquwtSl2MEe1fQF8HAcCDs1pCdU6CPUMAWI7TlS3VPInKnyRxkPen-XWr9sOf-dls1hRkQoIT_Uz950pXhiQ2-9w0nrf0SRAFBxJZhnb97tsCCZZ9OXSXMEHU8SU_zYCHcBGBhiFZPDaxiHQ9ND9xLo_hGv-mSX8Z0kYNzcu_k0WHj9B88TUtOeCO_ZOshwPFw5qilU0Xd7vBhUIuPo0pyBsB55L5TDsXbqaQWEChJQ_kAZwV3eb6WuNoXXjCsY84I3iXoL8YVRzLxEL0qCGQgULqVcdjvRpfZ_sqk9idOcud5L_ZmjPFwlYRnvmwk-i5n3btYEnU2LXyFVH8idWOT4aoPyhLmhClpJtFv3PmcwmvJZKg7qB0yi5GSAn-jwUJ9B2s1HPOZ47DwsV6tuLu1uCiQYOx98yKdiWRVc8YDNSH9A9SvsgVxG2f93QG_KG23PYkAl3uYOGTTj0omf_QJQLBRR5Kx9EFiiu7OTIeyfVX2p5cOE17QF7ANDPFenkNi3q2SnWGue1_h3ekuRDyxbZvJofE3anCYIoeDmcotuPztZTUlh9zdihdCq-9FmTJ9Hw4l08Rv6mwYaofpALUEf697HOq-7mo9-p7J6OEfbgOl6kCmWrXUWEH0TCoI5VNQK0Ix4nheCN7btiqzOTY1mw3kP6kqJfmfhPo-yhtZTVc1_o18nv0YKCvO5DdlNxv7gHc2YGcevvsi9vkoM1FvdnGsIMVNL_O8kbl--vKStlUBvmw9d1zzfVz3wobUaNcwwoNiVorj-ryLU1Ul0fmFE9-r-9Z551KDIq__j85PI5zwXo1va40Tdv5KT78i4pSYX2tEJS9PNtzuHqg9LYNZncgYe5aUZqF1RHK4Skyyr1qhJjtNagtNgE3jmnnrvoASjqbsysDygTlIgBYhJiY0m9J40GknLD6TP8FzVfcKspVFjeZyru3rCnkeHB4ibZrHLJjyTNnqFcySkDPhfmrItgCQON3nxKuDkr9cfSBfnAXjXVghLZWzqZAbuYHVBNlVvRkYW0qOs_jpWeYmsFvGfcvUfOL7ncmYP072_rhsoKPsZQCTQo8qSgtBHnv7VzbTJ3YwSe56ge8s5LTCcgLOywWk5WE6HaeLyq5MisuL6vNUeNhlUhKXjHEfRhBIyxT9xZnOV1qHq36It4trNuiIA-LPadCMo0tqawg09ow5IS4MFK7HmpAuUCBWt4mgQfv-Rap_90WcBhTzmmCR3u1wAAC0mTMlXFLPn8jTSOBUqMm4nFa9bEGBVqgtqj8h1NxhbofCnLbIbaYh7arZfIldrmphgQzUJ7QyeDAKMlOmoND7ZpgPESI3qUSn9sKBri0ymKmmrdcNrB1gaMrXNdBnt7m-E6ckaCcc33C7ukTncQzds2rxfvY2db9zrUF1bz3ch98g3yD5kU9s7REJzwJlf-vdKPgEkMfhZyF03wdze2QTZRHgfn0geNVy131YqiW7YMxhMRhML7yy_ZBPkOMmjJTrUlK4_Q-_w01jCd_Ahrddcwn3_7znjZQAcR6N3GQ2Qv9bksCP9GIic71UL2jY4KzI4fReVRmKndK0VI0o8AsYHDM7Of1P2GQHpwyqWvuDohUPugowirdkuYiRidUGeNHfCvGz0RznnBRyU1PQXbggyE9mM8yuc9S2XV3A8XWlCc-1N6g-6wI-BjE5kHcERVTx6GmQ29aqaRox278HiP-g9I1IKvIZaK11-kTUa74dZ2M97MdEg2ADntWB_9MEv4b0to5PJDnrfG33TujJtCvoeslTB8Os0OoQdZuCf2D_rSmEb5sUU75Llsepi-1u4Nlj-3IlTdnkYKWW1308FKI75dOV0fQDgti4BeaaVJ0wMwAut40M_eo_0mKptUwQVZDHKJyVKqpLH4odpGfddcuDJ01yjtc3a_ViLGEGIO2S0WvzVW-TF5tjfmqnf1laTL0Cec9PdsvOT9mjWk7nySu6NtcC6xu2c9j46N41f9IXw3ARGcn6N0FSe_cwa4VEZR3GK_iuZ1QBE8TX2ZhDZyId97y6c0PT6UGFJ4S-57hqq1MD_p2RdOPilZzTRqOQbwgCRDIJDvK_HRjpoxCYefVSpPX74bxMp30fvl_J6YViyhDIw2Fa5xHNB7GotzddXjv1LHFyNBCz49FSF5UrBYso2fgXo8ow3GW77iRP_hkeHhg9D_JYdAleoRuntdwAQWsGpv_R9X3MdfGiHb_3wxyTSl1gHkphuCr5y-NNGyjfAx2ESLCwobfokMCJVvVSuwnYDmBFs30pPnJaf3qNj37BlwvtlcLt8T4iypJUZgmGqBpHd4-kifhqEr-A1AGbl8mwy0yUUdjDX5mIooZ-h7YYHMDDEW09HYNp0Ma2v0S-w_a6KGQFieilIOULDG-f4YU2tYJNxsyko0jdU9UEA22LAiv8JXqwY-K7t3GadtPidHOKhZulVbGU4tLsCiTyg0i3nHpYqwRzFCXro2l9W6dFkbC453AlCpTCZE0piaaCMoc0Y7VAN_jmIqG3xo1mbaU55KxvEb7tmyPK3hiEi_Gf3BoLycTXme1j-ckAUdy2rYw4LB6DmqPHIilmxQSQX8Ol7CVRNf3bP0kw70ACph2joxA4rE4fvFc1hU_l3IqimYtG-jKzjcQcUZwP1Bd3AJ27j_85TlStYP93D12J4UNfu22rsKtpQWOuQhu2yiMQZrs2huNtArjYUeaP9ZYDZB4Z6_Yi9DwcI-RV2VFlwkf-CJnHn22j2_WK4zPzBZfRhYnb8iWv9rayXN_vZM_-bfhHNUbFy6NnFNIlrofUZmnGNKtVbqu65oAv2l74V0gJtWBEFy6yKor4qE0oBH-w7m1WI6mH6Ptha8_AY5AIYZ8ksHkxUWzCFLOM0MPMLmgqiET6OO8exmXvthqThM7lSxvtXu4QOKmGBVF5QqD5MQOIMO39DNVEvHJWlXLX1nbaXNy-ihQx5Ri9CFyZqWh29qqswhAeiltVytCaRnlNYY0B1aQW8MYLEcrY8b9MPUKRI5daHj75rHVjmx3SIIVb_wQojP_uP2EGXIocSXSVMQeBA8QJxIp4t-2R_fe0Py9GTzcvDOFcbTSw83_bRPrNKbpgemxFJzPkMSuCb7O3ivEQSoXUUFKvFT8lOKUz0CBjW7oo2k9MENClOWATvVdf9XctRZ63NgCahgtZ2aa6O0_qf7Y_nMfJpGcKJQRIdmRMKjj6Nu4KxbXX33LXwd0YSXZ5pqIMCsaslCP_4-bqV8apnkGV66WxTV5WWvNxguKKQbC82vK8d0j0DeNio_ZqBdw7lJfFAor5G43NWAPaEWeZgRb-zZ91Lyl5eWoWvy3hltNz-RFM6QsWSY_Iiokg5nmgtCn_SrqpZihcAXT958M6guXORwZjukjrgRIxwfC_mmFntT7TCm8RYHgMTxEpyOEHNQrmAcG0tCiATjooTp2JbMVmaTDVUosjP7_zVOWTUYexNLR9V-nQBwcM60y0Yyo18avd4KgtVAtWkoBq3GlBFQt6DUJFMY-Wt_O8vN-5Cz8tn8CJOMaBG1eb53tE1zmdbpDaBfjF5f9iqNg20KbcmtvKHSm9Zo_oM0XjqkQ7BPCoJDBZlUhiUNugeTJ4K6HwHEgUGPYIwB3XNPW1P-47xxwAq7Ls0rr7vjIrTvpDUEJTVjEcWy_jZgFNYr1HkiGe5WTZE_EwSqfl2LsXKEQ3DHtv_rHGDYfxgz-6xnNEi2LXW1p5CIOGXXy7g== \ No newline at end of file diff --git a/usage_tracker.py b/usage_tracker.py index 7ffb198..576f63e 100644 --- a/usage_tracker.py +++ b/usage_tracker.py @@ -51,7 +51,7 @@ def _get_comfyui_base_path() -> Optional[str]: """Get ComfyUI root directory dynamically.""" try: import folder_paths - return folder_paths.base_path + return getattr(folder_paths, "base_path", None) except ImportError: return None diff --git "a/\344\270\200\351\224\256\344\270\212\344\274\240\344\273\243\347\240\201.bat" "b/\344\270\200\351\224\256\344\270\212\344\274\240\344\273\243\347\240\201.bat" deleted file mode 100644 index 32c9599..0000000 --- "a/\344\270\200\351\224\256\344\270\212\344\274\240\344\273\243\347\240\201.bat" +++ /dev/null @@ -1,48 +0,0 @@ -@echo off -chcp 65001 >nul -cls -echo ======================================================== -echo Batchbox 代码上传助手 (Upload Helper) -echo ======================================================== -echo. -echo [1/4] 正在保存修改... (Saving changes) -git add . -git commit -m "Update: Separate providers config to secrets.yaml" -echo. - -echo [2/4] 设置远程仓库 (Setting up remote) -echo 这里的“账号不一样”意味着你需要把代码上传到属于你新账号的仓库。 -echo 请先登录你的新 GitHub 账号,创建一个新的【空仓库】(Empty Repository)。 -echo. -set /p REPO_URL="请粘贴新的仓库 HTTPS 链接 (例如 https://github.com/你的名字/项目名.git): " -echo. - -if "%REPO_URL%"=="" goto error - -echo [3/4] 切换链接至新仓库... -git remote remove origin -git remote add origin %REPO_URL% -git branch -M main - -echo. -echo [4/4] 开始上传... (可能会弹出登录窗口,请登录新账号) -git push -u origin main - -if errorlevel 1 ( - echo. - echo [!] 普通上传受阻,尝试强制上传... - git push -u origin main --force -) - -echo. -echo ======================================================== -echo 全部完成 (All Done) -echo ======================================================== -echo 现在你可以去 GitHub 刷新页面,查看代码是否已上传。 -echo 注意检查:secrets.yaml 应该不会被上传。 -pause -exit - -:error -echo [!] 错误:未输入链接。脚本已退出。 -pause