mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-06 11:47:03 +08:00
Added Ideogram and Minimax back in.
This commit is contained in:
parent
047734a1d2
commit
01494c9b06
11
.github/workflows/update-api-stubs.yml
vendored
11
.github/workflows/update-api-stubs.yml
vendored
@ -22,10 +22,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install 'datamodel-code-generator[http]'
|
pip install 'datamodel-code-generator[http]'
|
||||||
|
npm install @redocly/cli
|
||||||
|
|
||||||
|
- name: Download OpenAPI spec
|
||||||
|
run: |
|
||||||
|
curl -o openapi.yaml https://api.comfy.org/openapi
|
||||||
|
|
||||||
|
- name: Filter OpenAPI spec with Redocly
|
||||||
|
run: |
|
||||||
|
npx @redocly/cli bundle openapi.yaml --output filtered-openapi.yaml --config comfy_api_nodes/redocly.yaml --remove-unused-components
|
||||||
|
|
||||||
- name: Generate API models
|
- name: Generate API models
|
||||||
run: |
|
run: |
|
||||||
datamodel-codegen --use-subclass-enum --url https://api.comfy.org/openapi --output comfy_api_nodes/apis --output-model-type pydantic_v2.BaseModel
|
datamodel-codegen --use-subclass-enum --input filtered-openapi.yaml --output comfy_api_nodes/apis --output-model-type pydantic_v2.BaseModel
|
||||||
|
|
||||||
- name: Check for changes
|
- name: Check for changes
|
||||||
id: git-check
|
id: git-check
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,3 +21,6 @@ venv/
|
|||||||
*.log
|
*.log
|
||||||
web_custom_versions/
|
web_custom_versions/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
openapi.yaml
|
||||||
|
filtered-openapi.yaml
|
||||||
|
uv.lock
|
||||||
|
|||||||
30
comfy_api_nodes/README.md
Normal file
30
comfy_api_nodes/README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# ComfyUI API Nodes
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Below are a collection of nodes that work by calling external APIs. More information available in our [docs](https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
API stubs are generated through automatic codegen tools from OpenAPI definitions. Since the Comfy Org OpenAPI definition contains many things from the Comfy Registry as well, we use redocly/cli to filter out only the paths relevant for API nodes.
|
||||||
|
|
||||||
|
### Redocly Instructions
|
||||||
|
|
||||||
|
**Tip**
|
||||||
|
When developing locally, use the `redocly-dev.yaml` file to generate pydantic models. This lets you use stubs for APIs that are not marked `Released` yet.
|
||||||
|
|
||||||
|
Before your API node PR merges, make sure to add the `Released` tag to the `openapi.yaml` file and test in staging.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Download the OpenAPI file from prod server.
|
||||||
|
curl -o openapi.yaml https://api.comfy.org/openapi
|
||||||
|
|
||||||
|
# Filter out unneeded API definitions.
|
||||||
|
npm install -g @redocly/cli
|
||||||
|
redocly bundle openapi.yaml --output filtered-openapi.yaml --config comfy_api_nodes/redocly-dev.yaml --remove-unused-components
|
||||||
|
|
||||||
|
# Generate the pydantic datamodels for validation.
|
||||||
|
datamodel-codegen --use-subclass-enum --input filtered-openapi.yaml --output comfy_api_nodes/apis/__init__.py --output-model-type pydantic_v2.BaseModel
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# generated by datamodel-codegen:
|
# generated by datamodel-codegen:
|
||||||
# filename: https://api.comfy.org/openapi
|
# filename: filtered-openapi.yaml
|
||||||
# timestamp: 2025-04-23T15:56:33+00:00
|
# timestamp: 2025-04-24T22:16:48+00:00
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# generated by datamodel-codegen:
|
# generated by datamodel-codegen:
|
||||||
# filename: https://api.comfy.org/openapi
|
# filename: filtered-openapi.yaml
|
||||||
# timestamp: 2025-04-23T15:56:33+00:00
|
# timestamp: 2025-04-24T22:16:48+00:00
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# generated by datamodel-codegen:
|
# generated by datamodel-codegen:
|
||||||
# filename: https://api.comfy.org/openapi
|
# filename: filtered-openapi.yaml
|
||||||
# timestamp: 2025-04-23T15:56:33+00:00
|
# timestamp: 2025-04-24T22:29:35+00:00
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -8,34 +8,14 @@ from datetime import datetime
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from pydantic import AnyUrl, BaseModel, Field, confloat, conint
|
from pydantic import BaseModel, Field, conint, constr
|
||||||
|
|
||||||
class Customer(BaseModel):
|
|
||||||
createdAt: Optional[datetime] = Field(
|
|
||||||
None, description='The date and time the user was created'
|
|
||||||
)
|
|
||||||
email: Optional[str] = Field(None, description='The email address for this user')
|
|
||||||
id: str = Field(..., description='The firebase UID of the user')
|
|
||||||
name: Optional[str] = Field(None, description='The name for this user')
|
|
||||||
updatedAt: Optional[datetime] = Field(
|
|
||||||
None, description='The date and time the user was last updated'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Error(BaseModel):
|
|
||||||
details: Optional[List[str]] = Field(
|
|
||||||
None,
|
|
||||||
description='Optional detailed information about the error or hints for resolving it.',
|
|
||||||
)
|
|
||||||
message: Optional[str] = Field(
|
|
||||||
None, description='A clear and concise description of the error.'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorResponse(BaseModel):
|
class ErrorResponse(BaseModel):
|
||||||
error: str
|
error: str
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
class ImageRequest(BaseModel):
|
class ImageRequest(BaseModel):
|
||||||
aspect_ratio: Optional[str] = Field(
|
aspect_ratio: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
@ -71,6 +51,12 @@ class ImageRequest(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IdeogramGenerateRequest(BaseModel):
|
||||||
|
image_request: ImageRequest = Field(
|
||||||
|
..., description='The image generation request parameters.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Datum(BaseModel):
|
class Datum(BaseModel):
|
||||||
is_image_safe: Optional[bool] = Field(
|
is_image_safe: Optional[bool] = Field(
|
||||||
None, description='Indicates whether the image is considered safe.'
|
None, description='Indicates whether the image is considered safe.'
|
||||||
@ -91,6 +77,15 @@ class Datum(BaseModel):
|
|||||||
url: Optional[str] = Field(None, description='URL to the generated image.')
|
url: Optional[str] = Field(None, description='URL to the generated image.')
|
||||||
|
|
||||||
|
|
||||||
|
class IdeogramGenerateResponse(BaseModel):
|
||||||
|
created: Optional[datetime] = Field(
|
||||||
|
None, description='Timestamp when the generation was created.'
|
||||||
|
)
|
||||||
|
data: Optional[List[Datum]] = Field(
|
||||||
|
None, description='Array of generated image information.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Code(Enum):
|
class Code(Enum):
|
||||||
int_1100 = 1100
|
int_1100 = 1100
|
||||||
int_1101 = 1101
|
int_1101 = 1101
|
||||||
@ -106,135 +101,14 @@ class Code1(Enum):
|
|||||||
int_1004 = 1004
|
int_1004 = 1004
|
||||||
|
|
||||||
|
|
||||||
class AspectRatio(str, Enum):
|
class KlingErrorResponse(BaseModel):
|
||||||
field_16_9 = '16:9'
|
code: int = Field(
|
||||||
field_9_16 = '9:16'
|
..., description='Error code value as defined in the API documentation'
|
||||||
field_1_1 = '1:1'
|
)
|
||||||
|
message: str = Field(..., description='Human-readable error message')
|
||||||
|
request_id: str = Field(
|
||||||
class Config(BaseModel):
|
..., description='Request ID for tracking and troubleshooting'
|
||||||
horizontal: Optional[confloat(ge=-10.0, le=10.0)] = None
|
)
|
||||||
pan: Optional[confloat(ge=-10.0, le=10.0)] = None
|
|
||||||
roll: Optional[confloat(ge=-10.0, le=10.0)] = None
|
|
||||||
tilt: Optional[confloat(ge=-10.0, le=10.0)] = None
|
|
||||||
vertical: Optional[confloat(ge=-10.0, le=10.0)] = None
|
|
||||||
zoom: Optional[confloat(ge=-10.0, le=10.0)] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Type(str, Enum):
|
|
||||||
simple = 'simple'
|
|
||||||
down_back = 'down_back'
|
|
||||||
forward_up = 'forward_up'
|
|
||||||
right_turn_forward = 'right_turn_forward'
|
|
||||||
left_turn_forward = 'left_turn_forward'
|
|
||||||
|
|
||||||
|
|
||||||
class CameraControl(BaseModel):
|
|
||||||
config: Optional[Config] = None
|
|
||||||
type: Optional[Type] = Field(None, description='Predefined camera movements type')
|
|
||||||
|
|
||||||
|
|
||||||
class Duration(str, Enum):
|
|
||||||
field_5 = 5
|
|
||||||
field_10 = 10
|
|
||||||
|
|
||||||
|
|
||||||
class Mode(str, Enum):
|
|
||||||
std = 'std'
|
|
||||||
pro = 'pro'
|
|
||||||
|
|
||||||
|
|
||||||
class TaskInfo(BaseModel):
|
|
||||||
external_task_id: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Video(BaseModel):
|
|
||||||
duration: Optional[str] = Field(None, description='Total video duration')
|
|
||||||
id: Optional[str] = Field(None, description='Generated video ID')
|
|
||||||
url: Optional[AnyUrl] = Field(None, description='URL for generated video')
|
|
||||||
|
|
||||||
|
|
||||||
class TaskResult(BaseModel):
|
|
||||||
videos: Optional[List[Video]] = None
|
|
||||||
|
|
||||||
|
|
||||||
class TaskStatus(str, Enum):
|
|
||||||
submitted = 'submitted'
|
|
||||||
processing = 'processing'
|
|
||||||
succeed = 'succeed'
|
|
||||||
failed = 'failed'
|
|
||||||
|
|
||||||
|
|
||||||
class Data(BaseModel):
|
|
||||||
created_at: Optional[int] = Field(None, description='Task creation time')
|
|
||||||
task_id: Optional[str] = Field(None, description='Task ID')
|
|
||||||
task_info: Optional[TaskInfo] = None
|
|
||||||
task_result: Optional[TaskResult] = None
|
|
||||||
task_status: Optional[TaskStatus] = None
|
|
||||||
updated_at: Optional[int] = Field(None, description='Task update time')
|
|
||||||
|
|
||||||
|
|
||||||
class AspectRatio1(str, Enum):
|
|
||||||
field_16_9 = '16:9'
|
|
||||||
field_9_16 = '9:16'
|
|
||||||
field_1_1 = '1:1'
|
|
||||||
field_4_3 = '4:3'
|
|
||||||
field_3_4 = '3:4'
|
|
||||||
field_3_2 = '3:2'
|
|
||||||
field_2_3 = '2:3'
|
|
||||||
field_21_9 = '21:9'
|
|
||||||
|
|
||||||
|
|
||||||
class ImageReference(str, Enum):
|
|
||||||
subject = 'subject'
|
|
||||||
face = 'face'
|
|
||||||
|
|
||||||
|
|
||||||
class Image(BaseModel):
|
|
||||||
index: Optional[int] = Field(None, description='Image Number (0-9)')
|
|
||||||
url: Optional[AnyUrl] = Field(None, description='URL for generated image')
|
|
||||||
|
|
||||||
|
|
||||||
class TaskResult1(BaseModel):
|
|
||||||
images: Optional[List[Image]] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Data1(BaseModel):
|
|
||||||
created_at: Optional[int] = Field(None, description='Task creation time')
|
|
||||||
task_id: Optional[str] = Field(None, description='Task ID')
|
|
||||||
task_result: Optional[TaskResult1] = None
|
|
||||||
task_status: Optional[TaskStatus] = None
|
|
||||||
task_status_msg: Optional[str] = Field(None, description='Task status information')
|
|
||||||
updated_at: Optional[int] = Field(None, description='Task update time')
|
|
||||||
|
|
||||||
|
|
||||||
class AspectRatio2(str, Enum):
|
|
||||||
field_16_9 = '16:9'
|
|
||||||
field_9_16 = '9:16'
|
|
||||||
field_1_1 = '1:1'
|
|
||||||
|
|
||||||
|
|
||||||
class CameraControl1(BaseModel):
|
|
||||||
config: Optional[Config] = None
|
|
||||||
type: Optional[Type] = Field(None, description='Predefined camera movements type')
|
|
||||||
|
|
||||||
|
|
||||||
class ModelName2(str, Enum):
|
|
||||||
kling_v1 = 'kling-v1'
|
|
||||||
kling_v1_6 = 'kling-v1-6'
|
|
||||||
|
|
||||||
|
|
||||||
class TaskResult2(BaseModel):
|
|
||||||
videos: Optional[List[Video]] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Data2(BaseModel):
|
|
||||||
created_at: Optional[int] = Field(None, description='Task creation time')
|
|
||||||
task_id: Optional[str] = Field(None, description='Task ID')
|
|
||||||
task_info: Optional[TaskInfo] = None
|
|
||||||
task_result: Optional[TaskResult2] = None
|
|
||||||
task_status: Optional[TaskStatus] = None
|
|
||||||
updated_at: Optional[int] = Field(None, description='Task update time')
|
|
||||||
|
|
||||||
|
|
||||||
class Code2(Enum):
|
class Code2(Enum):
|
||||||
@ -244,43 +118,141 @@ class Code2(Enum):
|
|||||||
int_1203 = 1203
|
int_1203 = 1203
|
||||||
|
|
||||||
|
|
||||||
class ResourcePackType(str, Enum):
|
class KlingRequestError(KlingErrorResponse):
|
||||||
decreasing_total = 'decreasing_total'
|
code: Optional[Code2] = Field(
|
||||||
constant_period = 'constant_period'
|
None,
|
||||||
|
description='- 1200: Invalid request parameters\n- 1201: Invalid parameters\n- 1202: Invalid request method\n- 1203: Requested resource does not exist\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Code3(Enum):
|
||||||
|
int_5000 = 5000
|
||||||
|
int_5001 = 5001
|
||||||
|
int_5002 = 5002
|
||||||
|
|
||||||
|
|
||||||
|
class KlingServerError(KlingErrorResponse):
|
||||||
|
code: Optional[Code3] = Field(
|
||||||
|
None,
|
||||||
|
description='- 5000: Internal server error\n- 5001: Service temporarily unavailable\n- 5002: Server internal timeout\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Code4(Enum):
|
||||||
|
int_1300 = 1300
|
||||||
|
int_1301 = 1301
|
||||||
|
int_1302 = 1302
|
||||||
|
int_1303 = 1303
|
||||||
|
int_1304 = 1304
|
||||||
|
|
||||||
|
|
||||||
|
class KlingStrategyError(KlingErrorResponse):
|
||||||
|
code: Optional[Code4] = Field(
|
||||||
|
None,
|
||||||
|
description='- 1300: Trigger platform strategy\n- 1301: Trigger content security policy\n- 1302: API request too frequent\n- 1303: Concurrency/QPS exceeds limit\n- 1304: Trigger IP whitelist policy\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxBaseResponse(BaseModel):
|
||||||
|
status_code: int = Field(
|
||||||
|
...,
|
||||||
|
description='Status code. 0 indicates success, other values indicate errors.',
|
||||||
|
)
|
||||||
|
status_msg: str = Field(
|
||||||
|
..., description='Specific error details or success message.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class File(BaseModel):
|
||||||
|
bytes: Optional[int] = Field(None, description='File size in bytes')
|
||||||
|
created_at: Optional[int] = Field(
|
||||||
|
None, description='Unix timestamp when the file was created, in seconds'
|
||||||
|
)
|
||||||
|
download_url: Optional[str] = Field(
|
||||||
|
None, description='The URL to download the video'
|
||||||
|
)
|
||||||
|
file_id: Optional[int] = Field(None, description='Unique identifier for the file')
|
||||||
|
filename: Optional[str] = Field(None, description='The name of the file')
|
||||||
|
purpose: Optional[str] = Field(None, description='The purpose of using the file')
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxFileRetrieveResponse(BaseModel):
|
||||||
|
base_resp: MinimaxBaseResponse
|
||||||
|
file: File
|
||||||
|
|
||||||
|
|
||||||
class Status(str, Enum):
|
class Status(str, Enum):
|
||||||
toBeOnline = 'toBeOnline'
|
Queueing = 'Queueing'
|
||||||
online = 'online'
|
Preparing = 'Preparing'
|
||||||
expired = 'expired'
|
Processing = 'Processing'
|
||||||
runOut = 'runOut'
|
Success = 'Success'
|
||||||
|
Fail = 'Fail'
|
||||||
|
|
||||||
|
|
||||||
class ResourcePackSubscribeInfo(BaseModel):
|
class MinimaxTaskResultResponse(BaseModel):
|
||||||
effective_time: Optional[int] = Field(
|
base_resp: MinimaxBaseResponse
|
||||||
None, description='Effective time, Unix timestamp in ms'
|
file_id: Optional[str] = Field(
|
||||||
)
|
|
||||||
invalid_time: Optional[int] = Field(
|
|
||||||
None, description='Expiration time, Unix timestamp in ms'
|
|
||||||
)
|
|
||||||
purchase_time: Optional[int] = Field(
|
|
||||||
None, description='Purchase time, Unix timestamp in ms'
|
|
||||||
)
|
|
||||||
remaining_quantity: Optional[float] = Field(
|
|
||||||
None, description='Remaining quantity (updated with a 12-hour delay)'
|
|
||||||
)
|
|
||||||
resource_pack_id: Optional[str] = Field(None, description='Resource package ID')
|
|
||||||
resource_pack_name: Optional[str] = Field(None, description='Resource package name')
|
|
||||||
resource_pack_type: Optional[ResourcePackType] = Field(
|
|
||||||
None,
|
None,
|
||||||
description='Resource package type (decreasing_total=decreasing total, constant_period=constant periodicity)',
|
description='After the task status changes to Success, this field returns the file ID corresponding to the generated video.',
|
||||||
)
|
)
|
||||||
status: Optional[Status] = Field(None, description='Resource Package Status')
|
status: Status = Field(
|
||||||
total_quantity: Optional[float] = Field(None, description='Total quantity')
|
...,
|
||||||
|
description="Task status: 'Queueing' (in queue), 'Preparing' (task is preparing), 'Processing' (generating), 'Success' (task completed successfully), or 'Fail' (task failed).",
|
||||||
|
)
|
||||||
|
task_id: str = Field(..., description='The task ID being queried.')
|
||||||
|
|
||||||
class Background(str, Enum):
|
|
||||||
transparent = 'transparent'
|
class Model(str, Enum):
|
||||||
opaque = 'opaque'
|
T2V_01_Director = 'T2V-01-Director'
|
||||||
|
I2V_01_Director = 'I2V-01-Director'
|
||||||
|
S2V_01 = 'S2V-01'
|
||||||
|
I2V_01 = 'I2V-01'
|
||||||
|
I2V_01_live = 'I2V-01-live'
|
||||||
|
T2V_01 = 'T2V-01'
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectReferenceItem(BaseModel):
|
||||||
|
image: Optional[str] = Field(
|
||||||
|
None, description='URL or base64 encoding of the subject reference image.'
|
||||||
|
)
|
||||||
|
mask: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='URL or base64 encoding of the mask for the subject reference image.',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxVideoGenerationRequest(BaseModel):
|
||||||
|
callback_url: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='Optional. URL to receive real-time status updates about the video generation task.',
|
||||||
|
)
|
||||||
|
first_frame_image: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='URL or base64 encoding of the first frame image. Required when model is I2V-01, I2V-01-Director, or I2V-01-live.',
|
||||||
|
)
|
||||||
|
model: Model = Field(
|
||||||
|
...,
|
||||||
|
description='Required. ID of model. Options: T2V-01-Director, I2V-01-Director, S2V-01, I2V-01, I2V-01-live, T2V-01',
|
||||||
|
)
|
||||||
|
prompt: Optional[constr(max_length=2000)] = Field(
|
||||||
|
None,
|
||||||
|
description='Description of the video. Should be less than 2000 characters. Supports camera movement instructions in [brackets].',
|
||||||
|
)
|
||||||
|
prompt_optimizer: Optional[bool] = Field(
|
||||||
|
True,
|
||||||
|
description='If true (default), the model will automatically optimize the prompt. Set to false for more precise control.',
|
||||||
|
)
|
||||||
|
subject_reference: Optional[List[SubjectReferenceItem]] = Field(
|
||||||
|
None,
|
||||||
|
description='Only available when model is S2V-01. The model will generate a video based on the subject uploaded through this parameter.',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxVideoGenerationResponse(BaseModel):
|
||||||
|
base_resp: MinimaxBaseResponse
|
||||||
|
task_id: str = Field(
|
||||||
|
..., description='The task ID for the asynchronous video generation task.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Moderation(str, Enum):
|
class Moderation(str, Enum):
|
||||||
@ -294,12 +266,6 @@ class OutputFormat(str, Enum):
|
|||||||
jpeg = 'jpeg'
|
jpeg = 'jpeg'
|
||||||
|
|
||||||
|
|
||||||
class Quality(str, Enum):
|
|
||||||
low = 'low'
|
|
||||||
medium = 'medium'
|
|
||||||
high = 'high'
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIImageEditRequest(BaseModel):
|
class OpenAIImageEditRequest(BaseModel):
|
||||||
background: Optional[str] = Field(
|
background: Optional[str] = Field(
|
||||||
None, description='Background transparency', examples=['opaque']
|
None, description='Background transparency', examples=['opaque']
|
||||||
@ -337,7 +303,12 @@ class OpenAIImageEditRequest(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Quality1(str, Enum):
|
class Background(str, Enum):
|
||||||
|
transparent = 'transparent'
|
||||||
|
opaque = 'opaque'
|
||||||
|
|
||||||
|
|
||||||
|
class Quality(str, Enum):
|
||||||
low = 'low'
|
low = 'low'
|
||||||
medium = 'medium'
|
medium = 'medium'
|
||||||
high = 'high'
|
high = 'high'
|
||||||
@ -381,7 +352,7 @@ class OpenAIImageGenerationRequest(BaseModel):
|
|||||||
description='A text description of the desired image',
|
description='A text description of the desired image',
|
||||||
examples=['Draw a rocket in front of a blackhole in deep space'],
|
examples=['Draw a rocket in front of a blackhole in deep space'],
|
||||||
)
|
)
|
||||||
quality: Optional[Quality1] = Field(
|
quality: Optional[Quality] = Field(
|
||||||
None, description='The quality of the generated image', examples=['high']
|
None, description='The quality of the generated image', examples=['high']
|
||||||
)
|
)
|
||||||
response_format: Optional[ResponseFormat] = Field(
|
response_format: Optional[ResponseFormat] = Field(
|
||||||
@ -410,13 +381,17 @@ class Datum1(BaseModel):
|
|||||||
|
|
||||||
class OpenAIImageGenerationResponse(BaseModel):
|
class OpenAIImageGenerationResponse(BaseModel):
|
||||||
data: Optional[List[Datum1]] = None
|
data: Optional[List[Datum1]] = None
|
||||||
class User(BaseModel):
|
|
||||||
email: Optional[str] = Field(None, description='The email address for this user.')
|
|
||||||
id: Optional[str] = Field(None, description='The unique id for this user.')
|
class KlingAccountError(KlingErrorResponse):
|
||||||
isAdmin: Optional[bool] = Field(
|
code: Optional[Code] = Field(
|
||||||
None, description='Indicates if the user has admin privileges.'
|
None,
|
||||||
|
description='- 1100: Account exception\n- 1101: Account in arrears (postpaid scenario)\n- 1102: Resource pack depleted or expired (prepaid scenario)\n- 1103: Unauthorized access to requested resource\n',
|
||||||
)
|
)
|
||||||
isApproved: Optional[bool] = Field(
|
|
||||||
None, description='Indicates if the user is approved.'
|
|
||||||
|
class KlingAuthenticationError(KlingErrorResponse):
|
||||||
|
code: Optional[Code1] = Field(
|
||||||
|
None,
|
||||||
|
description='- 1000: Authentication failed\n- 1001: Authorization is empty\n- 1002: Authorization is invalid\n- 1003: Authorization is not yet valid\n- 1004: Authorization has expired\n',
|
||||||
)
|
)
|
||||||
name: Optional[str] = Field(None, description='The name for this user.')
|
|
||||||
|
|||||||
@ -99,11 +99,10 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Generic,
|
Generic,
|
||||||
Callable,
|
|
||||||
)
|
)
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import time
|
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|||||||
@ -2,21 +2,38 @@ import base64
|
|||||||
import io
|
import io
|
||||||
import math
|
import math
|
||||||
from inspect import cleandoc
|
from inspect import cleandoc
|
||||||
|
from comfy.comfy_types.node_typing import FileLocator
|
||||||
import numpy as np
|
from typing import Literal
|
||||||
import requests
|
from comfy.utils import common_upscale
|
||||||
import torch
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC, InputTypeDict
|
from comfy.comfy_types.node_typing import IO, ComfyNodeABC, InputTypeDict
|
||||||
from comfy.utils import common_upscale
|
from comfy.utils import common_upscale
|
||||||
from comfy_api_nodes.apis import (
|
from comfy_api_nodes.apis import (
|
||||||
OpenAIImageEditRequest,
|
OpenAIImageEditRequest,
|
||||||
OpenAIImageGenerationRequest,
|
OpenAIImageGenerationRequest,
|
||||||
|
OpenAIImageEditRequest,
|
||||||
OpenAIImageGenerationResponse,
|
OpenAIImageGenerationResponse,
|
||||||
|
MinimaxVideoGenerationRequest,
|
||||||
|
MinimaxVideoGenerationResponse,
|
||||||
|
MinimaxFileRetrieveResponse,
|
||||||
|
MinimaxTaskResultResponse,
|
||||||
|
IdeogramGenerateRequest,
|
||||||
|
IdeogramGenerateResponse,
|
||||||
|
ImageRequest,
|
||||||
|
Model
|
||||||
)
|
)
|
||||||
from comfy_api_nodes.apis.client import ApiEndpoint, HttpMethod, SynchronousOperation
|
from comfy_api_nodes.apis.client import ApiEndpoint, HttpMethod, SynchronousOperation, PollingOperation, EmptyRequest
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
import requests
|
||||||
|
import torch
|
||||||
|
import math
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import av
|
||||||
|
import os
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
def downscale_input(image):
|
def downscale_input(image):
|
||||||
samples = image.movedim(-1,1)
|
samples = image.movedim(-1,1)
|
||||||
@ -435,14 +452,168 @@ class OpenAIGPTImage1(ComfyNodeABC):
|
|||||||
return (img_tensor,)
|
return (img_tensor,)
|
||||||
|
|
||||||
|
|
||||||
class MinimaxVideoNode:
|
class IdeogramTextToImage(ComfyNodeABC):
|
||||||
|
"""
|
||||||
|
Generates images synchronously based on a given prompt and optional parameters.
|
||||||
|
|
||||||
|
Images links are available for a limited period of time; if you would like to keep the image, you must download it.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls) -> InputTypeDict:
|
||||||
|
"""
|
||||||
|
Return a dictionary which contains config for all input fields.
|
||||||
|
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
|
||||||
|
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
|
||||||
|
The type can be a list for selection.
|
||||||
|
|
||||||
|
Returns: `dict`:
|
||||||
|
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
|
||||||
|
- Value input_fields (`dict`): Contains input fields config:
|
||||||
|
* Key field_name (`string`): Name of a entry-point method's argument
|
||||||
|
* Value field_config (`tuple`):
|
||||||
|
+ First value is a string indicate the type of field or a list for selection.
|
||||||
|
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"prompt": (IO.STRING, {
|
||||||
|
"multiline": True,
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "Prompt for the image generation",
|
||||||
|
}),
|
||||||
|
"model": (IO.COMBO, { "options": ["V_2", "V_2_TURBO", "V_1", "V_1_TURBO"], "default": "V_2", "tooltip": "Model to use for image generation"}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"aspect_ratio": (IO.COMBO, { "options": ["ASPECT_1_1", "ASPECT_4_3", "ASPECT_3_4", "ASPECT_16_9", "ASPECT_9_16", "ASPECT_2_1", "ASPECT_1_2", "ASPECT_3_2", "ASPECT_2_3", "ASPECT_4_5", "ASPECT_5_4"], "default": "ASPECT_1_1", "tooltip": "The aspect ratio for image generation. Cannot be used with resolution"
|
||||||
|
}),
|
||||||
|
"resolution": (IO.COMBO, { "options": ["1024x1024", "1024x1792", "1792x1024"],
|
||||||
|
"default": "1024x1024",
|
||||||
|
"tooltip": "The resolution for image generation (V2 only). Cannot be used with aspect_ratio"
|
||||||
|
}),
|
||||||
|
"magic_prompt_option": (IO.COMBO, { "options": ["AUTO", "ON", "OFF"],
|
||||||
|
"default": "AUTO",
|
||||||
|
"tooltip": "Determine if MagicPrompt should be used in generation"
|
||||||
|
}),
|
||||||
|
"seed": (IO.INT, {
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 2147483647,
|
||||||
|
"step": 1,
|
||||||
|
"display": "number"
|
||||||
|
}),
|
||||||
|
"style_type": (IO.COMBO, { "options": ["NONE", "ANIME", "CINEMATIC", "CREATIVE", "DIGITAL_ART", "PHOTOGRAPHIC"],
|
||||||
|
"default": "NONE",
|
||||||
|
"tooltip": "Style type for generation (V2+ only)"
|
||||||
|
}),
|
||||||
|
"negative_prompt": (IO.STRING, {
|
||||||
|
"multiline": True,
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "Description of what to exclude from the image (V1/V2 only)"
|
||||||
|
}),
|
||||||
|
"num_images": (IO.INT, {
|
||||||
|
"default": 1,
|
||||||
|
"min": 1,
|
||||||
|
"max": 8,
|
||||||
|
"step": 1,
|
||||||
|
"display": "number"
|
||||||
|
}),
|
||||||
|
"color_palette": (IO.STRING, {
|
||||||
|
"multiline": False,
|
||||||
|
"default": "",
|
||||||
|
"tooltip": "Color palette preset name or hex colors with weights (V2/V2_TURBO only)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"hidden": {
|
||||||
|
"auth_token": "AUTH_TOKEN_COMFY_ORG"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = (IO.IMAGE,)
|
||||||
|
DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value
|
||||||
|
FUNCTION = "api_call"
|
||||||
|
API_NODE = True
|
||||||
|
CATEGORY = "Example"
|
||||||
|
|
||||||
|
def api_call(self, prompt, model, aspect_ratio=None, resolution=None,
|
||||||
|
magic_prompt_option="AUTO", seed=0, style_type="NONE",
|
||||||
|
negative_prompt="", num_images=1, color_palette="", auth_token=None):
|
||||||
|
import torch
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
|
||||||
|
operation = SynchronousOperation(
|
||||||
|
endpoint=ApiEndpoint(
|
||||||
|
path="/proxy/ideogram/generate",
|
||||||
|
method=HttpMethod.POST,
|
||||||
|
request_model=IdeogramGenerateRequest,
|
||||||
|
response_model=IdeogramGenerateResponse
|
||||||
|
),
|
||||||
|
request=IdeogramGenerateRequest(
|
||||||
|
image_request=ImageRequest(
|
||||||
|
prompt=prompt,
|
||||||
|
model=model,
|
||||||
|
num_images=num_images,
|
||||||
|
seed=seed,
|
||||||
|
aspect_ratio=aspect_ratio if aspect_ratio != "ASPECT_1_1" else None,
|
||||||
|
resolution=resolution if resolution != "1024x1024" else None,
|
||||||
|
magic_prompt_option=magic_prompt_option if magic_prompt_option != "AUTO" else None,
|
||||||
|
style_type=style_type if style_type != "NONE" else None,
|
||||||
|
negative_prompt=negative_prompt if negative_prompt else None,
|
||||||
|
color_palette=None
|
||||||
|
)
|
||||||
|
),
|
||||||
|
auth_token=auth_token
|
||||||
|
)
|
||||||
|
|
||||||
|
response = operation.execute()
|
||||||
|
|
||||||
|
if not response.data or len(response.data) == 0:
|
||||||
|
raise Exception("No images were generated in the response")
|
||||||
|
image_url = response.data[0].url
|
||||||
|
|
||||||
|
if not image_url:
|
||||||
|
raise Exception("No image URL was generated in the response")
|
||||||
|
img_response = requests.get(image_url)
|
||||||
|
if img_response.status_code != 200:
|
||||||
|
raise Exception("Failed to download the image")
|
||||||
|
|
||||||
|
img = Image.open(io.BytesIO(img_response.content))
|
||||||
|
img = img.convert("RGB") # Ensure RGB format
|
||||||
|
|
||||||
|
# Convert to numpy array, normalize to float32 between 0 and 1
|
||||||
|
img_array = np.array(img).astype(np.float32) / 255.0
|
||||||
|
|
||||||
|
# Convert to torch tensor and add batch dimension
|
||||||
|
img_tensor = torch.from_numpy(img_array)[None,]
|
||||||
|
|
||||||
|
return (img_tensor,)
|
||||||
|
|
||||||
|
"""
|
||||||
|
The node will always be re executed if any of the inputs change but
|
||||||
|
this method can be used to force the node to execute again even when the inputs don't change.
|
||||||
|
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||||
|
executed, if it is different the node will be executed again.
|
||||||
|
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||||
|
changes between executions the LoadImage node is executed again.
|
||||||
|
"""
|
||||||
|
#@classmethod
|
||||||
|
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||||
|
# return ""
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxTextToVideoNode:
|
||||||
"""
|
"""
|
||||||
Generates videos synchronously based on a prompt, and optional parameters using Minimax's API.
|
Generates videos synchronously based on a prompt, and optional parameters using Minimax's API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.output_dir = folder_paths.get_output_directory()
|
self.output_dir = folder_paths.get_output_directory()
|
||||||
self.type = "output"
|
self.type: Literal["output"] = "output"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -604,13 +775,14 @@ class MinimaxVideoNode:
|
|||||||
|
|
||||||
return {"ui": {"images": results, "animated": (True,)}}
|
return {"ui": {"images": results, "animated": (True,)}}
|
||||||
|
|
||||||
|
|
||||||
# A dictionary that contains all nodes you want to export with their names
|
# A dictionary that contains all nodes you want to export with their names
|
||||||
# NOTE: names should be globally unique
|
# NOTE: names should be globally unique
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"OpenAIDalle2": OpenAIDalle2,
|
"OpenAIDalle2": OpenAIDalle2,
|
||||||
"OpenAIDalle3": OpenAIDalle3,
|
"OpenAIDalle3": OpenAIDalle3,
|
||||||
"OpenAIGPTImage1": OpenAIGPTImage1,
|
"OpenAIGPTImage1": OpenAIGPTImage1,
|
||||||
|
"IdeogramTextToImage": IdeogramTextToImage,
|
||||||
|
"MinimaxTextToVideoNode": MinimaxTextToVideoNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||||
@ -618,4 +790,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
|||||||
"OpenAIDalle2": "OpenAI DALL·E 2",
|
"OpenAIDalle2": "OpenAI DALL·E 2",
|
||||||
"OpenAIDalle3": "OpenAI DALL·E 3",
|
"OpenAIDalle3": "OpenAI DALL·E 3",
|
||||||
"OpenAIGPTImage1": "OpenAI GPT Image 1",
|
"OpenAIGPTImage1": "OpenAI GPT Image 1",
|
||||||
|
"IdeogramTextToImage": "Ideogram Text to Image",
|
||||||
|
"MinimaxTextToVideoNode": "Minimax Text to Video",
|
||||||
}
|
}
|
||||||
|
|||||||
10
comfy_api_nodes/redocly-dev.yaml
Normal file
10
comfy_api_nodes/redocly-dev.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# This file is used to filter the Comfy Org OpenAPI spec for schemas related to API Nodes.
|
||||||
|
# This is used for development purposes to generate stubs for unreleased API endpoints.
|
||||||
|
apis:
|
||||||
|
filter:
|
||||||
|
root: openapi.yaml
|
||||||
|
decorators:
|
||||||
|
filter-in:
|
||||||
|
property: tags
|
||||||
|
value: ['API Nodes']
|
||||||
|
matchStrategy: all
|
||||||
10
comfy_api_nodes/redocly.yaml
Normal file
10
comfy_api_nodes/redocly.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# This file is used to filter the Comfy Org OpenAPI spec for schemas related to API Nodes.
|
||||||
|
|
||||||
|
apis:
|
||||||
|
filter:
|
||||||
|
root: openapi.yaml
|
||||||
|
decorators:
|
||||||
|
filter-in:
|
||||||
|
property: tags
|
||||||
|
value: ['API Nodes', 'Released']
|
||||||
|
matchStrategy: all
|
||||||
Loading…
x
Reference in New Issue
Block a user