--- name: lab-imagegen description: 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: ```json { "prompt": , "client_id": "dsh" } ``` The workflow for SDXL-turbo (4 steps): ```json { "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": "", "clip": ["4", 1] } }, "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "", "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=&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) ```js 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 ```