ChatImg 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chatimg/__init__.py +5 -0
- chatimg/cli.py +9 -0
- chatimg/config.py +138 -0
- chatimg/image/__init__.py +45 -0
- chatimg/image/base.py +30 -0
- chatimg/image/cli.py +422 -0
- chatimg/image/codex.py +435 -0
- chatimg/image/helpers.py +102 -0
- chatimg/image/huggingface.py +44 -0
- chatimg/image/liblib.py +150 -0
- chatimg/image/pollinations.py +56 -0
- chatimg/image/siliconflow.py +92 -0
- chatimg/image/tongyi.py +60 -0
- chatimg/openai_oauth.py +140 -0
- chatimg-0.1.0.dist-info/METADATA +171 -0
- chatimg-0.1.0.dist-info/RECORD +20 -0
- chatimg-0.1.0.dist-info/WHEEL +5 -0
- chatimg-0.1.0.dist-info/entry_points.txt +5 -0
- chatimg-0.1.0.dist-info/licenses/LICENSE +21 -0
- chatimg-0.1.0.dist-info/top_level.txt +1 -0
chatimg/__init__.py
ADDED
chatimg/cli.py
ADDED
chatimg/config.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Typed environment configuration for ChatImg."""
|
|
2
|
+
|
|
3
|
+
from chatenv import BaseEnvConfig, EnvField
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChatImgConfig(BaseEnvConfig):
|
|
7
|
+
"""ChatImg ChatEnv configuration for image generation providers."""
|
|
8
|
+
|
|
9
|
+
_title = "ChatImg Configuration"
|
|
10
|
+
_aliases = ["chatimg", "image"]
|
|
11
|
+
_storage_dir = "ChatImg"
|
|
12
|
+
|
|
13
|
+
DASHSCOPE_API_KEY = EnvField(
|
|
14
|
+
"DASHSCOPE_API_KEY",
|
|
15
|
+
desc="Aliyun DashScope API key for Tongyi Wanxiang.",
|
|
16
|
+
is_sensitive=True,
|
|
17
|
+
)
|
|
18
|
+
HUGGINGFACE_HUB_TOKEN = EnvField(
|
|
19
|
+
"HUGGINGFACE_HUB_TOKEN",
|
|
20
|
+
desc="Hugging Face User Access Token.",
|
|
21
|
+
is_sensitive=True,
|
|
22
|
+
)
|
|
23
|
+
LIBLIB_MODEL_ID = EnvField(
|
|
24
|
+
"LIBLIB_MODEL_ID",
|
|
25
|
+
desc="Default LiblibAI model ID.",
|
|
26
|
+
)
|
|
27
|
+
LIBLIB_ACCESS_KEY = EnvField(
|
|
28
|
+
"LIBLIB_ACCESS_KEY",
|
|
29
|
+
desc="LiblibAI access key.",
|
|
30
|
+
is_sensitive=True,
|
|
31
|
+
)
|
|
32
|
+
LIBLIB_SECRET_KEY = EnvField(
|
|
33
|
+
"LIBLIB_SECRET_KEY",
|
|
34
|
+
desc="LiblibAI secret key.",
|
|
35
|
+
is_sensitive=True,
|
|
36
|
+
)
|
|
37
|
+
POLLINATIONS_API_KEY = EnvField(
|
|
38
|
+
"POLLINATIONS_API_KEY",
|
|
39
|
+
desc="Pollinations API key from enter.pollinations.ai.",
|
|
40
|
+
is_sensitive=True,
|
|
41
|
+
)
|
|
42
|
+
POLLINATIONS_MODEL_ID = EnvField(
|
|
43
|
+
"POLLINATIONS_MODEL_ID",
|
|
44
|
+
default="flux",
|
|
45
|
+
desc="Default Pollinations image model ID.",
|
|
46
|
+
)
|
|
47
|
+
SILICONFLOW_API_KEY = EnvField(
|
|
48
|
+
"SILICONFLOW_API_KEY",
|
|
49
|
+
desc="SiliconFlow API key.",
|
|
50
|
+
is_sensitive=True,
|
|
51
|
+
)
|
|
52
|
+
SILICONFLOW_MODEL_ID = EnvField(
|
|
53
|
+
"SILICONFLOW_MODEL_ID",
|
|
54
|
+
default="black-forest-labs/FLUX.1-schnell",
|
|
55
|
+
desc="Default SiliconFlow image model ID.",
|
|
56
|
+
)
|
|
57
|
+
OPENAI_ACCESS_TOKEN = EnvField(
|
|
58
|
+
"OPENAI_ACCESS_TOKEN",
|
|
59
|
+
desc="OpenAI/ChatGPT OAuth access token for Codex image generation.",
|
|
60
|
+
is_sensitive=True,
|
|
61
|
+
)
|
|
62
|
+
OPENAI_CODEX_ACCESS_TOKEN = EnvField(
|
|
63
|
+
"OPENAI_CODEX_ACCESS_TOKEN",
|
|
64
|
+
desc="Legacy Codex image OAuth access token alias.",
|
|
65
|
+
is_sensitive=True,
|
|
66
|
+
)
|
|
67
|
+
OPENAI_REFRESH_TOKEN = EnvField(
|
|
68
|
+
"OPENAI_REFRESH_TOKEN",
|
|
69
|
+
desc="OpenAI/ChatGPT OAuth refresh token for Codex image generation.",
|
|
70
|
+
is_sensitive=True,
|
|
71
|
+
)
|
|
72
|
+
OPENAI_CODEX_AUTH_JSON = EnvField(
|
|
73
|
+
"OPENAI_CODEX_AUTH_JSON",
|
|
74
|
+
default="~/.hermes/auth.json",
|
|
75
|
+
desc="Hermes auth.json path used as a Codex OAuth token fallback.",
|
|
76
|
+
)
|
|
77
|
+
OPENAI_OAUTH_BASE_URL = EnvField(
|
|
78
|
+
"OPENAI_OAUTH_BASE_URL",
|
|
79
|
+
default="https://auth.openai.com",
|
|
80
|
+
desc="OpenAI OAuth auth server base URL.",
|
|
81
|
+
)
|
|
82
|
+
OPENAI_ACCESS_TOKEN_EXPIRES_AT = EnvField(
|
|
83
|
+
"OPENAI_ACCESS_TOKEN_EXPIRES_AT",
|
|
84
|
+
desc="UTC ISO timestamp for OPENAI_ACCESS_TOKEN expiry.",
|
|
85
|
+
)
|
|
86
|
+
OPENAI_IMAGE_MODEL = EnvField(
|
|
87
|
+
"OPENAI_IMAGE_MODEL",
|
|
88
|
+
default="gpt-image-2-medium",
|
|
89
|
+
desc="Default Codex/OpenAI image model preset.",
|
|
90
|
+
)
|
|
91
|
+
OPENAI_IMAGE_ASPECT_RATIO = EnvField(
|
|
92
|
+
"OPENAI_IMAGE_ASPECT_RATIO",
|
|
93
|
+
default="square",
|
|
94
|
+
desc="Default Codex image aspect ratio.",
|
|
95
|
+
)
|
|
96
|
+
OPENAI_CODEX_HOST_MODEL = EnvField(
|
|
97
|
+
"OPENAI_CODEX_HOST_MODEL",
|
|
98
|
+
default="gpt-5.4",
|
|
99
|
+
desc="Codex host model used to invoke the image_generation tool.",
|
|
100
|
+
)
|
|
101
|
+
OPENAI_CODEX_BASE_URL = EnvField(
|
|
102
|
+
"OPENAI_CODEX_BASE_URL",
|
|
103
|
+
default="https://chatgpt.com/backend-api/codex",
|
|
104
|
+
desc="Codex Responses API base URL.",
|
|
105
|
+
)
|
|
106
|
+
OPENAI_CODEX_TIMEOUT = EnvField(
|
|
107
|
+
"OPENAI_CODEX_TIMEOUT",
|
|
108
|
+
default="300",
|
|
109
|
+
desc="Codex image request timeout in seconds.",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def test(cls) -> None:
|
|
114
|
+
"""Validate schema registration without external side effects."""
|
|
115
|
+
|
|
116
|
+
print(f"Testing {cls._title}...")
|
|
117
|
+
print("Schema loaded; provider network checks are command-specific.")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Backward-compatible aliases for code migrated from ChatTool provider modules.
|
|
121
|
+
ChatimgConfig = ChatImgConfig
|
|
122
|
+
TongyiConfig = ChatImgConfig
|
|
123
|
+
HuggingFaceConfig = ChatImgConfig
|
|
124
|
+
LiblibConfig = ChatImgConfig
|
|
125
|
+
PollinationsConfig = ChatImgConfig
|
|
126
|
+
SiliconFlowConfig = ChatImgConfig
|
|
127
|
+
OpenAIConfig = ChatImgConfig
|
|
128
|
+
|
|
129
|
+
__all__ = [
|
|
130
|
+
"ChatImgConfig",
|
|
131
|
+
"ChatimgConfig",
|
|
132
|
+
"TongyiConfig",
|
|
133
|
+
"HuggingFaceConfig",
|
|
134
|
+
"LiblibConfig",
|
|
135
|
+
"PollinationsConfig",
|
|
136
|
+
"SiliconFlowConfig",
|
|
137
|
+
"OpenAIConfig",
|
|
138
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from .base import ImageGenerator
|
|
2
|
+
from .tongyi import TongyiImageGenerator
|
|
3
|
+
from .huggingface import HuggingFaceImageGenerator
|
|
4
|
+
from .liblib import LiblibImageGenerator
|
|
5
|
+
from .pollinations import PollinationsImageGenerator
|
|
6
|
+
from .siliconflow import SiliconFlowImageGenerator
|
|
7
|
+
from .codex import CodexImageGenerator
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ImageGenerator",
|
|
11
|
+
"TongyiImageGenerator",
|
|
12
|
+
"HuggingFaceImageGenerator",
|
|
13
|
+
"LiblibImageGenerator",
|
|
14
|
+
"PollinationsImageGenerator",
|
|
15
|
+
"SiliconFlowImageGenerator",
|
|
16
|
+
"CodexImageGenerator",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
def create_generator(provider: str, **kwargs) -> ImageGenerator:
|
|
20
|
+
"""
|
|
21
|
+
Factory function to create an image generator instance.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
provider (str): 'tongyi', 'huggingface', 'liblib', 'pollinations',
|
|
25
|
+
'siliconflow', 'codex', or 'openai-codex'
|
|
26
|
+
**kwargs: Additional config (api_key, etc.)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
ImageGenerator: The initialized generator.
|
|
30
|
+
"""
|
|
31
|
+
if provider == "tongyi":
|
|
32
|
+
return TongyiImageGenerator(**kwargs)
|
|
33
|
+
elif provider == "huggingface":
|
|
34
|
+
return HuggingFaceImageGenerator(**kwargs)
|
|
35
|
+
elif provider == "liblib":
|
|
36
|
+
return LiblibImageGenerator(**kwargs)
|
|
37
|
+
elif provider == "pollinations":
|
|
38
|
+
return PollinationsImageGenerator(**kwargs)
|
|
39
|
+
elif provider == "siliconflow":
|
|
40
|
+
return SiliconFlowImageGenerator(**kwargs)
|
|
41
|
+
elif provider in {"codex", "openai-codex"}:
|
|
42
|
+
return CodexImageGenerator(**kwargs)
|
|
43
|
+
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f"Unknown provider: {provider}")
|
chatimg/image/base.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
|
|
4
|
+
class ImageGenerator(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Abstract base class for all image generation tools.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def generate(self, prompt: str, **kwargs) -> Any:
|
|
11
|
+
"""
|
|
12
|
+
Generate an image from the given prompt.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
prompt (str): The text description for the image.
|
|
16
|
+
**kwargs: Additional parameters specific to the implementation (e.g. style, size).
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Any: The result object (e.g. image URL, bytes, or file path).
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def get_models(self) -> list:
|
|
24
|
+
"""
|
|
25
|
+
Get available models for image generation.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
list: A list of available model IDs or objects.
|
|
29
|
+
"""
|
|
30
|
+
return []
|
chatimg/image/cli.py
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
from chatimg import __version__
|
|
2
|
+
import click
|
|
3
|
+
from chatstyle import (
|
|
4
|
+
CommandField,
|
|
5
|
+
CommandSchema,
|
|
6
|
+
add_interactive_option,
|
|
7
|
+
resolve_command_inputs,
|
|
8
|
+
)
|
|
9
|
+
from chatimg.image import create_generator
|
|
10
|
+
from chatimg.image.helpers import (
|
|
11
|
+
download_binary,
|
|
12
|
+
echo_model_list,
|
|
13
|
+
resolve_generated_output_path,
|
|
14
|
+
save_binary,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
PROMPT_SCHEMA = CommandSchema(
|
|
19
|
+
name="image-prompt",
|
|
20
|
+
fields=(CommandField("prompt", prompt="prompt", required=True),),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
HF_GENERATE_SCHEMA = CommandSchema(
|
|
25
|
+
name="image-huggingface-generate",
|
|
26
|
+
fields=(
|
|
27
|
+
CommandField("prompt", prompt="prompt", required=True),
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@click.group()
|
|
33
|
+
@click.version_option(__version__, prog_name="chatimg")
|
|
34
|
+
def main() -> None:
|
|
35
|
+
"""ChatImg image generation tools."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@main.group()
|
|
40
|
+
def liblib():
|
|
41
|
+
"""LiblibAI tools."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@liblib.command(name="generate")
|
|
46
|
+
@click.argument("prompt", required=False)
|
|
47
|
+
@click.option("--model-id", help="Model ID for generation (required).")
|
|
48
|
+
@click.option("--output", "-o", help="Optional output file path to download the image.")
|
|
49
|
+
@add_interactive_option
|
|
50
|
+
def liblib_generate(prompt, model_id, output, interactive):
|
|
51
|
+
"""Generate an image using LiblibAI."""
|
|
52
|
+
inputs = resolve_command_inputs(
|
|
53
|
+
schema=PROMPT_SCHEMA,
|
|
54
|
+
provided={"prompt": prompt},
|
|
55
|
+
interactive=interactive,
|
|
56
|
+
usage="Usage: chatimg liblib generate [PROMPT] [-i|-I]",
|
|
57
|
+
)
|
|
58
|
+
prompt = inputs["prompt"]
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
generator = create_generator("liblib")
|
|
62
|
+
click.echo("Generating image with LiblibAI...")
|
|
63
|
+
|
|
64
|
+
kwargs = {}
|
|
65
|
+
if model_id:
|
|
66
|
+
kwargs["model_id"] = model_id
|
|
67
|
+
|
|
68
|
+
result = generator.generate(prompt, **kwargs)
|
|
69
|
+
click.echo(f"Image URL: {result}")
|
|
70
|
+
|
|
71
|
+
if output and result.startswith("http"):
|
|
72
|
+
download_binary(result, output)
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise click.ClickException(str(e)) from e
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@liblib.command(name="list-models")
|
|
79
|
+
def liblib_list_models():
|
|
80
|
+
"""List available models for LiblibAI."""
|
|
81
|
+
try:
|
|
82
|
+
generator = create_generator("liblib")
|
|
83
|
+
models = generator.get_models()
|
|
84
|
+
click.echo("Available models for LiblibAI:")
|
|
85
|
+
for model in models:
|
|
86
|
+
click.echo(
|
|
87
|
+
f"- {model.get('name', 'Unknown')} (ID: {model.get('id', 'Unknown')})"
|
|
88
|
+
)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
raise click.ClickException(str(e)) from e
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@main.group()
|
|
94
|
+
def huggingface():
|
|
95
|
+
"""Hugging Face tools."""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@huggingface.command(name="generate")
|
|
100
|
+
@click.argument("prompt", required=False)
|
|
101
|
+
@click.option(
|
|
102
|
+
"--output",
|
|
103
|
+
"-o",
|
|
104
|
+
required=False,
|
|
105
|
+
help="Optional output file path. Defaults to ./generated/image_huggingface_<model>_<timestamp>.png for bytes results.",
|
|
106
|
+
)
|
|
107
|
+
@add_interactive_option
|
|
108
|
+
def huggingface_generate(prompt, output, interactive):
|
|
109
|
+
"""Generate an image using Hugging Face."""
|
|
110
|
+
inputs = resolve_command_inputs(
|
|
111
|
+
schema=HF_GENERATE_SCHEMA,
|
|
112
|
+
provided={"prompt": prompt},
|
|
113
|
+
interactive=interactive,
|
|
114
|
+
usage="Usage: chatimg huggingface generate [PROMPT] [-o PATH] [-i|-I]",
|
|
115
|
+
)
|
|
116
|
+
prompt = inputs["prompt"]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
from chatimg.image.huggingface import HuggingFaceImageGenerator
|
|
120
|
+
|
|
121
|
+
generator = create_generator("huggingface")
|
|
122
|
+
click.echo("Generating image with Hugging Face...")
|
|
123
|
+
result = generator.generate(prompt)
|
|
124
|
+
|
|
125
|
+
if isinstance(result, bytes):
|
|
126
|
+
output_path = resolve_generated_output_path(
|
|
127
|
+
output,
|
|
128
|
+
provider="huggingface",
|
|
129
|
+
model=HuggingFaceImageGenerator.DEFAULT_MODEL,
|
|
130
|
+
)
|
|
131
|
+
save_binary(result, output_path)
|
|
132
|
+
click.echo(f"Image saved to {output_path}")
|
|
133
|
+
else:
|
|
134
|
+
click.echo(f"Result: {result}")
|
|
135
|
+
except Exception as e:
|
|
136
|
+
raise click.ClickException(str(e)) from e
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@main.group()
|
|
140
|
+
def tongyi():
|
|
141
|
+
"""Tongyi Wanxiang tools."""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@tongyi.command(name="generate")
|
|
146
|
+
@click.argument("prompt", required=False)
|
|
147
|
+
@click.option("--style", default="<auto>", help="Image style.")
|
|
148
|
+
@click.option("--size", default="1024*1024", help="Image size.")
|
|
149
|
+
@click.option("--output", "-o", help="Optional output file path to download the image.")
|
|
150
|
+
@add_interactive_option
|
|
151
|
+
def tongyi_generate(prompt, style, size, output, interactive):
|
|
152
|
+
"""Generate an image using Tongyi Wanxiang."""
|
|
153
|
+
inputs = resolve_command_inputs(
|
|
154
|
+
schema=PROMPT_SCHEMA,
|
|
155
|
+
provided={"prompt": prompt},
|
|
156
|
+
interactive=interactive,
|
|
157
|
+
usage="Usage: chatimg tongyi generate [PROMPT] [-i|-I]",
|
|
158
|
+
)
|
|
159
|
+
prompt = inputs["prompt"]
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
generator = create_generator("tongyi")
|
|
163
|
+
click.echo("Generating image with Tongyi Wanxiang...")
|
|
164
|
+
result = generator.generate(prompt, style=style, size=size)
|
|
165
|
+
|
|
166
|
+
# Tongyi returns a list of dicts with 'url'
|
|
167
|
+
if isinstance(result, list):
|
|
168
|
+
for i, item in enumerate(result):
|
|
169
|
+
url = item.get("url")
|
|
170
|
+
click.echo(f"Image {i + 1}: {url}")
|
|
171
|
+
if output and url:
|
|
172
|
+
download_binary(url, output)
|
|
173
|
+
else:
|
|
174
|
+
click.echo(f"Result: {result}")
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise click.ClickException(str(e)) from e
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@main.group()
|
|
181
|
+
def pollinations():
|
|
182
|
+
"""Pollinations.ai tools."""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pollinations.command(name="generate")
|
|
187
|
+
@click.argument("prompt", required=False)
|
|
188
|
+
@click.option("--model", help="Model name (flux, turbo, etc).")
|
|
189
|
+
@click.option("--width", default=1024, help="Image width.")
|
|
190
|
+
@click.option("--height", default=1024, help="Image height.")
|
|
191
|
+
@click.option("--output", "-o", help="Optional output file path to download the image.")
|
|
192
|
+
@add_interactive_option
|
|
193
|
+
def pollinations_generate(prompt, model, width, height, output, interactive):
|
|
194
|
+
"""Generate an image using Pollinations.ai."""
|
|
195
|
+
inputs = resolve_command_inputs(
|
|
196
|
+
schema=PROMPT_SCHEMA,
|
|
197
|
+
provided={"prompt": prompt},
|
|
198
|
+
interactive=interactive,
|
|
199
|
+
usage="Usage: chatimg pollinations generate [PROMPT] [-i|-I]",
|
|
200
|
+
)
|
|
201
|
+
prompt = inputs["prompt"]
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
from chatimg.config import PollinationsConfig
|
|
205
|
+
from chatimg.image import create_generator
|
|
206
|
+
|
|
207
|
+
model = model or PollinationsConfig.POLLINATIONS_MODEL_ID.value or "flux"
|
|
208
|
+
generator = create_generator("pollinations", model=model)
|
|
209
|
+
click.echo(f"Generating image with Pollinations.ai (model: {model})...")
|
|
210
|
+
result = generator.generate(prompt, width=width, height=height)
|
|
211
|
+
|
|
212
|
+
for i, url in enumerate(result):
|
|
213
|
+
click.echo(f"Image URL: {url}")
|
|
214
|
+
|
|
215
|
+
if output:
|
|
216
|
+
click.echo(f"Downloading to {output}...")
|
|
217
|
+
headers = {
|
|
218
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
219
|
+
}
|
|
220
|
+
if PollinationsConfig.POLLINATIONS_API_KEY.value:
|
|
221
|
+
headers["Authorization"] = (
|
|
222
|
+
f"Bearer {PollinationsConfig.POLLINATIONS_API_KEY.value}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
download_binary(url, output, headers=headers)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise click.ClickException(str(e)) from e
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@pollinations.command(name="list-models")
|
|
232
|
+
def pollinations_list_models():
|
|
233
|
+
"""List available image models for Pollinations.ai."""
|
|
234
|
+
try:
|
|
235
|
+
from chatimg.image import create_generator
|
|
236
|
+
|
|
237
|
+
generator = create_generator("pollinations")
|
|
238
|
+
models = generator.get_models()
|
|
239
|
+
echo_model_list(models, "Available image models for Pollinations.ai:")
|
|
240
|
+
except Exception as e:
|
|
241
|
+
raise click.ClickException(str(e)) from e
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@main.group()
|
|
245
|
+
def siliconflow():
|
|
246
|
+
"""SiliconFlow tools."""
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@main.group()
|
|
251
|
+
def codex():
|
|
252
|
+
"""ChatGPT/Codex OAuth image tools."""
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@siliconflow.command(name="generate")
|
|
257
|
+
@click.argument("prompt", required=False)
|
|
258
|
+
@click.option("--model", help="Model name.")
|
|
259
|
+
@click.option("--size", default="1024x1024", help="Image size (e.g., 1024x1024).")
|
|
260
|
+
@click.option("--output", "-o", help="Optional output file path to download the image.")
|
|
261
|
+
@add_interactive_option
|
|
262
|
+
def siliconflow_generate(prompt, model, size, output, interactive):
|
|
263
|
+
"""Generate an image using SiliconFlow API."""
|
|
264
|
+
inputs = resolve_command_inputs(
|
|
265
|
+
schema=PROMPT_SCHEMA,
|
|
266
|
+
provided={"prompt": prompt},
|
|
267
|
+
interactive=interactive,
|
|
268
|
+
usage="Usage: chatimg siliconflow generate [PROMPT] [-i|-I]",
|
|
269
|
+
)
|
|
270
|
+
prompt = inputs["prompt"]
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
from chatimg.config import SiliconFlowConfig
|
|
274
|
+
from chatimg.image import create_generator
|
|
275
|
+
|
|
276
|
+
# Determine model
|
|
277
|
+
model = (
|
|
278
|
+
model
|
|
279
|
+
or SiliconFlowConfig.SILICONFLOW_MODEL_ID.value
|
|
280
|
+
or "black-forest-labs/FLUX.1-schnell"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
generator = create_generator("siliconflow")
|
|
284
|
+
click.echo(f"Generating image with SiliconFlow (model: {model})...")
|
|
285
|
+
|
|
286
|
+
result = generator.generate(prompt, model=model, size=size)
|
|
287
|
+
|
|
288
|
+
for i, url in enumerate(result):
|
|
289
|
+
click.echo(f"Image URL: {url}")
|
|
290
|
+
|
|
291
|
+
if output:
|
|
292
|
+
click.echo(f"Downloading to {output}...")
|
|
293
|
+
download_binary(url, output)
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
raise click.ClickException(str(e)) from e
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@siliconflow.command(name="list-models")
|
|
300
|
+
def siliconflow_list_models():
|
|
301
|
+
"""List available image models for SiliconFlow."""
|
|
302
|
+
try:
|
|
303
|
+
from chatimg.image import create_generator
|
|
304
|
+
|
|
305
|
+
generator = create_generator("siliconflow")
|
|
306
|
+
models = generator.get_models()
|
|
307
|
+
|
|
308
|
+
# Filter for text-to-image models only if possible
|
|
309
|
+
image_models = []
|
|
310
|
+
for m in models:
|
|
311
|
+
# Check type or sub_type
|
|
312
|
+
is_image = m.get("type") == "image" or m.get("sub_type") == "text-to-image"
|
|
313
|
+
if is_image:
|
|
314
|
+
image_models.append(m)
|
|
315
|
+
|
|
316
|
+
if not image_models:
|
|
317
|
+
image_models = models
|
|
318
|
+
|
|
319
|
+
click.echo("Available image models for SiliconFlow:")
|
|
320
|
+
for model in image_models:
|
|
321
|
+
model_id = model.get("id")
|
|
322
|
+
# Check if it's a free model (no Pro/ prefix)
|
|
323
|
+
is_free = not model_id.startswith("Pro/")
|
|
324
|
+
price_info = "FREE" if is_free else "PAID"
|
|
325
|
+
|
|
326
|
+
click.echo(f"- {model_id} [{price_info}]")
|
|
327
|
+
|
|
328
|
+
except Exception as e:
|
|
329
|
+
raise click.ClickException(str(e)) from e
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@codex.command(name="generate")
|
|
333
|
+
@click.argument("prompt", required=False)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--aspect-ratio",
|
|
336
|
+
type=click.Choice(["square", "landscape", "portrait"]),
|
|
337
|
+
help="Image aspect ratio.",
|
|
338
|
+
)
|
|
339
|
+
@click.option(
|
|
340
|
+
"--image-model",
|
|
341
|
+
type=click.Choice(
|
|
342
|
+
["gpt-image-2-low", "gpt-image-2-medium", "gpt-image-2-high"]
|
|
343
|
+
),
|
|
344
|
+
help="Codex image model preset.",
|
|
345
|
+
)
|
|
346
|
+
@click.option(
|
|
347
|
+
"--host-model",
|
|
348
|
+
help="Codex host model used to invoke the image_generation tool.",
|
|
349
|
+
)
|
|
350
|
+
@click.option(
|
|
351
|
+
"--base-url",
|
|
352
|
+
help="Override the Codex backend base URL. The OAuth token is sent to this host.",
|
|
353
|
+
)
|
|
354
|
+
@click.option(
|
|
355
|
+
"--timeout",
|
|
356
|
+
type=float,
|
|
357
|
+
help="Request timeout in seconds.",
|
|
358
|
+
)
|
|
359
|
+
@click.option(
|
|
360
|
+
"--output",
|
|
361
|
+
"-o",
|
|
362
|
+
help="Optional output file path. Defaults to ./generated/image_codex_<model>_<timestamp>.png",
|
|
363
|
+
)
|
|
364
|
+
@add_interactive_option
|
|
365
|
+
def codex_generate(
|
|
366
|
+
prompt,
|
|
367
|
+
aspect_ratio,
|
|
368
|
+
image_model,
|
|
369
|
+
host_model,
|
|
370
|
+
base_url,
|
|
371
|
+
timeout,
|
|
372
|
+
output,
|
|
373
|
+
interactive,
|
|
374
|
+
):
|
|
375
|
+
"""Generate an image using the ChatGPT/Codex OAuth image bridge."""
|
|
376
|
+
inputs = resolve_command_inputs(
|
|
377
|
+
schema=PROMPT_SCHEMA,
|
|
378
|
+
provided={"prompt": prompt},
|
|
379
|
+
interactive=interactive,
|
|
380
|
+
usage="Usage: chatimg codex generate [PROMPT] [-i|-I]",
|
|
381
|
+
)
|
|
382
|
+
prompt = inputs["prompt"]
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
generator = create_generator(
|
|
386
|
+
"codex",
|
|
387
|
+
base_url=base_url,
|
|
388
|
+
host_model=host_model,
|
|
389
|
+
image_model=image_model,
|
|
390
|
+
aspect_ratio=aspect_ratio,
|
|
391
|
+
timeout_seconds=timeout,
|
|
392
|
+
)
|
|
393
|
+
click.echo(
|
|
394
|
+
"Generating image with Codex "
|
|
395
|
+
f"(host: {generator.host_model}, image: {generator.image_model})..."
|
|
396
|
+
)
|
|
397
|
+
result = generator.generate(prompt)
|
|
398
|
+
output_path = resolve_generated_output_path(
|
|
399
|
+
output,
|
|
400
|
+
provider="codex",
|
|
401
|
+
model=generator.image_model,
|
|
402
|
+
prompt=prompt,
|
|
403
|
+
)
|
|
404
|
+
saved = save_binary(result, output_path)
|
|
405
|
+
click.echo(f"Image saved to {saved}")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
raise click.ClickException(str(e)) from e
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@codex.command(name="list-models")
|
|
411
|
+
def codex_list_models():
|
|
412
|
+
"""List built-in Codex image model presets."""
|
|
413
|
+
try:
|
|
414
|
+
generator = create_generator("codex")
|
|
415
|
+
models = generator.get_models()
|
|
416
|
+
echo_model_list(models, "Available image models for Codex:")
|
|
417
|
+
except Exception as e:
|
|
418
|
+
raise click.ClickException(str(e)) from e
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == "__main__":
|
|
422
|
+
main()
|