From c2faecf1b51804293bed7684d01ba3f18df852a9 Mon Sep 17 00:00:00 2001 From: zlei9 Date: Sun, 29 Mar 2026 13:19:22 +0800 Subject: [PATCH] Initial commit with translated description --- SKILL.md | 66 ++++++++++++ _meta.json | 6 ++ scripts/generate_image.py | 204 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 SKILL.md create mode 100644 _meta.json create mode 100644 scripts/generate_image.py diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..bfe97fe --- /dev/null +++ b/SKILL.md @@ -0,0 +1,66 @@ +--- +name: qwen-image +description: "使用Qwen Image API生成图像。" +homepage: https://dashscope.aliyuncs.com/ +metadata: {"openclaw":{"emoji":"🎨","requires":{"bins":["uv"]},"install":[{"id":"uv-brew","kind":"brew","formula":"uv","bins":["uv"],"label":"Install uv (brew)"}]}} +--- + +# Qwen Image + +Generate high-quality images using Alibaba Cloud's Qwen Image API (通义万相). + +## Usage + +Generate an image (returns URL only): +```bash +uv run {baseDir}/scripts/generate_image.py --prompt "一副典雅庄重的对联悬挂于厅堂之中" --size "1664*928" --api-key sk-xxx +``` + +Generate and save locally: +```bash +uv run {baseDir}/scripts/generate_image.py --prompt "一副典雅庄重的对联悬挂于厅堂之中" --size "1664*928" --api-key sk-xxx +``` + +With custom model: +Support `qwen-image-max-2025-12-30` `qwen-image-plus-2026-01-09` `qwen-image-plus` +```bash +uv run {baseDir}/scripts/generate_image.py --prompt "a beautiful sunset over mountains" --model qwen-image-plus-2026-01-09 --api-key sk-xxx +``` + +## API Key +You can obtain the API key and run the image generation command in the following order. + +- Get apiKey from `models.providers.bailian.apiKey` in `~/.openclaw/openclaw.json` +- Or get from `skills."qwen-image".apiKey` in `~/.openclaw/openclaw.json` +- Or get from `DASHSCOPE_API_KEY` environment variable +- Or Get your API key from: https://dashscope.console.aliyun.com/ + +## Options +**Sizes:** +- `1664*928` (default) - 16:9 landscape +- `1024*1024` - Square format +- `720*1280` - 9:16 portrait +- `1280*720` - 16:9 landscape (smaller) + +**Additional flags:** +- `--negative-prompt "unwanted elements"` - Specify what to avoid +- `--no-prompt-extend` - Disable automatic prompt enhancement +- `--watermark` - Add watermark to generated image +- `--no-verify-ssl` - Disable SSL certificate verification (use when behind corporate proxy) + +## Workflow + +1. Execute the generate_image.py script with the user's prompt +2. Parse the script output and find the line starting with `MEDIA_URL:` +3. Extract the image URL from that line (format: `MEDIA_URL: https://...`) +4. Display the image to the user using markdown syntax: `![Generated Image](URL)` +5. Do NOT download or save the image unless the user specifically requests it + +## Notes + +- Supports both Chinese and English prompts +- By default, returns image URL directly without downloading +- The script prints `MEDIA_URL:` in the output - extract this URL and display it using markdown image syntax: `![generated image](URL)` +- Always look for the line starting with `MEDIA_URL:` in the script output and render the image for the user +- Default negative prompt helps avoid common AI artifacts +- Images are hosted on Alibaba Cloud OSS with temporary access URLs diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..ca05332 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn7dkx3sey4sf5s5336q2axad580mwhy", + "slug": "qwen-image", + "version": "1.0.0", + "publishedAt": 1770348338231 +} \ No newline at end of file diff --git a/scripts/generate_image.py b/scripts/generate_image.py new file mode 100644 index 0000000..f25eff5 --- /dev/null +++ b/scripts/generate_image.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "requests>=2.31.0", +# ] +# /// +""" +Generate images using Qwen Image API (Alibaba Cloud DashScope). + +Usage: + uv run generate_image.py --prompt "your image description" --filename "output.png" [--model qwen-image-max|qwen-image-turbo] [--size 1664*928|1024*1024|720*1280|1280*720] [--api-key KEY] +""" + +import argparse +import os +import sys +import json +import base64 +from pathlib import Path + + +def get_api_key(provided_key: str | None) -> str | None: + """Get API key from argument first, then environment.""" + if provided_key: + return provided_key + return os.environ.get("DASHSCOPE_API_KEY") + + +def main(): + parser = argparse.ArgumentParser( + description="Generate images using Qwen Image API" + ) + parser.add_argument( + "--prompt", "-p", + required=True, + help="Image description/prompt" + ) + parser.add_argument( + "--filename", "-f", + help="Output filename (optional, if not provided will only return URL)" + ) + parser.add_argument( + "--model", "-m", + choices=["qwen-image-max", "qwen-image-turbo", "qwen-image-plus-2026-01-09"], + default="qwen-image-max", + help="Model to use: qwen-image-max (default) or qwen-image-turbo" + ) + parser.add_argument( + "--size", "-s", + choices=["1664*928", "1024*1024", "720*1280", "1280*720"], + default="1664*928", + help="Output size (default: 1664*928 for 16:9 ratio)" + ) + parser.add_argument( + "--negative-prompt", "-n", + default="低分辨率,低画质,肢体畸形,手指畸形,画面过饱和,蜡像感,人脸无细节,过度光滑,画面具有AI感。构图混乱。文字模糊,扭曲。", + help="Negative prompt to avoid unwanted elements" + ) + parser.add_argument( + "--no-prompt-extend", + action="store_true", + help="Disable automatic prompt enhancement" + ) + parser.add_argument( + "--watermark", + action="store_true", + help="Add watermark to generated image" + ) + parser.add_argument( + "--api-key", "-k", + help="DashScope API key (overrides DASHSCOPE_API_KEY env var)" + ) + parser.add_argument( + "--no-verify-ssl", + action="store_true", + help="Disable SSL certificate verification (use with caution)" + ) + + args = parser.parse_args() + + # Get API key + api_key = get_api_key(args.api_key) + if not api_key: + print("Error: No API key provided.", file=sys.stderr) + print("Please either:", file=sys.stderr) + print(" 1. Provide --api-key argument", file=sys.stderr) + print(" 2. Set DASHSCOPE_API_KEY environment variable", file=sys.stderr) + sys.exit(1) + + # Import here after checking API key + import requests + + # Set up output path + + # Build request payload + payload = { + "model": args.model, + "input": { + "messages": [ + { + "role": "user", + "content": [ + { + "text": args.prompt + } + ] + } + ] + }, + "parameters": { + "negative_prompt": args.negative_prompt, + "prompt_extend": not args.no_prompt_extend, + "watermark": args.watermark, + "size": args.size + } + } + + print(f"Generating image with {args.model}...") + print(f"Size: {args.size}") + print(f"Prompt: {args.prompt}") + + try: + # Make API request + response = requests.post( + "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation", + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}" + }, + json=payload, + timeout=120, + verify=True + ) + + response.raise_for_status() + result = response.json() + + # Check for errors + if result.get("code"): + error_msg = result.get("message", "Unknown error") + print(f"API Error: {error_msg}", file=sys.stderr) + sys.exit(1) + + # Extract image URL from response + output_data = result.get("output", {}) + choices = output_data.get("choices", []) + + if not choices: + print("Error: No choices in response", file=sys.stderr) + print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}", file=sys.stderr) + sys.exit(1) + + # Get image URL from first choice + message = choices[0].get("message", {}) + content = message.get("content", []) + + if not content or not content[0].get("image"): + print("Error: No image URL in response", file=sys.stderr) + print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}", file=sys.stderr) + sys.exit(1) + + image_url = content[0]["image"] + print(f"\nImage URL: {image_url}") + + # If filename is provided, download and save the image + if args.filename: + output_path = Path(args.filename) + output_path.parent.mkdir(parents=True, exist_ok=True) + + print("Downloading image...") + img_response = requests.get(image_url, timeout=30, verify=not args.no_verify_ssl) + img_response.raise_for_status() + + # Save the image + with open(output_path, "wb") as f: + f.write(img_response.content) + + full_path = output_path.resolve() + print(f"Image saved: {full_path}") + # Clawdbot parses MEDIA tokens and will attach the file on supported providers. + print(f"MEDIA: {full_path}") + else: + # Just return the URL for Clawdbot to display + print(f"MEDIA_URL: {image_url}") + + except requests.exceptions.HTTPError as e: + print(f"HTTP Error: {e}", file=sys.stderr) + try: + error_detail = response.json() + print(f"Error details: {json.dumps(error_detail, indent=2, ensure_ascii=False)}", file=sys.stderr) + except: + print(f"Response text: {response.text}", file=sys.stderr) + sys.exit(1) + except requests.exceptions.RequestException as e: + print(f"Error making API request: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error generating image: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()