## 使用 Mellon 搭配自定义块

[Mellon](https://github.com/cubiq/Mellon) 是一个可视化工作流界面，与 Modular Diffusers 集成，专为基于节点的工作流而设计。

> [!WARNING]
> Mellon 目前仍处于早期开发阶段，尚未准备好用于生产环境。请将其视为对集成方式的一次预览！

在 Mellon 中，自定义块开箱即用——只需在您的仓库中添加一个 `mellon_pipeline_config.json` 即可。该配置文件用于告知 Mellon 如何将您块的参数渲染为 UI 组件。

以下是使用 [Gemini Prompt Expander](https://huggingface.co/diffusers/gemini-prompt-expander-mellon) 块时的实际效果：

![Mellon custom block demo](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/modular_demo_dynamic.gif)

要在 Mellon 中使用模块化的 diffusers 自定义块：
1. 从 ModularDiffusers 部分拖入一个 **Dynamic Block Node**
2. 输入 `repo_id`（例如 `diffusers/gemini-prompt-expander-mellon`）
3. 点击 **加载自定义块**
4. 节点会变换以显示您块的输入和输出

现在，让我们逐步讲解如何为你自己的自定义块创建此配置。

## 创建 Mellon 配置的步骤

1. **为参数指定 Mellon 类型** - 每个 `InputParam`/`OutputParam` 都需要一个类型，用于告知 Mellon 应渲染哪个 UI 组件（例如 `"textbox"`、`"dropdown"`、`"image"`）。
2. **生成 `mellon_pipeline_config.json`** - 使用我们的工具生成配置模板并将其推送到您的 Hub 仓库。
3. **（可选）手动调整配置** - 根据您的具体需求微调生成的配置。

## 为参数指定 Mellon 类型

Mellon 类型决定了每个参数在 UI 中的渲染方式。如果未为某个参数指定类型，它将默认为 `"custom"`，并渲染为一个简单的连接点。您始终可以在生成的配置中稍后调整此设置。

| 类型 | 输入/输出 | 描述 |
|------|--------------|-------------|
| `image` | 两者 | 图像（PIL Image） |
| `video` | 两者 | 视频 |
| `text` | 两者 | 文本显示 |
| `textbox` | 输入 | 文本输入 |
| `dropdown` | 输入 | 下拉选择菜单 |
| `slider` | 输入 | 用于数值的滑块 |
| `number` | 输入 | 数值输入 |
| `checkbox` | 输入 | 布尔切换 |

对于需要更多配置（例如带有选项的下拉菜单，或带有最小值/最大值的滑块）的参数，请直接传入 `MellonParam` 实例，而不是字符串。您可以使用下面的类方法之一，或者使用 `MellonParam(name, label, type, ...)` 创建一个完全自定义的实例。

| 方法 | 描述 |
|--------|-------------|
| `MellonParam.Input.image(name)` | 图像输入 |
| `MellonParam.Input.textbox(name, default)` | 作为文本区域的文本输入 |
| `MellonParam.Input.dropdown(name, options, default)` | 下拉选择 |
| `MellonParam.Input.slider(name, default, min, max, step)` | 用于数值的滑块 |
| `MellonParam.Input.number(name, default, min, max, step)` | 数值输入（无滑块） |
| `MellonParam.Input.seed(name, default)` | 使用随机化按钮为输入设置随机种子 |
| `MellonParam.Input.checkbox(name, default)` | 布尔复选框 |
| `MellonParam.Input.model(name)` | diffusers 组件的模型输入 |
| `MellonParam.Output.image(name)` | 图像输出 |
| `MellonParam.Output.video(name)` | 视频输出 |
| `MellonParam.Output.text(name)` | 文本输出 |
| `MellonParam.Output.model(name)` | diffusers 组件的模型输出 |

选择以下方法之一来指定 Mellon 类型。

### 在块定义中使用 `metadata`

如果您正在从头定义一个自定义块，请直接在 `InputParam` 和 `OutputParam` 定义中添加 `metadata={"mellon": "<type>"}`。如果您正在编辑 Hub 上现有的自定义块，请参阅 [编辑自定义块](./custom_blocks#editing-custom-blocks) 了解如何将其下载到本地。

```python
class GeminiPromptExpander(ModularPipelineBlocks):
    
    @property
    def inputs(self) -> List[InputParam]:
        return [
            InputParam(
                "prompt",
                type_hint=str,
                required=True,
                description="Prompt to use",
                metadata={"mellon": "textbox"},  # Text input
            )
        ]
    
    @property
    def intermediate_outputs(self) -> List[OutputParam]:
        return [
            OutputParam(
                "prompt",
                type_hint=str,
                description="Expanded prompt by the LLM",
                metadata={"mellon": "text"},  # Text output
            ),
            OutputParam(
                "old_prompt",
                type_hint=str,
                description="Old prompt provided by the user",
<CopyLLMTxtMenu containerStyle="float: inline-end; margin-inline-start: 10px; display: inline-flex; position: relative; z-index: 10;"></CopyLLMTxtMenu>

                # No metadata - we don't want to render this in UI
            )
        ]
```

若要完全控制 UI 配置，请直接传入一个 `MellonParam` 实例：
```python
from diffusers.modular_pipelines.mellon_node_utils import MellonParam

InputParam(
    "mode",
    type_hint=str,
    default="balanced",
    metadata={"mellon": MellonParam.Input.dropdown("mode", options=["fast", "balanced", "quality"])},
)
```

### 在生成配置时使用 `input_types` 和 `output_types`

如果您正在使用现有的流水线，或者希望保持块定义简洁，可以在生成配置时通过 `input_types/output_types` 参数指定类型：
```python
from diffusers.modular_pipelines.mellon_node_utils import MellonPipelineConfig

mellon_config = MellonPipelineConfig.from_custom_block(
    blocks,
    input_types={"prompt": "textbox"},
    output_types={"prompt": "text"}
)
```

> [!NOTE]
> 当同时指定了 `metadata` 和 `input_types`/`output_types` 时，参数会覆盖 `metadata`。

## 生成并推送 Mellon 配置

向您的 block 添加元数据后，生成默认的 Mellon 配置模板并将其推送到 Hub：

```python
from diffusers import ModularPipelineBlocks
from diffusers.modular_pipelines.mellon_node_utils import MellonPipelineConfig

# load your custom blocks from your local dir
blocks = ModularPipelineBlocks.from_pretrained("/path/local/folder", trust_remote_code=True)

# Generate the default config template
mellon_config = MellonPipelineConfig.from_custom_block(blocks)
# push the default template to `repo_id`, you will need to pass the same local folder path so that it will save the config locally first
mellon_config.save(
    local_dir="/path/local/folder",
    repo_id= repo_id,
    push_to_hub=True
)
```

这会在你的仓库中创建一个 `mellon_pipeline_config.json` 文件。

## 审查并调整配置

生成的模板只是一个起点——您可能需要根据自身需求对其进行调整。下面我们来逐步了解 Gemini Prompt Expander 的生成配置：

```json
{
  "label": "Gemini Prompt Expander",
  "default_repo": "",
  "default_dtype": "",
  "node_params": {
    "custom": {
      "params": {
        "prompt": {
          "label": "Prompt",
          "type": "string",
          "display": "textarea",
          "default": ""
        },
        "out_prompt": {
          "label": "Prompt",
          "type": "string",
          "display": "output"
        },
        "old_prompt": {
          "label": "Old Prompt",
          "type": "custom",
          "display": "output"
        },
        "doc": {
          "label": "Doc",
          "type": "string",
          "display": "output"
        }
      },
      "input_names": ["prompt"],
      "model_input_names": [],
      "output_names": ["out_prompt", "old_prompt", "doc"],
      "block_name": "custom",
      "node_type": "custom"
    }
  }
}
```

### 理解结构

`params` 字典定义了每个 UI 元素的渲染方式。`input_names`、`model_input_names` 和 `output_names` 列表将这些 UI 元素映射到底层 [ModularPipelineBlocks](/docs/diffusers/main/zh/api/modular_diffusers/pipeline_blocks#diffusers.ModularPipelineBlocks) 的 I/O 接口：

| Mellon 配置 | ModularPipelineBlocks |
|---------------|----------------------|
| `input_names` | `inputs` 属性 |
| `model_input_names` | `expected_components` 属性 |
| `output_names` | `intermediate_outputs` 属性 |

在此示例中：`prompt` 是唯一的输入。没有模型组件，输出包括 `out_prompt`、`old_prompt` 和 `doc`。

现在让我们来看看 `params` 字典：

- **`prompt`**：一个输入参数，具有 `display: "textarea"`（渲染为文本输入框）、`label: "Prompt"`（在 UI 中显示）以及 `default: ""`（初始为空）。`type: "string"` 字段在 Mellon 中非常重要，因为它决定了哪些节点可以连接在一起——只有类型匹配的节点才能通过“noodles”进行链接。

- **`out_prompt`**：扩展后的提示词输出。由于输入和输出共享相同的名称（`prompt`），系统自动添加了 `out_` 前缀，以避免配置中的命名冲突。它具有 `display: "output"` 属性，因此会渲染为输出插槽。

- **`old_prompt`**：由于我们未指定元数据，其类型为 `type: "custom"`。在 UI 中，它会显示为一个简单的圆点。由于我们实际上并不希望在 UI 中暴露它，因此可以将其移除。

- **`doc`**：文档输出，会自动添加到所有自定义块中。

### 进行调整

由于您不再需要使用 `old_prompt`，请将其从 `params` 和 `output_names` 中移除。

```json
{
  "label": "Gemini Prompt Expander",
  "default_repo": "",
  "default_dtype": "",
  "node_params": {
    "custom": {
      "params": {
        "prompt": {
          "label": "Prompt",
          "type": "string",
          "display": "textarea",
          "default": ""
        },
        "out_prompt": {
          "label": "Prompt",
          "type": "string",
          "display": "output"
        },
        "doc": {
          "label": "Doc",
          "type": "string",
          "display": "output"
        }
      },
      "input_names": ["prompt"],
      "model_input_names": [],
      "output_names": ["out_prompt", "doc"],
      "block_name": "custom",
      "node_type": "custom"
    }
  }
}
```

最终配置请参见 [diffusers/gemini-prompt-expander-mellon](https://huggingface.co/diffusers/gemini-prompt-expander-mellon)。

