作者:鸥弋、筱姜
2023年下半年,comfyui 以其快速、流畅的图像生成能力,结合多样的自定义节点,迅速在创作者中流行起来。comfyui 的亮点就是能够批量化生成图像,一键加载大量工作流,让用户可以轻松实现人像生成、背景替换、风格迁移和图像动画化等功能。越来越多的企业及个人开发者希望借助 comfyui 能力进行 ai 绘画领域创业或者业务上新,获得高流量及商业价值,但使用原生的 comfyui 仍然存在一些问题:
1.显卡资源昂贵且难以购买 :gpu 卡池管理技术门槛高:高性能的 gpu 资源不仅价格昂贵,而且往往难以大规模采购。此外,gpu 卡池的有效管理和维护需要复杂的技术支持,也带来了额外的挑战。
2.难以应对高并发 :原生的 comfyui 出图需要排队,并发处理能力有限。在面对高并发场景时,尤其是并发请求具有大的波动性时,资源配置难以精确预测,从而可能导致系统错误和业务中断。
3.门槛高,难以对外透出:comfyui 拥有一定的门槛,对于普通的创作者而言几乎无法使用,需要对其进行二次包装才能让更多用户享受到 ai 的便捷。
为了帮助用户高效率、低成本应对企业级复杂场景,以下介绍 comfyui api serverless 版解决方案,通过使用该方案,用户可以充分利用 comfyui + serverless 技术优势快速开发上线 ai 绘画应用,期待为广大开发者 ai 绘画创业及变现提供思路。 相关文章:ai 绘画平台难开发,难变现?试试 stable diffusion api serverless 版解决方案
阿里云x优酷联名发起的「creat@ai江湖创作大赛」使用本文章中的解决方案,基于函数计算fc 一键部署 ai 绘图平台,1分钟实现 "破次元壁合照"、5分钟实现 stable diffusion、comfyui 部署 ,生成以"少年江湖"为主题的画作赢万元奖金。 链接: https://developer.aliyun.com/plan/create/snbm
方案优势
在以往的活动中,我们也面临了很多非技术相关的用户期望享受 ai 的魅力。结合实际需要我们给出了 serverless 化的 comfyui 实践案例,解决了上述问题。
- 部署简单:提供基础 comfyui 镜像,不需要修改时一键即可拉起出图,需要修改时也只需要修改 comfyui 镜像地址即可
- 弹性 gpu:函数计算提供了 gpu 弹性的能力,根据实际请求控制实例个数,有突发流量时自动弹新实例承接请求,完全不需要增加额外的关注
- 按量付费:函数计算的按量实例为毫秒级粒度的计费策略,用多久就收多少钱,确保每分钱都花在刀刃上
- comfyui serverless 化改造:对原本不适应 serverless 弹性能力的 comfyui 改造,使其可以支持异步、并发、弹性等各种 serverless 能力
- 前后端联动:活动开源了一个支持自定义参数,并且并发出图的前端页面,可直接提供给客户使用
应用场景
comfyui 提供了非常高的自由度和灵活性,支持定制化工作流,并且可以重复使用,批量出图,特别适用于需要创意图像生成场景:
- 艺术创作与设计: 艺术家和设计师可以利用 comfyui 生成独特的艺术作品,包括概念艺术、插画、海报设计等。通过 comfyui,他们可以根据自己的创意想法生成初步的图像草稿,然后再进一步细化和完善。
- 内容制作与营销: 在社交媒体、广告和营销领域,comfyui 可用于快速生成符合品牌风格的视觉素材,用于社交媒体内容、广告横幅、海报等
- 游戏开发: 游戏开发者可能利用 comfyui 自动生成游戏内的景观或建筑物的纹理,减少手工制作这些元素所需的时间和成本。
- 视觉特效与影视后期: 电影和电视行业的视觉特效团队可以使用 comfyui 来辅助创建逼真的背景、特殊效果或修复旧影片中的画面缺陷。
通过 api 接口调用 comfyui 解决方案
常规的 comfyui 出图的流程大致如下
- 调用
/prompt
接口,发起出图任务 - 通过 websocket 获取出图进度
由于在 serverless 场景下,无请求的时候实例会被冻结,因此 websocket 请求是必须要存在的,且需要保持连接到出图完成。
在并发请求数比较大的情况下,我们往往期望可以利用 serverless 的弹性,动态创建多个函数实例处理出图任务。但由于 comfyui 本身是"有状态"的,难以确保出图的请求和获取状态的请求固定打到同一个实例上,这可能会导致接口的调用不符合预期。
为了让 comfyui 更加适配 serverless 模式,需要针对 comfyui 进行一定的改造。 参考 fc-comfyui/src/images/agent 的代码,在 comfyui 镜像里内置 agent 程序,负责转换 comfyui 请求并且拉起 comfyui。
目前提供的 agent 能力介绍
开启 agent 能力,需要增加环境变量
use_agent
:1
当通过 agent 的 api 调用时,建议您调整单实例并发度为 1 ~ 5,确保并发请求尽量使用单独的实例,提高出图效率
数据类型
出图 prompt
与 comfyui 在 dev mode 导出的文件一致
type tpromptnode struct {
inputs map[string]any `json:"inputs"`
classtype string `json:"class_type"`
meta map[string]any `json:"_meta"`
}
type tprompt map[string]tpromptnode
loadimage 节点的参数做了特殊处理,如果内容为 base64 或 http 地址,会自动将对应的文件上传,并转换为 comfyui 可识别的形式
进度
// key 为 node id 的 map 对象
type tprogress map[string]tprogressnode
type tprogressnode struct {
max int `json:"max"` // 进度的最大值
value int `json:"value"` // 当前进度
start int64 `json:"start"` // 开始时间
lastupdated int64 `json:"last_updated"` // 最后一次更新时间
images []tprogressnodeimage `json:"images"` // 当前节点输出的图片信息(路径)
results []string `json:"results,omitempty"` // 当前节点输出的图片 base64
}
接口
出图请求(http 同步)
路径:/api/run
body:json 格式的 prompt 数据 返回值:最后一次的进度(包含图片信息)
当需要异步请求时,需要增加 x-fc-invocation-type
和 task-id
,前者告知 fc 异步形式调用,后者用于记录当前任务的唯一 id,方便后续获取状态
curl http://xxxxx/api/run -v \
-h 'x-fc-invocation-type: async' \
-h "task-id: abcdefg" \
-xpost \
-d '{
"3": {
"inputs": {
"seed": 1586995582004891,
"steps": 17,
"cfg": 6,
"sampler_name": "dpm_2",
"scheduler": "karras",
"denoise": 1,
"model": [
"33",
0
],
"positive": [
"31",
0
],
"negative": [
"32",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "ksampler",
"_meta": {
"title": "ksampler"
}
},
"4": {
"inputs": {
"ckpt_name": "majicmix realistic_v7.safetensors"
},
"class_type": "checkpointloadersimple",
"_meta": {
"title": "load checkpoint"
}
},
"5": {
"inputs": {
"width": 1024,
"height": 784,
"batch_size": 1
},
"class_type": "emptylatentimage",
"_meta": {
"title": "empty latent image"
}
},
"6": {
"inputs": {
"text": "2 human\nhi quality,detailed",
"clip": [
"4",
1
]
},
"class_type": "cliptextencode",
"_meta": {
"title": "clip text encode (prompt)"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "vaedecode",
"_meta": {
"title": "vae decode"
}
},
"9": {
"inputs": {
"filename_prefix": "comfyui",
"images": [
"8",
0
]
},
"class_type": "saveimage",
"_meta": {
"title": "save image"
}
},
"10": {
"inputs": {
"image": "https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/default.png",
"upload": "image"
},
"class_type": "loadimage",
"_meta": {
"title": "load image"
}
},
"11": {
"inputs": {
"image": "https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/百里东君.png",
"upload": "image"
},
"class_type": "loadimage",
"_meta": {
"title": "load image",
"edit": []
}
},
"12": {
"inputs": {
"image": "https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/background.png",
"upload": "image"
},
"class_type": "loadimage",
"_meta": {
"title": "load image"
}
},
"13": {
"inputs": {
"image": "https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/mask.png",
"upload": "image"
},
"class_type": "loadimage",
"_meta": {
"title": "load image"
}
},
"15": {
"inputs": {
"threshold_r": 0.15,
"threshold_g": 0.15,
"threshold_b": 0.15,
"remove_isolated_pixels": 0,
"fill_holes": false,
"image": [
"13",
0
]
},
"class_type": "maskfromrgbcmybw+",
"_meta": {
"title": " mask from rgb/cmy/bw"
}
},
"21": {
"inputs": {
"image_weight": 0.8,
"prompt_weight": 1,
"weight_type": "linear",
"start_at": 0,
"end_at": 1,
"image": [
"10",
0
],
"mask": [
"15",
0
],
"positive": [
"24",
0
],
"negative": [
"25",
0
]
},
"class_type": "ipadapterregionalconditioning",
"_meta": {
"title": "ipadapter regional conditioning"
}
},
"22": {
"inputs": {
"image_weight": 1,
"prompt_weight": 1,
"weight_type": "linear",
"start_at": 0,
"end_at": 1,
"image": [
"11",
0
],
"mask": [
"15",
1
],
"positive": [
"26",
0
],
"negative": [
"25",
0
]
},
"class_type": "ipadapterregionalconditioning",
"_meta": {
"title": "ipadapter regional conditioning"
}
},
"23": {
"inputs": {
"image_weight": 0.7000000000000001,
"prompt_weight": 1,
"weight_type": "linear",
"start_at": 0,
"end_at": 1,
"image": [
"12",
0
],
"mask": [
"15",
6
]
},
"class_type": "ipadapterregionalconditioning",
"_meta": {
"title": "ipadapter regional conditioning"
}
},
"24": {
"inputs": {
"text": "illustration of a body with black hair, presented in high definition with intricate details",
"clip": [
"4",
1
]
},
"class_type": "cliptextencode",
"_meta": {
"title": "clip text encode (prompt)"
}
},
"25": {
"inputs": {
"text": "(worst quality:1.6),(low quality:1.6),(lowres:1.6),(nsfw:1.5),watermark,monochrome,disconnected limbs,malformed limbs,extra limb,mutated hands,fused fingers,too many fingers,extra arms,missing fingers,bad hands,bad feet,mutated hands and fingers,malformed hands,extra legs,floating limbs,missing limb,mutation,mutated,deformed,bad body,poorly drawn hands,(badhandv4),(naked),(nude),",
"clip": [
"4",
1
]
},
"class_type": "cliptextencode",
"_meta": {
"title": "clip text encode (prompt)"
}
},
"26": {
"inputs": {
"text": "anime aillustration of 1 boy with black hair, depicted in high definition showcasing rich details, in 8k resolution.",
"clip": [
"4",
1
]
},
"class_type": "cliptextencode",
"_meta": {
"title": "clip text encode (prompt)"
}
},
"28": {
"inputs": {
"params_1": [
"21",
0
],
"params_2": [
"22",
0
],
"params_3": [
"23",
0
]
},
"class_type": "ipadaptercombineparams",
"_meta": {
"title": "ipadapter combine params"
}
},
"31": {
"inputs": {
"conditioning_1": [
"21",
1
],
"conditioning_2": [
"22",
1
],
"conditioning_3": [
"6",
0
],
"conditioning_4": [
"47",
0
]
},
"class_type": "conditioningcombinemultiple+",
"_meta": {
"title": " conditionings combine multiple "
}
},
"32": {
"inputs": {
"conditioning_1": [
"47",
1
],
"conditioning_2": [
"22",
2
],
"conditioning_3": [
"25",
0
]
},
"class_type": "conditioningcombinemultiple+",
"_meta": {
"title": " conditionings combine multiple "
}
},
"33": {
"inputs": {
"combine_embeds": "concat",
"embeds_scaling": "v only",
"model": [
"4",
0
],
"ipadapter": [
"34",
1
],
"ipadapter_params": [
"28",
0
]
},
"class_type": "ipadapterfromparams",
"_meta": {
"title": "ipadapter from params"
}
},
"34": {
"inputs": {
"preset": "plus (high strength)",
"model": [
"4",
0
]
},
"class_type": "ipadapterunifiedloader",
"_meta": {
"title": "ipadapter unified loader"
}
},
"43": {
"inputs": {
"clip_name": "clip-vit-h-14-laion2b-s32b-b79k.safetensors"
},
"class_type": "clipvisionloader",
"_meta": {
"title": "load clip vision"
}
},
"45": {
"inputs": {
"ipadapter_file": "ip-adapter-plus_sd15.safetensors"
},
"class_type": "ipadaptermodelloader",
"_meta": {
"title": "ipadapter model loader"
}
},
"46": {
"inputs": {
"provider": "cpu"
},
"class_type": "ipadapterinsightfaceloader",
"_meta": {
"title": "ipadapter insightface loader"
}
},
"47": {
"inputs": {
"strength": 0.8,
"start_percent": 0,
"end_percent": 1,
"positive": [
"21",
1
],
"negative": [
"21",
2
],
"control_net": [
"48",
0
],
"image": [
"49",
0
]
},
"class_type": "controlnetapplyadvanced",
"_meta": {
"title": "apply controlnet (advanced)"
}
},
"48": {
"inputs": {
"control_net_name": "control_v11p_sd15_openpose_fp16.safetensors"
},
"class_type": "controlnetloader",
"_meta": {
"title": "load controlnet model"
}
},
"49": {
"inputs": {
"detect_hand": "enable",
"detect_body": "enable",
"detect_face": "enable",
"resolution": 512,
"image": [
"10",
0
]
},
"class_type": "openposepreprocessor",
"_meta": {
"title": "openpose pose"
}
}
}'
{"":{"max":0,"value":0,"start":0,"last_updated":1722234889,"images":null},"10":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"11":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"12":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"13":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"15":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"21":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"22":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"23":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"24":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"25":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"26":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"28":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"3":{"max":17,"value":17,"start":1722234848,"last_updated":1722234889,"images":null},"31":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"32":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"33":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"34":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"4":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"43":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"45":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"46":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"47":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"48":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"49":{"max":1,"value":1,"start":1722234846,"last_updated":1722234848,"images":null},"5":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"6":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"8":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":null},"9":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":[{"filename":"comfyui_00004_.png","subfolder":"","type":"output"}]}}
出图请求(websocket)
路径:/api/run/ws
message:
- 客户端 -> 服务端:仅发送一次,json 格式的 prompt 信息
- 服务端 -> 客户端:中间状态
获取状态
路径:/api/run/ws?id=
query 参数:
id
:task id
curl http://xxxxx/api/status?id=abcdefg -v
{"":{"max":0,"value":0,"start":0,"last_updated":1722234889,"images":null},"10":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"11":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"12":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"13":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"15":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"21":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"22":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"23":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"24":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"25":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"26":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"28":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"3":{"max":17,"value":17,"start":1722234848,"last_updated":1722234889,"images":null},"31":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"32":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"33":{"max":1,"value":0,"start":1722234844,"last_updated":1722234844,"images":null},"34":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"4":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"43":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"45":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"46":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"47":{"max":1,"value":0,"start":1722234848,"last_updated":1722234848,"images":null},"48":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"49":{"max":1,"value":1,"start":1722234846,"last_updated":1722234848,"images":null},"5":{"max":0,"value":0,"start":0,"last_updated":0,"images":null},"6":{"max":0,"value":0,"start":0,"last_updated":0,"imag* connection #0 to host photo-b-comfyui-ibiwqxodsh.cn-hangzhou.fcapp.run left intact
es":null},"8":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":null},"9":{"max":1,"value":0,"start":1722234889,"last_updated":1722234889,"images":[{"filename":"comfyui_00004_.png","subfolder":"","type":"output"}]}}
其他
原样转发至 comfyui
调用方式
同步调用
/api/run
和 /api/run/ws
都是同步接口,直接调用即可,区别在于是否需要出图进度
- 在 websocket 内部获取:只调用
/api/run/ws
- 不关心出图进度 / 起另一个线程获取进度:使用
/api/run
+/api/status
异步调用
调用 /api/run
接口,并且添加 http header,借助函数计算自带的能力,将请求转换为异步形式
- key:
x-fc-invocation-type
- value:
async
二次开发
我们提供的 agent 仅用作参考,正式使用时,请根据业务需要进行二次开发
状态存储
在 src/images/agent/pkg/store/fs.go
中,我们实现了基于文件系统的状态存储,您只需要挂载 nas 系统,确保文件可被正常持久化,既可以在多个实例之间共享状态文件,确保可以正确拿到状态信息 更好的做法是,将状态信息写入到 ots、mysql 等数据库中,您只需要仿照 fs.go
实现 stroe
接口针对其他数据库的实现即可
// store kv 数据存储
type store interface {
// save 存储 value 到 key
save(key string, value string) error
// load 从 key 加载 value
load(key string) (string, error)
}
output 节点
目前,agent 仅针对 saveimage 节点做了特殊处理,提取其中的图片信息。对于特殊的业务需要,您可能需要更加定制化的工作流处理,如
- 增加更多对于 output 的解析
- 不解析图片节点,而是借助于其他接口获取图片文件
case "execution_error", "executed":
// 节点执行结束
log.debugf("%s node %s finished", logprefix, nodeid)
// 节点已完成时,修改下 max 和 value 至少为 1
if currentnodeprogress.max == 0 && currentnodeprogress.value == 0 {
currentnodeprogress.max = 1
currentnodeprogress.value = 1
}
if promptnode.classtype == "saveimage" && msg.data.output.images != nil && len(msg.data.output.images) > 0 {
// 如果是图片节点,则记录一下图片数据
if currentnodeprogress.images == nil {
currentnodeprogress.images = make([]store.tprogressnodeimage, 0, len(msg.data.output.images))
}
for _, img := range msg.data.output.images {
currentnodeprogress.images = append(currentnodeprogress.images, store.tprogressnodeimage{
filename: img.filename,
subfolder: img.subfolder,
type: img.type,
})
}
}
前端功能集成
与 agent 对应,我们也给出了一份前端页面 devsapp/fc-comfyui-couple-photo
在这里,我们针对 comfyui 的 prompt 做了一些特殊的约定,以适应自定义需要。 以函数计算支持活动 "阿里云x优酷江湖创作大赛" 为例,我们提供了预定义的 prompt 文件
[
{
"title": "破次元壁合照",
"prompt": {},
"params": [
{
"type": "group",
"title": "step 1 - 上传您的照片",
"children": [
{
"type": "image",
"id": "10",
"key": "image",
"title": "参考图",
"description": "请上传您的照片,帮助模型理解您的样貌。请尽量选择背景简单、主体突出的半身照,不要佩戴墨镜、帽子等可能影响您特征的衣物。"
},
{
"type": "string",
"id": "24",
"key": "text",
"title": "参考形象描述",
"description": "为了确保模型更好地理解您的特点,您可以使用提示词来加强模型对您的印象(请使用因为描述)。"
}
]
},
{
"type": "image",
"id": "11",
"key": "image",
"title": "step 2 - 选择角色",
"description": "请选择您希望合照的角色。",
"options": [
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/百里东君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/司空长风.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/玥瑶.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/叶鼎之.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/易文君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/南宫春水.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/萧若风.png"
]
},
{
"type": "image",
"id": "12",
"key": "image",
"title": "step 3 - 上传背景图",
"description": "请上传您期望的合影地点的图片,这将作为背景图片的参考。"
}
]
},
{
"title": "背景替换",
"prompt": {},
"params": [
{
"type": "image",
"id": "10",
"key": "image",
"title": "step 1 - 选择角色",
"description": "请选择您希望合照的角色。",
"options": [
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/百里东君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/司空长风.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/玥瑶.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/叶鼎之.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/易文君.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/南宫春水.png",
"https://serverless-tool-images.oss-cn-hangzhou.aliyuncs.com/aigc/json/couple/萧若风.png"
]
},
{
"type": "image",
"id": "12",
"key": "image",
"title": "step 2 - 上传背景图",
"description": "请上传您期望的合影地点的图片,这将作为背景图片的参考。"
}
]
}
]
通过 params
字段,约定了如何渲染页面并允许用户填入自己的参数
export type comfyuiprompteditpanel = {
type: 'image' | 'select' | 'number' | 'string' | 'group'; // 数据类型
id?: string; // 对应 prompt 中的 node id
key: string; // 要修改的参数
title: string; // 标题
description?: string; // 描述
options?: string[] | string; // 可选项
min?: number; // 最小值
max?: number; // 最大值
step?: number; // 调整步数
hidden?: boolean; // 是否隐藏
children?: comfyuiprompteditpanel[]; // group 类型的子节点
};
一些其他约定:
- 如果
seed
字段为-1
,则会被替换为随机数
如果您也希望创建自己的 comfyui 自定义页面提供给自己的客户,可以参考相关的前端代码。
最佳实践
为了方便大家直观体验一下该解决方案成效,函数计算serverless 应用中心上线基于 comfyui serverless api 解决方案搭建的 应用-【少年白马专属】破次元壁合照 ai 绘画平台 ,作为一个实验demo 开放体验,期待为广大开发者 ai 绘画创业及变现提供一些有益思考。
实验链接: https://developer.aliyun.com/plan/create/snbm
更多内容关注 serverless 微信公众号(id:serverlessdevs),汇集 serverless 技术最全内容,定期举办 serverless 活动、直播,用户最佳实践。
发表评论