# Comfyui_o1key 开发指南

## 对话原则
始终使用中文进行对话。

## 编码规范 ⚠️ 重要

### 文件编码要求
- **所有文本文件必须使用 UTF-8 编码（无 BOM）**
- **行结束符使用 LF（Unix 风格），Windows 批处理文件除外（CRLF）**
- 项目已配置 `.gitattributes` 和 `.editorconfig` 来自动处理编码

### 编辑器配置
确保编辑器设置：
- 文件编码：UTF-8（无 BOM）
- 行结束符：LF
- 自动插入文件末尾空行：开启

## Git 提交规范

### Commit Message 规范
- **所有 commit message 必须使用英文**，避免中文编码问题
- 使用 Conventional Commits 格式：`<type>: <description>`

### 常用类型
- `feat`: 新增功能
- `fix`: 修复问题
- `docs`: 文档更新
- `refactor`: 代码重构
- `style`: 代码格式调整
- `test`: 测试相关
- `chore`: 构建/工具配置

### 示例
```bash
git commit -m "feat: add new model support"
git commit -m "fix: resolve image encoding issue"
git commit -m "docs: update README installation guide"
```

## 配置文件管理

### 基本原则

`.config` 文件包含敏感信息（API 密钥），已添加到 `.gitignore` 中，**不会被提交到版本控制**。

### 配置方式

用户通过以下方式创建本地配置：

1. **快捷脚本**（推荐）
   - Windows: 双击 `设置API密钥(win).bat`
   - Linux/Mac: 运行 `./设置API密钥(mac).sh`
   - 脚本会自动创建 `.config` 文件

2. **手动创建**
   - 参考 `.config.example` 模板
   - 在插件根目录创建 `.config` 文件
   - 填写 API 密钥

3. **环境变量**
   - 设置 `O1KEY_API_KEY` 环境变量
   - 无需创建配置文件

### 注意事项

- `.config` 文件仅存在于本地，不会被 Git 追踪
- 开发者无需担心意外提交密钥的问题
- 提交代码时会自动忽略 `.config` 文件

## 项目概述

这是一个 ComfyUI 自定义节点插件，通过 api.o1key.com 调用 AI 模型进行图像生成。

### 技术栈
- Python 3.7+
- ComfyUI 框架
- aiohttp (异步 HTTP)
- Pillow (图像处理)
- PyTorch (张量处理)

---

## 目录结构

```
Comfyui_o1key/
├── __init__.py              # 节点注册入口
├── models_config.py         # 模型配置中心 ⭐ 管理所有支持的模型
├── version.txt              # 版本号文件
├── update.bat               # Windows 自动更新脚本
├── update.sh                # Linux/Mac 自动更新脚本
├── nodes/                   # 节点模块
│   ├── __init__.py
│   ├── nano_banana_pro.py   # NanoBananaPro 节点
│   └── batch_nano_banana_pro.py  # 批量节点
├── utils/                   # 工具模块
│   ├── __init__.py
│   ├── image_utils.py       # 图像转换工具
│   ├── config.py            # 配置管理
│   └── update_checker.py    # 更新检查器
├── clients/                 # API 客户端
│   ├── __init__.py
│   ├── base_client.py       # 客户端基类
│   └── gemini_client.py     # Gemini API 客户端
├── .config.example          # 配置文件模板
├── requirements.txt         # 依赖包
└── README.md                # 用户文档
├── 设置API密钥(win).bat     # Windows 配置脚本
└── 设置API密钥(mac).sh      # Mac/Linux 配置脚本

注：.config 文件在本地自动创建，不提交到版本控制
```

---

## 模型管理系统

### 概述

所有 Nano Banana Pro 支持的模型都在 `models_config.py` 中统一管理。要添加新模型或临时关闭某个模型，只需编辑这个文件即可。

### 模型配置文件 (models_config.py)

#### 配置结构

```python
GEMINI_MODELS = [
    {
        "id": "gemini-3-pro-image-preview-url",
        "description": "URL 模式,根据分辨率自动选择端点 (1K/2K/4K)",
        "enabled": True,
        "endpoint_type": "dynamic",
        "endpoint": None  # 动态端点，由代码根据分辨率选择
    },
    {
        "id": "gemini-3-pro-image-preview",
        "description": "标准模式,固定端点",
        "enabled": True,
        "endpoint_type": "standard",
        "endpoint": "/v1beta/models/gemini-3-pro-image-preview:generateContent"
    },
    # 更多模型...
]
```

#### 字段说明

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `id` | string | 是 | 模型标识符，用于 API 调用 |
| `description` | string | 是 | 模型描述，说明特点和适用场景 |
| `enabled` | boolean | 是 | 是否启用该模型（false 则在节点中隐藏） |
| `endpoint_type` | string | 是 | 端点类型："dynamic", "standard", "flatfee" |
| `endpoint` | string | 是 | API 端点路径（动态端点设为 None） |

#### 端点类型说明

- **dynamic**: 根据分辨率动态选择端点（如 gemini-3-pro-image-preview-url）
- **standard**: 使用固定端点（如 gemini-3-pro-image-preview）
- **flatfee**: 固定费用模式端点（如 gemini-3-pro-image-preview-flatfee）

### 常见操作

#### 1. 添加新模型

在 `GEMINI_MODELS` 列表末尾添加新模型：

```python
GEMINI_MODELS = [
    # ... 现有模型 ...
    {
        "id": "gemini-新模型名称",
        "description": "新模型的描述和特点",
        "enabled": True,
        "endpoint_type": "standard",  # 根据实际情况选择
        "endpoint": "/v1beta/models/gemini-新模型名称:generateContent"  # 配置端点
    }
]
```

**注意**：
- **固定端点模型**：直接在 `endpoint` 字段填写完整的端点路径即可，无需修改代码
- **动态端点模型**：如果模型需要根据分辨率动态选择端点，设置 `endpoint_type: "dynamic"` 和 `endpoint: None`，并在 `gemini_client.py` 的 `get_endpoint()` 方法中添加对应逻辑

#### 2. 临时关闭模型

将模型的 `enabled` 字段设为 `False`：

```python
{
    "id": "gemini-3-pro-image-preview-url",
    "description": "URL 模式",
    "enabled": False,  # 临时关闭
    "endpoint_type": "dynamic"
}
```

关闭后，该模型将不会出现在 ComfyUI 节点的下拉列表中。

#### 3. 重新启用模型

将 `enabled` 改回 `True`：

```python
{
    "id": "gemini-3-pro-image-preview-url",
    "enabled": True,  # 重新启用
    # ...
}
```

#### 4. 修改模型描述

直接编辑 `description` 字段：

```python
{
    "id": "gemini-3-pro-image-preview",
    "description": "标准模式,固定端点,适用于常规图像生成",  # 更新描述
    # ...
}
```

### 工具函数

`models_config.py` 提供了一些工具函数，可在代码中使用：

```python
from ..models_config import (
    get_enabled_models,      # 获取启用的模型列表
    get_all_models,          # 获取所有模型（包括禁用的）
    get_model_config,        # 获取指定模型的完整配置
    is_model_enabled,        # 检查模型是否启用
    get_model_description,   # 获取模型描述
    get_endpoint_type,       # 获取端点类型
    get_model_endpoint       # 获取模型端点
)

# 示例：获取启用的模型
enabled = get_enabled_models()
# ['gemini-3-pro-image-preview-url', 'gemini-3-pro-image-preview', ...]

# 示例：获取模型配置
config = get_model_config("gemini-3-pro-image-preview-url")
# {'id': '...', 'description': '...', 'enabled': True, 'endpoint_type': 'dynamic', 'endpoint': None}

# 示例：获取模型端点
endpoint = get_model_endpoint("gemini-3-pro-image-preview")
# '/v1beta/models/gemini-3-pro-image-preview:generateContent'
```

### 节点集成

所有使用模型列表的节点都会自动从 `models_config.py` 加载：

```python
from ..models_config import get_enabled_models

class NanoBananaPro:
    @classmethod
    def INPUT_TYPES(cls):
        # 自动从配置加载启用的模型
        enabled_models = get_enabled_models()
        
        return {
            "required": {
                "模型": (enabled_models, {
                    "default": enabled_models[0]
                }),
                # ...
            }
        }
```

### 配置验证

`models_config.py` 在加载时会自动验证配置：

- 检查每个模型是否有必需字段（id, description, enabled, endpoint_type, endpoint）
- 检查 `endpoint_type` 是否合法（dynamic, standard, flatfee）
- 检查非动态端点模型必须配置有效的 `endpoint`
- 检查端点格式是否正确（应以 `/v1beta/models/` 开头）
- 确保至少有一个模型是启用的

如果配置不合法，会在终端打印警告信息。

### 最佳实践

1. **添加新模型前**：
   - 确认模型使用 Gemini 原生接口格式
   - 确认端点规则（dynamic/standard/flatfee）
   - 编写清晰的描述说明

2. **临时测试**：
   - 关闭其他模型，只启用测试模型
   - 验证功能后再重新启用其他模型

3. **版本控制**：
   - `models_config.py` 应纳入版本控制
   - 重大模型变更应记录在 `CHANGELOG.md` 中

4. **文档更新**：
   - 添加新模型后，更新 `README.md` 中的模型列表
   - 如有特殊使用说明，添加到文档中

---

## 开发新节点流程

### 1. 创建节点文件

在 `nodes/` 目录下创建新的 Python 文件：

```python
# nodes/my_new_node.py

from typing import Optional, Tuple
import torch

from ..utils.image_utils import tensor_to_pil, pil_to_tensor
from ..clients.gemini_client import GeminiAPIClient


class MyNewNode:
    """节点描述"""
    
    def __init__(self):
        self.client = None
    
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "prompt": ("STRING", {"default": "", "multiline": True}),
                # 更多参数...
            },
            "optional": {
                "images": ("IMAGE",)
            }
        }
    
    RETURN_TYPES = ("IMAGE",)
    RETURN_NAMES = ("images",)
    FUNCTION = "execute"
    CATEGORY = "image/generation"
    
    def execute(self, prompt: str, images: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor]:
        # 实现逻辑
        pass
```

### 2. 注册节点

在 `nodes/__init__.py` 中添加导出：

```python
from .my_new_node import MyNewNode
__all__ = ['NanoBananaPro', 'MyNewNode']
```

在根 `__init__.py` 中注册：

```python
from .nodes import NanoBananaPro, MyNewNode

NODE_CLASS_MAPPINGS = {
    "NanoBananaPro": NanoBananaPro,
    "MyNewNode": MyNewNode
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "NanoBananaPro": "Nano Banana Pro",
    "MyNewNode": "My New Node"
}
```

### 3. 更新 CHANGELOG.md

记录新增功能。

---

## ComfyUI 节点规范

### INPUT_TYPES 参数类型

| 类型 | 格式 | 示例 |
|------|------|------|
| 字符串 | `("STRING", {...})` | `("STRING", {"default": "", "multiline": True})` |
| 整数 | `("INT", {...})` | `("INT", {"default": 1, "min": 1, "max": 100})` |
| 浮点数 | `("FLOAT", {...})` | `("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1})` |
| 下拉选项 | `([...], {...})` | `(["option1", "option2"], {"default": "option1"})` |
| 图像 | `("IMAGE",)` | 放在 optional 中 |

### 返回值规范

```python
RETURN_TYPES = ("IMAGE", "MASK", "STRING")  # 类型元组
RETURN_NAMES = ("images", "mask", "text")   # 名称元组
```

### 必须的类属性

```python
FUNCTION = "execute"           # 执行函数名
CATEGORY = "image/generation"  # 节点分类路径
```

---

## 工具模块使用

### 图像转换 (utils/image_utils.py)

```python
from ..utils.image_utils import tensor_to_pil, pil_to_tensor

# ComfyUI Tensor → PIL Image 列表
pil_images = tensor_to_pil(tensor)  # tensor: [B, H, W, C], range [0, 1]

# PIL Image 列表 → ComfyUI Tensor
tensor = pil_to_tensor(pil_images)  # 返回 [B, H, W, C], range [0, 1]

# PIL → Base64
from ..utils.image_utils import encode_image_to_base64
b64_str = encode_image_to_base64(pil_image)

# Base64 → PIL
from ..utils.image_utils import decode_base64_to_pil
pil_image = decode_base64_to_pil(b64_str)
```

### 配置管理 (utils/config.py)

```python
from ..utils.config import get_api_key, get_api_key_or_raise, load_config, get_api_base_url

# 获取 API 密钥（返回 None 如果未找到）
api_key = get_api_key("O1KEY_API_KEY")

# 获取 API 密钥（抛出异常如果未找到）
api_key = get_api_key_or_raise("O1KEY_API_KEY")

# 获取 API 基础 URL（统一配置）
base_url = get_api_base_url()  # 默认: https://vip.o1key.com

# 加载完整配置
config = load_config()
```

### API 基础 URL 配置

所有 API 客户端都使用统一的基础 URL 配置，默认为 `https://vip.o1key.com`。

#### 配置优先级

1. **环境变量** `O1KEY_API_BASE_URL`（优先级最高）
2. **.config 文件**中的 `O1KEY_API_BASE_URL` 配置项
3. **默认值** `https://vip.o1key.com`（在 `utils/config.py` 中定义）

#### 修改 API 地址

**方法 1：修改默认值（影响所有用户）**

编辑 `utils/config.py`：

```python
# 修改此常量
DEFAULT_API_BASE_URL = "https://your-api-domain.com"
```

**方法 2：使用环境变量（推荐，不影响代码）**

在系统环境变量中设置：
```bash
# Windows
set O1KEY_API_BASE_URL=https://your-api-domain.com

# Linux/Mac
export O1KEY_API_BASE_URL=https://your-api-domain.com
```

**方法 3：在 .config 文件中配置**

在插件根目录的 `.config` 文件中添加：
```
O1KEY_API_BASE_URL=https://your-api-domain.com
```

#### 使用示例

所有客户端会自动使用统一配置：

```python
from ..utils.config import get_api_base_url

# 获取当前配置的 API 地址
base_url = get_api_base_url()
print(f"当前 API 地址: {base_url}")
```

---

## API 客户端使用

### 使用 GeminiAPIClient

```python
from ..clients.gemini_client import GeminiAPIClient

# 初始化（自动读取配置）
client = GeminiAPIClient()

# 同步生成（用于 ComfyUI 节点）
images = client.generate_sync(
    prompt="描述文字",
    model="gemini-3-pro-image-preview-url",
    resolution="2K",
    aspect_ratio="1:1",
    batch_size=1,
    images=None,  # 可选：输入图像列表
    progress_callback=None
)
```

### 创建新的 API 客户端

继承 `BaseAPIClient` 并实现抽象方法：

```python
from ..clients.base_client import BaseAPIClient

class MyAPIClient(BaseAPIClient):
    def __init__(self):
        super().__init__(
            base_url="https://api.example.com",
            api_key=get_api_key_or_raise("MY_API_KEY"),
            max_request_size=20 * 1024 * 1024
        )
    
    def get_endpoint(self, **kwargs) -> str:
        return "/v1/generate"
    
    def build_request_body(self, **kwargs) -> dict:
        return {"prompt": kwargs.get("prompt", "")}
    
    def parse_response(self, response: dict) -> Any:
        return response.get("result")
```

---

## API 端点说明

### Gemini 模型端点

**gemini-3-pro-image-preview-url** (根据分辨率动态选择):
- 1K: `/v1beta/models/gemini-3-pro-image-preview-url:generateContent`
- 2K: `/v1beta/models/gemini-3-pro-image-preview-2k-url:generateContent`
- 4K: `/v1beta/models/gemini-3-pro-image-preview-4k-url:generateContent`

**gemini-3-pro-image-preview** (固定端点):
- `/v1beta/models/gemini-3-pro-image-preview:generateContent`

**gemini-3-pro-image-preview-flatfee** (固定端点):
- `/v1beta/models/gemini-3-pro-image-preview-flatfee:generateContent`

### 请求格式

```json
{
  "contents": [{
    "role": "user",
    "parts": [
      {"text": "提示词"},
      {"inline_data": {"mime_type": "image/png", "data": "base64..."}}
    ]
  }],
  "generationConfig": {
    "responseModalities": ["TEXT", "IMAGE"],
    "imageConfig": {
      "aspectRatio": "1:1",
      "imageSize": "2K"
    }
  }
}
```

---

## 代码规范

### 命名约定

- 类名：PascalCase（如 `NanoBananaPro`）
- 函数/方法：snake_case（如 `tensor_to_pil`）
- 常量：UPPER_CASE（如 `API_BASE_URL`）
- 私有方法：前缀下划线（如 `_load_config`）

### 类型注解

所有公开函数必须有类型注解：

```python
def function_name(param1: str, param2: Optional[int] = None) -> List[Image.Image]:
    pass
```

### 文档字符串

使用 Google 风格的 docstring：

```python
def function_name(param1: str, param2: int) -> bool:
    """
    函数简短描述
    
    Args:
        param1: 参数1说明
        param2: 参数2说明
    
    Returns:
        返回值说明
    
    Raises:
        ValueError: 异常情况说明
    
    Example:
        >>> result = function_name("test", 42)
        >>> print(result)
        True
    """
    pass
```

### 错误处理

```python
try:
    # 业务逻辑
    pass
except ValueError as e:
    # 用户输入错误
    print(f"节点名: 输入错误 - {str(e)}")
    raise
except RuntimeError as e:
    # API 或网络错误
    print(f"节点名: API 错误 - {str(e)}")
    raise
except Exception as e:
    # 未知错误
    print(f"节点名: 未知错误 - {str(e)}")
    raise
```

---

## 限制与约束

| 限制项 | 值 | 说明 |
|--------|-----|------|
| 请求体大小 | 20MB | 超过会报错 |
| 输入图像数量 | 14张 | 图生图模式限制 |
| 批次大小 | 1-1000 | 并发生成数量 |
| 支持的分辨率 | 1K/2K/4K | API 限制 |

---

## 测试检查清单

新节点开发完成后，验证以下场景：

- [ ] 文生图基础功能
- [ ] 图生图功能（如支持）
- [ ] 不同分辨率（1K/2K/4K）
- [ ] 不同宽高比
- [ ] 批量生成
- [ ] 错误处理（无 API 密钥、网络错误等）
- [ ] 边界条件（最大图像数、最大批次）

---

## 更新日志

修改代码后，更新 `CHANGELOG.md` 记录变更。

格式：
```markdown
## [版本号] - 日期

### Added
- 新增功能

### Changed
- 变更内容

### Fixed
- 修复问题
```

---

## 版本发布流程

### 1. 准备发布

发布新版本前确认以下事项：

- [ ] 所有功能测试通过
- [ ] 更新 `CHANGELOG.md`（记录本次变更）
- [ ] 更新 `version.txt`（更新版本号）
- [ ] 更新 `README.md`（如有新功能需要说明）

### 2. 版本号规范

遵循语义化版本 (Semantic Versioning)：

- **主版本号** (Major): 重大架构变更、不兼容的 API 修改
- **次版本号** (Minor): 新增功能、向后兼容
- **修订号** (Patch): Bug 修复、小改进

示例：`v1.10.2` → Major.Minor.Patch

### 3. 发布步骤

```bash
# 1. 更新版本号
echo "v1.11.0" > version.txt

# 2. 提交变更
git add .
git commit -m "Release v1.11.0: 添加新功能描述"

# 3. 创建标签
git tag v1.11.0

# 4. 推送到远程
git push origin main --tags
```

### 4. 用户更新

用户运行更新脚本即可获取最新版本：

- **Windows**: 双击 `update.bat`
- **Linux/Mac**: 运行 `./update.sh`

更新脚本会自动：
- 检查远程更新
- 备份配置文件
- 拉取最新代码
- 更新依赖包
- 显示更新日志

---