chore: add shared skills catalog (19 skills), installers, manifest, validator
This commit is contained in:
71
skills/lab-imagegen/SKILL.md
Normal file
71
skills/lab-imagegen/SKILL.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
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": <workflow>, "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": "<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)
|
||||
|
||||
```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
|
||||
```
|
||||
Reference in New Issue
Block a user