Signed-off-by: JaredforReal <w13431838023@gmail.com> Signed-off-by: Jared Wen <w13431838023@gmail.com> Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
173 lines
5.4 KiB
Python
173 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GLM-Image OpenAI-compatible chat client for text-to-image and image-to-image.
|
|
|
|
Usage:
|
|
# Text-to-image
|
|
python openai_chat_client.py --prompt "A cute cat" --output output.png
|
|
|
|
# Image-to-image (image editing)
|
|
python openai_chat_client.py --prompt "Convert to watercolor style" --image input.png --output output.png
|
|
"""
|
|
|
|
import argparse
|
|
import base64
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
|
|
def generate_image(
|
|
prompt: str,
|
|
server_url: str = "http://localhost:8091",
|
|
image_path: str | None = None,
|
|
height: int = 1024,
|
|
width: int = 1024,
|
|
steps: int = 50,
|
|
guidance_scale: float = 1.5,
|
|
seed: int | None = None,
|
|
negative_prompt: str | None = None,
|
|
) -> bytes | None:
|
|
"""Generate or edit an image using the chat completions API.
|
|
|
|
Args:
|
|
prompt: Text description or editing instruction
|
|
server_url: Server URL
|
|
image_path: Path to input image (for image-to-image editing)
|
|
height: Image height in pixels
|
|
width: Image width in pixels
|
|
steps: Number of inference steps
|
|
guidance_scale: Classifier-free guidance scale
|
|
seed: Random seed for reproducibility
|
|
negative_prompt: Negative prompt
|
|
|
|
Returns:
|
|
Image bytes or None if failed
|
|
"""
|
|
# Build message content
|
|
content: list[dict] = [{"type": "text", "text": prompt}]
|
|
|
|
if image_path:
|
|
img_path = Path(image_path)
|
|
if not img_path.exists():
|
|
print(f"Error: Image file not found: {image_path}")
|
|
return None
|
|
b64_data = base64.b64encode(img_path.read_bytes()).decode("utf-8")
|
|
suffix = img_path.suffix.lstrip(".").lower()
|
|
mime = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png", "webp": "webp"}.get(suffix, "png")
|
|
content.append(
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {"url": f"data:image/{mime};base64,{b64_data}"},
|
|
}
|
|
)
|
|
|
|
messages = [{"role": "user", "content": content}]
|
|
|
|
# Build request payload
|
|
extra_body: dict = {
|
|
"height": height,
|
|
"width": width,
|
|
"num_inference_steps": steps,
|
|
"guidance_scale": guidance_scale,
|
|
}
|
|
if seed is not None:
|
|
extra_body["seed"] = seed
|
|
if negative_prompt:
|
|
extra_body["negative_prompt"] = negative_prompt
|
|
|
|
payload = {"messages": messages, "extra_body": extra_body}
|
|
|
|
# Send request
|
|
try:
|
|
mode = "image-to-image" if image_path else "text-to-image"
|
|
print(f"Sending {mode} request to {server_url}...")
|
|
response = requests.post(
|
|
f"{server_url}/v1/chat/completions",
|
|
headers={"Content-Type": "application/json"},
|
|
json=payload,
|
|
timeout=600,
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
# Extract image from response
|
|
choices = data.get("choices", [])
|
|
for choice in choices:
|
|
choice_content = choice.get("message", {}).get("content")
|
|
if isinstance(choice_content, list):
|
|
for item in choice_content:
|
|
if isinstance(item, dict) and "image_url" in item:
|
|
img_url = item["image_url"].get("url", "")
|
|
if img_url.startswith("data:image"):
|
|
_, b64 = img_url.split(",", 1)
|
|
return base64.b64decode(b64)
|
|
|
|
print(f"Unexpected response format: {data}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return None
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="GLM-Image chat client")
|
|
parser.add_argument(
|
|
"--prompt",
|
|
"-p",
|
|
default="A beautiful sunset over the ocean with sailing boats",
|
|
help="Text prompt",
|
|
)
|
|
parser.add_argument("--output", "-o", default="glm_image_output.png", help="Output file")
|
|
parser.add_argument("--server", "-s", default="http://localhost:8091", help="Server URL")
|
|
|
|
# Image-to-image
|
|
parser.add_argument(
|
|
"--image",
|
|
"-i",
|
|
type=str,
|
|
help="Input image path (for image-to-image editing)",
|
|
)
|
|
|
|
# Generation parameters
|
|
parser.add_argument("--height", type=int, default=1024, help="Image height")
|
|
parser.add_argument("--width", type=int, default=1024, help="Image width")
|
|
parser.add_argument("--steps", type=int, default=50, help="Inference steps")
|
|
parser.add_argument("--guidance-scale", type=float, default=1.5, help="CFG guidance scale")
|
|
parser.add_argument("--seed", type=int, default=42, help="Random seed")
|
|
parser.add_argument("--negative", help="Negative prompt")
|
|
|
|
args = parser.parse_args()
|
|
|
|
mode = "image-to-image" if args.image else "text-to-image"
|
|
print(f"Mode: {mode}")
|
|
print(f"Prompt: {args.prompt}")
|
|
if args.image:
|
|
print(f"Input image: {args.image}")
|
|
|
|
image_bytes = generate_image(
|
|
prompt=args.prompt,
|
|
server_url=args.server,
|
|
image_path=args.image,
|
|
height=args.height,
|
|
width=args.width,
|
|
steps=args.steps,
|
|
guidance_scale=args.guidance_scale,
|
|
seed=args.seed,
|
|
negative_prompt=args.negative,
|
|
)
|
|
|
|
if image_bytes:
|
|
output_path = Path(args.output)
|
|
output_path.write_bytes(image_bytes)
|
|
print(f"Image saved to: {output_path}")
|
|
print(f"Size: {len(image_bytes) / 1024:.1f} KB")
|
|
else:
|
|
print("Failed to generate image")
|
|
exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|