零门槛AI 绘图:教你为客户定制 ComfyUI Serverless API 应用

news2024/9/9 7:50:12

作者:鸥弋、筱姜

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 截屏2024-07-30 14.12.49.png

方案优势

在以往的活动中,我们也面临了很多非技术相关的用户期望享受 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。 截屏2024-07-30 13.59.35.png

注意! 我们提供的代码仅用于运营活动使用,作为 Serverless 方式调用的实践参考。 功能未经过严格测试,请根据实际的业务需要开发或调整相关的代码,并构建 ComfyUI 镜像。

目前提供的 Agent 能力介绍

开启 Agent 能力,需要增加环境变量

  • USE_AGENT1

当通过 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-Typetask-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 + /api/status 方式时,您需要挂载一个 NAS 实例或改造代码,将状态存放至 OTS 等数据库,否则在多实例时无法获取进度

异步调用

调用 /api/run 接口,并且添加 HTTP Header,借助函数计算自带的能力,将请求转换为异步形式

  • Key:X-Fc-Invocation-Type
  • Value:Async

:当选择异步调用时,您需要挂载一个 NAS 实例或改造代码,将状态存放至 OTS 等数据库,否则在多实例时无法获取进度

二次开发

我们提供的 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 截屏2024-07-30 14.07.49.png

更多内容关注 Serverless 微信公众号(ID:serverlessdevs),汇集 Serverless 技术最全内容,定期举办 Serverless 活动、直播,用户最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1963532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代理仓业务好做吗?有没有什么系统可以打理的?

做海外仓代理相对自建海外仓而言,风险较小,更适合货代,但也需要面对一些难题:仓库管理、市场拓展、多仓协同、打通接口等。易境通DWMS系统就可以让海外仓代理变得简单明晰,让刚进入这个领域的小白也可以成功转型&#…

各类网页设计的设计尺寸大揭秘

我们讨论网页的大小时,我们需要明白这不仅仅是一个数字。在一个充满随时变化和创新的数字世界中,网页的建立和设计应考虑设备类型、屏幕大小和分辨率、用户浏览习惯等诸多因素。本文将深入分析这个主题,以帮助您充分理解这种情况,…

tensorRT 自带测试样例 sampleOnnxMNIST 源码分析

该测试样例是进行手写数字识别,使用 Visual Studio 2022 打开 tensorRT安装目录下的 samples 下的 sampleOnnxMNIST 工程( 例如我的是 D:\install\tensorRT\TensorRT-8.6.1.6\samples\sampleOnnxMNIST ),执行输出结果: 成功识别出数字0&#…

ai模特换装软件哪个好用?不知道怎么穿搭就用这几个

最近#紫色跑道的city穿搭#风靡全网,大家纷纷晒出自己的紫色风情。 可一想到衣橱里堆积如山的衣服和钱包的“瘦身计划”,是不是有点小纠结? 别怕,科技来救场!那就是“一键换装在线工具”,让你无需剁手&…

【初阶数据结构】11.排序(2)

文章目录 2.3 交换排序2.3.1 冒泡排序2.3.2 快速排序2.3.2.1 hoare版本2.3.2.2 挖坑法2.3.2.3 lomuto前后指针2.3.2.4 非递归版本 2.4 归并排序2.5 测试代码:排序性能对比2.6 非比较排序2.6.1 计数排序 3.排序算法复杂度及稳定性分析 2.3 交换排序 交换排序基本思想…

2024最新 Navicat Premium 17 简体中文激活版详细安装教程(最简单的激活方式)

一、下载地址 下载链接:分享文件:Navicat Premium 17.0.8 (x64) 中文版.zip 二、安装步骤 1、解压后点击运行navicat170_premium_cs_x64.exe 2、开始安装 3、选择安装路径,最好不要放在系统盘C盘,后面两个步骤默认 4、安装中&a…

【小知识】黑白分明的计算机世界——关系表达式,逻辑表达式和三目运算符

【小知识】黑白分明的计算机世界——关系表达式,逻辑表达式和三目运算符 1.逻辑变量2.关系表达式和逻辑表达式2.1.关系表达式2.1.1.例题——a和b的关系2.1.2.浮点数精度误差 2.2.逻辑表达式2.2.1.常见的逻辑运算符2.2.2.优先级2.2.3.注意事项2.2.3.1.在写逻辑表达式…

书生大模型学习笔记 - 连接云端开发机

申请InternStudio开发机: 这里进去报名参加实战营即可获取 书生大模型实战营 InternStudio平台 创建开发机 SSH连接开发机: SSH免密码登录 本地创建SSH密钥 ssh-keygen -t rsa打开以下文件获取公钥 ~/.ssh/id_rsa.pub去InternStudio添加公钥 …

OPenCV高级编程——OpenCV常见的API及绘图知识详解

目录 引言 一、Mat类详解 1. Mat类的基本结构 2. Mat类的数据类型 3. Mat类的创建与初始化 4. Mat类的使用技巧 二、OpenCV核心功能模块 1. 基本的图像读取与显示 2. 图像的保存 3. 矩阵操作 4. 等待键盘输入与销毁窗口 5. 命名窗口 三、图像处理模块 1. 色彩空间…

一个简单的车辆目标检测和跟踪示例

点击下方卡片,关注“小白玩转Python”公众号 介绍 目标检测:目标检测是指在图像或视频帧中识别和定位特定目标,并使用边界框来确定它们的位置。YOLO(You Only Look Once)是一种高效的单阶段目标检测算法,以…

普冉Puya 超高性价比M0 MCU 工业电子解决方案

普冉半导体(上海)股份有限公司成立于2016年,总部位于上海张江高科,公司目前主要产品包括微控制器芯片、非易失性存储器芯片及模拟产品。产品广泛应用于物联网、智能手机及周边、可穿戴、服务器、光模块、工业控制、汽车电子、安防等领域。公司在深圳、韩…

Spring Boot集成udp通讯

Spring Boot集成udp通讯 加入依赖编辑配置文件配置相关属性具体业务类客户端调试 加入依赖 <!--加入UDP通信所需依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-integration</artifactId&…

GD32 MCU电源复位和系统复位有什么区别

GD32 MCU的复位分为电源复位和系统复位&#xff0c;电源复位又称为冷复位&#xff0c;相较于系统复位&#xff0c;上电复位更彻底&#xff0c;下面为大家详细介绍上电复位和系统复位的实现以及区别。 电源复位包括上电/掉电复位或者从standby模式唤醒产生的复位&#xff0c;电…

HarmonyOS NEXT——奇妙的调用方式

注解调用一句话总结Extend抽取特定组件样式、事件&#xff0c;可以传递参数Style抽取公共样式、事件&#xff0c;不可以传递参数Builder抽取结构、样式、事件&#xff0c;可以传递参数BuilderParams自定义组件中传递UI组件多个BuilderParams自定义组件中传递多个UI组件 Extend…

echarts加载区域地图,并标注点

效果如下&#xff0c;加载了南海区域的地图&#xff0c;并标注几个气象站点&#xff1b; 1、下载区域地图的JSON&#xff1a;DataV.GeoAtlas地理小工具系列 新建nanhai.json&#xff0c;把下载的JSON数据放进来 说明&#xff1a;如果第二步不打勾&#xff0c;只显示省的名字&a…

全新微软语音合成网页版源码,短视频影视解说配音网页版系统-仿真人语音

源码介绍 最新微软语音合成网页版源码&#xff0c;可以用来给影视解说和短视频配音。它是TTS文本转语言&#xff0c;API接口和PHP源码。 这个微软语音合成接口的源码&#xff0c;超级简单&#xff0c;就几个文件搞定。用的是官方的API&#xff0c;试过了&#xff0c;合成速度…

InnoDB存储引擎(1)

InnoDB存储引擎的优点 InnoDB在设计时考虑到了处理大数据量时的性能&#xff0c;支持事务&#xff0c;回滚和崩溃修复的能力&#xff0c;通过多版本并发控制来减少锁定(降低了锁的争用),同时还支持外键的约束&#xff1b;通过缓冲池在内存中缓存数据来提高查询的性能&#xff…

内容营销专家刘鑫炜:驾驭AI为品牌服务,从成为卓越投喂师开始!

在这个信息爆炸、注意力稀缺的时代&#xff0c;品牌内容营销已成为企业连接消费者、塑造品牌形象的关键途径。而人工智能&#xff08;AI&#xff09;技术的融入&#xff0c;更是为内容营销带来了前所未有的变革与机遇。然而&#xff0c;要让AI真正为你的品牌内容营销高效服务&a…

vue3后台管理系统 vue3+vite+pinia+element-plus+axios上

前言 项目安装与启动 使用vite作为项目脚手架 # pnpm pnpm create vite my-vue-app --template vue安装相应依赖 # sass pnpm i sass # vue-router pnpm i vue-router # element-plus pnpm i element-plus # element-plus/icon pnpm i element-plus/icons-vue安装element-…

WebWorker处理百万数据

Home.vue <template><el-input v-model"Val" style"width: 400px"></el-input><el-button click"imgHandler">过滤</el-button><hr /><canvas id"myCanvas" width"500" height&quo…