Files
dsh-skills/skills/lab-imagegen/SKILL.md

4.4 KiB

name, description
name description
lab-imagegen Generate images using the user's home-lab ComfyUI server (SDXL-turbo on 2x AMD Radeon Pro VII). Use when the user asks to generate, render, or create an image or picture.

Lab Image Generation (ComfyUI)

The user's home lab runs a ComfyUI server at http://192.168.31.240:8188 (server1, 2x AMD Radeon Pro VII 16GB, SDXL-turbo). Use it to generate images on demand. No authentication is required — the API is open on the LAN.

Workflow

Use run_code to generate: submit a JSON workflow, poll /history/{prompt_id}, then return the served image URL.

1. Submit the prompt

POST /prompt with Content-Type: application/json and body:

{ "prompt": <workflow>, "client_id": "dsh" }

The workflow for SDXL-turbo (4 steps):

{
  "3": { "class_type": "KSampler", "inputs": { "seed": 123, "steps": 4, "cfg": 1.0, "sampler_name": "euler", "scheduler": "normal", "denoise": 1.0, "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0] } },
  "4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sdxl_turbo.safetensors" } },
  "5": { "class_type": "EmptyLatentImage", "inputs": { "width": 512, "height": 512, "batch_size": 1 } },
  "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "<POSITIVE>", "clip": ["4", 1] } },
  "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "<NEGATIVE>", "clip": ["4", 1] } },
  "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } },
  "9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "dsh", "images": ["8", 0] } }
}

Defaults: 512x512, steps 4, cfg 1.0, euler/normal, seed random. The response contains prompt_id and node_errors. If node_errors is non-empty, the submission failed — report them.

2. Poll for completion

Poll GET http://192.168.31.240:8188/history/{prompt_id} every ~1.5s. The result entry under outputs["9"].images contains filename. Stop polling when the image array appears.

3. Return the image

The served image URL is:

http://192.168.31.240:8188/view?filename=<filename>&subfolder=&type=output

Return this URL (a well-formed markdown image link if the user wants to view it inline).

Policy

  • Timeout: poll no longer than ~120s. SDXL-turbo at 4 steps typically finishes in well under 60s. If it exceeds the budget, report a timeout rather than looping forever.
  • Prompt quality: SDXL-turbo responds best to a descriptive positive prompt and a modest negative prompt (e.g. blurry, low quality, distorted). High cfg (>1) and high steps are unnecessary for turbo.
  • Keep the model sdxl_turbo.safetensors unless the user asks otherwise — it is the verified installed checkpoint.
  • Do not write the PNG bytes to a local file unless asked; return the server URL.

Example (run_code)

const base = 'http://192.168.31.240:8188'
const wf = { "3": { "class_type": "KSampler", "inputs": { "seed": 42, "steps": 4, "cfg": 1.0, "sampler_name": "euler", "scheduler": "normal", "denoise": 1.0, "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0] } }, "4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sdxl_turbo.safetensors" } }, "5": { "class_type": "EmptyLatentImage", "inputs": { "width": 512, "height": 512, "batch_size": 1 } }, "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "a red fox in a snowy forest, photorealistic", "clip": ["4", 1] } }, "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "blurry, low quality, distorted", "clip": ["4", 1] } }, "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } }, "9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "dsh", "images": ["8", 0] } } }
const r = await (await fetch(base + '/prompt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: wf, client_id: 'dsh' }) })).json()
if (r.node_errors && Object.keys(r.node_errors).length) throw new Error('node_errors: ' + JSON.stringify(r.node_errors))
let url = null
for (let i = 0; i < 160 && !url; i++) {
  const h = await (await fetch(base + '/history/' + r.prompt_id)).json()
  const imgs = h[r.prompt_id]?.outputs?.['9']?.images
  if (imgs?.length) url = base + '/view?filename=' + encodeURIComponent(imgs[0].filename) + '&subfolder=&type=output'
  else await new Promise(s => setTimeout(s, 1500))
}
return url