过去,评价文生图系统往往只看"第一张图是否惊艳";进入商业生产后,真正决定可用性的却是另一组问题:人物右手能否单独修正,产品位置能否保持不变,横版主视觉能否扩成竖版海报,放大后文字边缘和材质纹理是否仍然稳定,以及一次成功的修改能否继续成为下一次修改的起点。生成不再是单次采样,而是一条需要状态、版本、审计和回退能力的编辑链。
Midjourney 的 Vary、Vary Region、Zoom Out、Pan、Reroll 与 Upscale 等 Creation Actions,正是这条编辑链的交互入口。需要先说明技术边界:用户可以观察到按钮、消息组件、任务回调和结果图,但无法仅凭界面反推出 Midjourney 服务端的完整模型结构、隐空间尺寸或采样器实现。本文对 Latent Inpainting、Outpainting 的讨论属于扩散模型通用机制与工程解释,不把它冒充为未公开的官方源码;对 customId 的分析聚焦可观测的交互协议,不把一个不透明动作令牌解释成"包含全部 Seed 的可逆编码"。
版本也必须分开看。V6/v6.1 奠定了这套二次编辑工作流的工程语境,而产品界面和模型版本会继续演进。按当前公开帮助信息,较新的生成版本已经出现,部分 Vary Region 与 Pan 路径仍使用 V6.1 兼容能力;最新 Upscale 选项以 Subtle 和 Creative 为主,通常把图像边长放大两倍。某些 WebUI 显示的"2x/4x"可能来自特定上游、旧版能力或连续放大编排,前端不应把它写死成所有账号、所有版本都具备的固定事实。理解这一点,才能把"功能上线"真正转化为可靠的 AIGC 视觉工程。
一、视觉生成式 AI 的控制力革命:从盲盒抽卡到精准二次编辑
传统 Text-to-Image 的基本过程可以抽象为:给定文本条件 (c)、随机噪声 (z_T) 与模型参数 (\theta),经过一系列去噪步骤得到图像 (x_0=G_\theta(z_T,c))。即使 Prompt 完全相同,只要 Seed、模型版本、个性化配置、采样路径或服务端默认参数发生变化,构图和细节就可能漂移。这种随机性有利于探索,却不适合把一张已经"八成正确"的图片精修到交付标准。
商业图像的目标通常不是"再来四张不同的",而是"只改错的两成"。例如一张美妆海报已经具备正确的模特、产品、光线和色调,只需要修正手指、替换耳饰并向上扩展留出标题区。如果重新执行完整 Prompt,模型可能同时更换人物面孔、产品比例与背景结构,前一次选择成本全部作废。二次编辑的价值,就是把搜索空间从"整幅图的所有像素与语义"收缩到"指定区域、指定方向或指定分辨率"。
从产品层看,Action 可分为五种语义:Vary 重新探索相近候选;Vary Region 对局部区域重绘;Zoom Out 向四周补充上下文;Pan 沿单一方向扩展画布;Reroll 从当前条件重新采样;Upscale 提升输出尺寸,并根据模式决定尽量保真还是允许生成新细节。它们不是一组孤立按钮,而是一张有向图:每个结果节点都携带可继续执行的动作集合,每次动作又产生新的任务和子节点。
#mermaid-svg-JljoF4g9UczZkYJI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JljoF4g9UczZkYJI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JljoF4g9UczZkYJI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JljoF4g9UczZkYJI .error-icon{fill:#552222;}#mermaid-svg-JljoF4g9UczZkYJI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JljoF4g9UczZkYJI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JljoF4g9UczZkYJI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JljoF4g9UczZkYJI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JljoF4g9UczZkYJI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JljoF4g9UczZkYJI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JljoF4g9UczZkYJI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JljoF4g9UczZkYJI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JljoF4g9UczZkYJI .marker.cross{stroke:#333333;}#mermaid-svg-JljoF4g9UczZkYJI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JljoF4g9UczZkYJI p{margin:0;}#mermaid-svg-JljoF4g9UczZkYJI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JljoF4g9UczZkYJI .cluster-label text{fill:#333;}#mermaid-svg-JljoF4g9UczZkYJI .cluster-label span{color:#333;}#mermaid-svg-JljoF4g9UczZkYJI .cluster-label span p{background-color:transparent;}#mermaid-svg-JljoF4g9UczZkYJI .label text,#mermaid-svg-JljoF4g9UczZkYJI span{fill:#333;color:#333;}#mermaid-svg-JljoF4g9UczZkYJI .node rect,#mermaid-svg-JljoF4g9UczZkYJI .node circle,#mermaid-svg-JljoF4g9UczZkYJI .node ellipse,#mermaid-svg-JljoF4g9UczZkYJI .node polygon,#mermaid-svg-JljoF4g9UczZkYJI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JljoF4g9UczZkYJI .rough-node .label text,#mermaid-svg-JljoF4g9UczZkYJI .node .label text,#mermaid-svg-JljoF4g9UczZkYJI .image-shape .label,#mermaid-svg-JljoF4g9UczZkYJI .icon-shape .label{text-anchor:middle;}#mermaid-svg-JljoF4g9UczZkYJI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JljoF4g9UczZkYJI .rough-node .label,#mermaid-svg-JljoF4g9UczZkYJI .node .label,#mermaid-svg-JljoF4g9UczZkYJI .image-shape .label,#mermaid-svg-JljoF4g9UczZkYJI .icon-shape .label{text-align:center;}#mermaid-svg-JljoF4g9UczZkYJI .node.clickable{cursor:pointer;}#mermaid-svg-JljoF4g9UczZkYJI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JljoF4g9UczZkYJI .arrowheadPath{fill:#333333;}#mermaid-svg-JljoF4g9UczZkYJI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JljoF4g9UczZkYJI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JljoF4g9UczZkYJI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JljoF4g9UczZkYJI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JljoF4g9UczZkYJI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JljoF4g9UczZkYJI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JljoF4g9UczZkYJI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JljoF4g9UczZkYJI .cluster text{fill:#333;}#mermaid-svg-JljoF4g9UczZkYJI .cluster span{color:#333;}#mermaid-svg-JljoF4g9UczZkYJI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JljoF4g9UczZkYJI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JljoF4g9UczZkYJI rect.text{fill:none;stroke-width:0;}#mermaid-svg-JljoF4g9UczZkYJI .icon-shape,#mermaid-svg-JljoF4g9UczZkYJI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JljoF4g9UczZkYJI .icon-shape p,#mermaid-svg-JljoF4g9UczZkYJI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JljoF4g9UczZkYJI .icon-shape .label rect,#mermaid-svg-JljoF4g9UczZkYJI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JljoF4g9UczZkYJI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JljoF4g9UczZkYJI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JljoF4g9UczZkYJI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 整体方向不对
方向正确
局部仍失败
Prompt 与参数
初始生成任务
候选评审
Reroll
Vary
Vary Region
Zoom Out 或 Pan
Upscale
质量检查与导出
这张图揭示了控制力的三个层次。第一层是语义控制:Prompt、Remix 和参考图决定"要生成什么";第二层是空间控制:Mask、Zoom 与 Pan 决定"在哪里改变";第三层是流程控制:任务状态、动作句柄、幂等键和版本快照决定"如何稳定地继续"。只有三层同时成立,图生图一致性才不是一次偶然的视觉效果,而是可以重复执行、量化验收的生产能力。
控制力也不等于绝对像素锁定。Vary Region 的目标是尽量保持未选区域,而不是承诺每个未选像素在编码、压缩和合成后逐位相同;Creative Upscale 会主动补充细节,也不能与传统插值放大混为一谈。工程验收应使用分层指标:主体身份可用人脸或实例特征相似度衡量,布局可用关键点和边界框偏移衡量,色彩可用直方图或 Delta E 衡量,局部修改则比较 Mask 内任务完成度与 Mask 外感知差异。把"看起来差不多"变成指标,才能知道哪个 Action 真正改善了作品。
二、Midjourney 底层交互逻辑解构:Discord 组件与 customId 状态机
在 Discord 交互模型中,一条机器人消息可以带有 Action Row、Button 等组件。按钮除了可见标签,还包含供交互回调识别的 custom_id。当用户点击按钮时,客户端并不是重新拼接一条自然语言命令,而是向交互系统提交消息上下文、组件标识和身份信息;服务端据此判断动作是否合法、作用于哪个结果,以及下一步需要创建什么任务。WebUI Action 封装的核心工作,正是把这种"消息上的可执行能力"转换为自己的任务与按钮模型。

这里最容易出现一个误解:customId 不应被当作可随意拆解的业务 JSON,更不能假定其中必然明文保存 Prompt、Seed、图片索引和所有参数。对集成层而言,它首先是上游签发的不透明动作句柄。即使某个阶段的字符串看起来带有可读片段,生产代码也应该整体保存、整体回传,并把展示标签、动作类型、来源消息和版本能力单独建模。上游一旦调整编码格式,依赖字符串切片的系统就会立刻失效。
一个可审计的动作快照至少要记录以下字段:内部任务 ID、上游消息 ID、父任务 ID、结果图资产 ID、Prompt 摘要、模型或路由别名、动作标签、原始 customId、首次观测时间、过期策略与动作执行状态。customId 属于敏感操作数据,日志中应只保存哈希指纹或末尾少量字符,原值放在加密存储中,并限制租户、用户和任务三重作用域。
json
{
"taskId": "tsk_01J...",
"parentTaskId": "tsk_01H...",
"sourceMessageId": "msg_128...",
"assetId": "ast_sha256_7f...",
"modelAlias": "mj-v6.1-compatible",
"state": "SUCCEEDED",
"actions": [
{
"actionId": "act_01J...",
"kind": "VARY_REGION",
"label": "Vary Region",
"customIdRef": "secret://actions/act_01J...",
"customIdFingerprint": "sha256:21b7...",
"status": "AVAILABLE"
}
]
}
为什么新任务有 Action,旧图片却没有
"旧图没有按钮"往往不是图像本身在像素层面无法编辑,而是集成系统没有保存它当时的交互上下文。新任务完成时,回调消费者拿到了消息的 components 数组,于是能够抽取 Action 并持久化;旧图片如果只保存了 CDN 地址或下载文件,没有保存消息 ID、任务关联和按钮令牌,系统就缺少可验证的上游动作入口。图片文件只能证明"结果是什么",不能证明"当前账号仍被允许对它执行哪些动作"。
这类缺失不能靠猜测 customId 修复。正确恢复顺序是:先查本地任务事件与原始回调;再查供应方是否提供按任务或消息查询能力;在 Discord 支持且权限允许时,可通过公开的历史消息或 /show 类恢复路径重新展示既有 Job,但仍要以实际返回的组件为准;若无法恢复,就把旧图导入支持外部图像的 Editor 或新任务,而不是伪造按钮。能否恢复还受版本、权限、内容来源和平台条款影响。
用状态机代替"按钮点完就等"
一次 Action 从点击到结果返回,至少经历 AVAILABLE -> SUBMITTED -> QUEUED -> RUNNING -> SUCCEEDED/FAILED/EXPIRED。局部重绘还可能多出 WAITING_MASK 与 WAITING_PROMPT;回调乱序时,系统必须按事件序号或服务器时间做单调更新,不能让迟到的 RUNNING 覆盖已经落库的 SUCCEEDED。用户重复点击时,要用 actionId + maskHash + promptHash 生成幂等键,确保同一个意图不会因网络重试消耗两次计算额度。
父子关系同样重要。Reroll、Vary 和 Pan 都会创建新节点,而不是原地修改父图。若只在一条数据库记录上覆盖 imageUrl,团队将无法比较分支、回退到上一版或解释费用去向。更合理的数据结构是事件溯源或不可变任务图:父节点永久保留,每个 Action 产生子任务,最终选中的节点另设 approvedVersionId。这就是 customId 状态映射真正的价值:它连接的不只是一个按钮和一个 HTTP 请求,而是视觉资产的完整谱系。
三、局部重绘 Vary Region 与扩散模型隐空间修复机制
Vary Region 面向的用户承诺非常直观:选择图像的一部分,让系统重新生成该区域,并尽量保持其他区域不变。在当前产品路径中,Discord 侧称为 Vary Region,网站 Editor 中常以 Erase 工具呈现。开启 Remix 后,还可以在局部重绘时修改 Prompt。实践上,选择范围需要给模型足够上下文;选区太小,模型难以理解目标与周围结构,选区太大,又会把本来正确的内容一起带入重采样。

从通用 Latent Inpainting 机制看,原图 (x) 先经编码器映射到隐变量 (z_0=E(x)),用户掩码 (M\in0,1^{H\times W}) 被缩放到隐空间分辨率。白色区域表示允许重绘,黑色区域表示尽量保留。第 (t) 步可以概念化为:
z_t\^{mix}=M\\odot z_t\^{gen}+(1-M)\\odot z_t\^{src}
其中 (z_t^{gen}) 是受新 Prompt 条件引导的生成分支,(z_t^{src}) 是原图隐变量按相同噪声日程得到的参考分支。每轮去噪后再按 Mask 合成,可以抑制未选择区域漂移。真实商业系统还会处理掩码羽化、边缘膨胀、颜色匹配、解码误差与安全策略;公式表达的是常见思想,并非对某个闭源服务内部代码的断言。
Mask 不是简单的黑白贴纸
局部重绘的质量常由 Mask 工程决定。若 Mask 紧贴物体边缘,模型没有足够空间重建阴影、遮挡和材质过渡,容易留下硬边;若向外扩得太多,人物身份和背景纹理又会漂移。一个实用流程是先对二值 Mask 做 8--32 像素的语义相关膨胀,再生成 4--16 像素的羽化带。具体数值应按输出分辨率归一化,例如令膨胀半径 (r=\alpha\min(H,W)),而不是对 512 像素和 4K 图片固定使用同一半径。
选区还要覆盖因果上下文。修手指不能只涂一个指尖,应包含整只手、袖口与被握物体的接触区;替换耳饰要包含耳垂、头发遮挡和局部皮肤;修改包装文字时,应覆盖完整文字块及其透视平面。模型生成的是视觉关系,不是 Photoshop 的字符替换命令。需要精确品牌字样时,可靠做法通常是让模型生成干净承载面,再由排版工具叠加真实文字,而不是反复赌模型拼写。
Prompt 也应局部化。初始生成 Prompt 可能包含场景、镜头、风格、材质和氛围几十个要素;局部重绘时重复全部描述,反而会提高系统重新解释整幅图的倾向。更稳定的写法是保留身份与材质锚点,只描述选区目标,例如 silver geometric earring, realistic metal reflection, same studio lighting。修改多处时分步执行,每一步只解决一个可验收问题,成功后再把新节点作为下一步父图。
与可控开源工作流的差异
| 方案 | 主要输入 | 空间约束方式 | 一致性优势 | 工程代价 |
|---|---|---|---|---|
| Midjourney Vary Region | 原图、选区、可选新 Prompt | 平台内置 Mask 与上下文重绘 | 交互简单,风格延续自然 | 内部参数不可见,依赖任务与 Action 状态 |
| Stable Diffusion XL Inpaint | 原图、Mask、Prompt、Seed、采样参数 | 显式 Mask 与去噪强度 | 参数透明,可本地复现 | 节点、显存、模型与插件治理复杂 |
| ControlNet Inpaint | 上述输入加边缘、深度或姿态条件 | Mask 加结构控制图 | 几何约束强,适合姿态和产品结构 | 控制权重需调参,条件冲突会出伪影 |
| IP-Adapter | 参考图特征加文本条件 | 图像特征注入注意力层 | 身份、风格或物体一致性较好 | 参考强度过高会抑制编辑自由度 |
| Flux.1 Redux 类参考工作流 | 参考图与生成条件 | 参考特征引导 | 适合图像变体和语义延续 | 能力受具体模型、许可与节点实现影响 |
这些方案不是简单的"谁替代谁"。Midjourney 适合把复杂参数隐藏在高层 Action 后面;ControlNet、SDXL、IP-Adapter 更适合需要确定性节点、私有部署或批量参数搜索的团队。成熟架构甚至会混合使用:先用 Midjourney 快速确定视觉方向,再在可控管线中完成字体、产品几何和批量适配。比较标准应是任务完成率、单位成功样本成本与回放能力,而不是单张图的主观冲击力。
如何衡量图生图一致性
设 Mask 内区域为 (R_{in}),外部为 (R_{out})。局部编辑的目标可写成多目标函数:
J=\\lambda_1 L_{task}(R_{in})+\\lambda_2 D_{perceptual}(R_{out}^{new},R_{out}^{old})+\\lambda_3 L_{seam}+\\lambda_4 L_{identity}
L_task 衡量新耳饰、正确手部等目标是否完成;外部感知距离衡量背景是否漂移;L_seam 检查边界色差和纹理断裂;身份损失用于人物或产品主体。线上系统不一定能获得内部 Loss,但可以用人工双检、LPIPS/SSIM、特征相似度与局部缺陷模型组合成代理指标。只有同时看 Mask 内"改对了没有"和 Mask 外"误伤了多少",才不会把一张漂亮但换了人的图误判为成功。
四、构图拓展 Zoom 与平移 Pan 的外绘 Outpainting 算法
局部重绘是在画布内部修改,Outpainting 则要回答画布之外是什么。Zoom Out 通常保留原图作为中心内容,并在四周增加新区域;Pan 沿上、下、左、右某一方向扩展,适合为标题、人物视线或产品动线腾出空间。它们看似只是改变画布尺寸,实质上同时改变了视觉叙事:镜头从特写退到中景,主体在新画幅中的占比下降,背景需要补充符合透视、光线和语义的内容。
设原图尺寸为 (W\times H),Zoom 系数为 (s>1)。若界面把原图缩放为新画布中心的 (1/s),则理想新画布尺寸约为 (W'=sW, H'=sH),新增面积比例为 (s^2-1)。1.5x Zoom 的新增面积不是 50%,而是 125%;2x Zoom 的新增面积达到原图的三倍。新增区域越大,模型获得的自由度越高,偏离原叙事的风险也越高。因此大幅扩图最好拆成两次较小 Zoom,每次评审边缘、透视和主体比例。
Make Square 可以视为受约束的画布补齐。横图 (W>H) 时,通常向上下或指定方向补足到 (W\times W);竖图 (H>W) 时,则向左右补足到 (H\times H)。它不是把原图非等比拉伸成正方形,而是尽量保持已有内容的几何比例,在缺失方向执行外绘。WebUI 应从上游 Action 元数据读取真实行为,不要仅根据按钮文字自行裁切。
Outpainting 的边界条件
通用外绘管线会先创建更大的画布,将原图放置在锚定位置,再把新增区和一条窄的交叠带设为可生成 Mask。交叠带给模型提供边缘纹理、景深、光照方向和透视消失点。若完全不允许重算原图边缘,新旧区域很容易出现接缝;若交叠过宽,原图主体又可能被改写。工程上应保存原始资产,并在结果返回后对非扩展区做差异检测,而不是默认"扩图一定不会动中心"。
最常见的失败包括:天空渐变在接缝处断层,建筑透视线不连续,重复生成半个人物,背景文字出现乱码,以及 Pan 多次后主体逐步偏离主题。应对方法包括在 Prompt 中声明场景延续而非新增主体,使用 empty negative space、continuous studio backdrop、same lighting direction 等约束;一次只沿一个方向 Pan;每轮都从通过评审的节点继续;对不能接受的文字、Logo 和产品几何,回到传统合成软件完成最终锁定。
Zoom、Pan 与裁切也不能混为一谈。裁切只删除像素,不产生新信息;普通图像缩放只进行插值;Outpainting 会生成原图不存在的内容。对于电商多尺寸适配,先从高质量母版外绘到目标比例,再进行轻量裁切,通常比把同一方图硬塞进所有版式更自然。但生成内容必须经过品牌、版权与安全审核,尤其不能把扩出的虚构产品结构当成真实商品信息。
Reroll、Vary 与 Zoom 的选择边界
当整体构图、主体或风格都不对时,使用 Reroll;当方向正确但想比较相近候选时,使用 Vary;当局部缺陷明确时,使用 Vary Region;当主体正确但画幅不足时,使用 Zoom 或 Pan。错误的动作选择会制造无效成本:用 Reroll 修一只手,相当于放弃整张图;用 Vary Region 改整体镜头,选区再大也缺少全局重新规划能力;用 Zoom 企图提高分辨率,则混淆了画布扩展与 Upscale。
可以把选择逻辑写成缺陷覆盖率 (q=A_{defect}/A_{image}) 与结构影响级别 (g) 的联合判断。若 (q<0.15) 且 (g) 低,优先局部重绘;若 (0.15\le q<0.5),尝试 Vary 或分区编辑;若主体关系、镜头和风格都需重做,即使缺陷面积不大,也应 Reroll。画幅问题独立进入 Zoom/Pan 分支,像素尺寸问题进入 Upscale 分支。这个决策树比"哪个按钮看起来强就点哪个"更节省计算预算。
五、商业级 AI 视觉创作的工程痛点:多轮迭代中的状态丢失与操作门槛
创作者看到的是图片,系统必须看到的是谱系。一个商业海报可能经历初图四选一、局部修手、替换饰品、向上 Pan、2x 放大、文字合成和颜色校正。若文件名仍是 final_v7_really_final.png,没人能准确回答哪一步用了什么 Prompt、哪张图对应哪个任务、失败重试消耗了多少额度,以及能否从第三步重新生成另一个比例。这不是整理习惯问题,而是生产系统缺少版本模型。
第一类痛点是状态丢失。只持久化结果 URL 会受到链接过期、上游消息删除和 Action 组件缺失影响;只保存 customId 又没有来源消息、租户和能力版本,也可能在回放时失去上下文。系统应同时保存二进制资产、内容哈希、任务事件、动作快照和父子关系,并把外部 URL 视为传输位置而非永久资产标识。
第二类痛点是操作门槛。Discord 原生交互适合个人探索,但团队工作还需要权限、审批、批注、批量任务和可见的成本。WebUI 的价值并非简单"把命令改成按钮",而是把低层组件翻译成稳定的领域语义:设计师看到"局部重绘",后端知道需要 Mask、父任务、动作句柄和 Prompt;财务看到本次动作消耗;审阅者能并排比较前后差异。
第三类痛点是多轮退化。每一次生成式修改都可能累积身份漂移、纹理过锐、重复细节和压缩误差。连续 Creative Upscale 并不等于无损放大,连续局部重绘也可能让边缘越来越不自然。系统需要设置最大生成深度,例如一条分支不超过六次生成式 Action;超过阈值就提示回到较早母版,或切换到确定性的图像编辑工具。
第四类痛点是能力漂移。Action 标签、可用模型、输出尺寸和兼容规则会随版本、账号和入口变化。把 UPSCALE_4X 写成永远存在的数据库枚举,会让前端在上游取消该按钮后仍显示无效操作。更稳妥的做法是"核心语义 + 原始能力"双层模型:核心层识别 UPSCALE、VARY、OUTPAINT 等大类,展示层仍以本次任务实际返回的 label、参数约束和句柄为准。
生产指标也要从"生成了多少张"升级为"交付了多少张"。建议记录首轮通过率、每个通过资产的平均 Action 次数、局部编辑成功率、Mask 外漂移率、P50/P95 完成时延、重试放大系数、单位交付成本和人工返工分钟数。如果某类手部修复平均重试四次仍低于 50% 成功率,就应引入专门修复模型或人工流程,而不是继续用更多 Reroll 掩盖问题。
六、Web 端 Midjourney Action 交互架构设计与状态映射工程
一套可靠的 Midjourney API WebUI 封装应把浏览器、业务后端、异步执行器和上游交互适配器分开。前端只提交内部 actionId,绝不直接接触真实 customId;后端验证用户是否拥有父任务,再从密钥存储读取句柄,通过队列调用适配器。上游回调先写入不可变事件,再由投影器更新当前状态与可用 Action。这样既能处理 WebSocket 消息队列的乱序,也能在投影失败时重放事件。
在把原生 Discord 异步回调转化为可视化 Action 矩阵时,解析重点是消息对象的 components 及按钮所携带的动作句柄。在典型的 Web 端状态映射控制台中,可将 https://178.nz/yinc 配置为前端测试节点:任务状态更新后,界面根据本次回调实际返回的能力动态渲染放大、变体、局部重绘、构图拓展、平移与重掷入口。这个节点只负责交互展示,动作授权、句柄保管和上游调用仍应留在服务端。
Discord/Provider Upstream Adapter Task Queue Event Store Action API WebUI Discord/Provider Upstream Adapter Task Queue Event Store Action API WebUI #mermaid-svg-wag0CD0RZx3YpQyc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wag0CD0RZx3YpQyc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wag0CD0RZx3YpQyc .error-icon{fill:#552222;}#mermaid-svg-wag0CD0RZx3YpQyc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wag0CD0RZx3YpQyc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wag0CD0RZx3YpQyc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wag0CD0RZx3YpQyc .marker.cross{stroke:#333333;}#mermaid-svg-wag0CD0RZx3YpQyc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wag0CD0RZx3YpQyc p{margin:0;}#mermaid-svg-wag0CD0RZx3YpQyc .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wag0CD0RZx3YpQyc text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wag0CD0RZx3YpQyc .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wag0CD0RZx3YpQyc .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-wag0CD0RZx3YpQyc .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wag0CD0RZx3YpQyc .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wag0CD0RZx3YpQyc #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wag0CD0RZx3YpQyc .sequenceNumber{fill:white;}#mermaid-svg-wag0CD0RZx3YpQyc #sequencenumber{fill:#333;}#mermaid-svg-wag0CD0RZx3YpQyc #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wag0CD0RZx3YpQyc .messageText{fill:#333;stroke:none;}#mermaid-svg-wag0CD0RZx3YpQyc .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wag0CD0RZx3YpQyc .labelText,#mermaid-svg-wag0CD0RZx3YpQyc .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wag0CD0RZx3YpQyc .loopText,#mermaid-svg-wag0CD0RZx3YpQyc .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wag0CD0RZx3YpQyc .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wag0CD0RZx3YpQyc .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wag0CD0RZx3YpQyc .noteText,#mermaid-svg-wag0CD0RZx3YpQyc .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wag0CD0RZx3YpQyc .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wag0CD0RZx3YpQyc .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wag0CD0RZx3YpQyc .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wag0CD0RZx3YpQyc .actorPopupMenu{position:absolute;}#mermaid-svg-wag0CD0RZx3YpQyc .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wag0CD0RZx3YpQyc .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wag0CD0RZx3YpQyc .actor-man circle,#mermaid-svg-wag0CD0RZx3YpQyc line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wag0CD0RZx3YpQyc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} POST /actions/{actionId}/executetenant/owner/state/idempotency checkappend ACTION_REQUESTEDenqueue internalJobIdload opaque customId referencesubmit component interactionasync progress/result messageappend upstream eventWebSocket/SSE projection update
统一 Action 数据契约
前端需要的是稳定契约,而不是供应方原始对象。以下 TypeScript 模型保留未知动作的扩展性,同时避免把句柄泄露给浏览器:
ts
type ActionKind =
| "UPSCALE"
| "VARY"
| "VARY_REGION"
| "ZOOM"
| "PAN"
| "REROLL"
| "UNKNOWN";
interface ActionView {
actionId: string;
kind: ActionKind;
label: string;
enabled: boolean;
inputSchema?: {
maskRequired?: boolean;
promptEditable?: boolean;
direction?: "UP" | "DOWN" | "LEFT" | "RIGHT";
scale?: number;
};
}
interface ActionSecret {
actionId: string;
tenantId: string;
sourceMessageId: string;
encryptedCustomId: string;
expiresAt?: string;
}
适配器收到组件后,先按可信的结构字段读取标签与句柄,再通过可配置规则映射到核心类型。无法识别的按钮应保留为 UNKNOWN 并默认不可执行,记录脱敏指纹供开发者分析;不能因为标签里包含 U、V 等字符就盲目启用。映射规则需要带 provider + modelFamily + ruleVersion,否则一次正则更新可能改变历史任务的含义。
"Upscale 2x/4x"尤其需要能力驱动。当前任务若只返回 Subtle/Creative 的两倍放大,UI 就只显示这两个动作;若特定适配器确实返回 4x,才展示 4x;若所谓 4x 实际是两次 2x,系统应明确建立两个子任务并分别计费、评审,而不是把一次请求伪装成无损 4x。Zoom 1.5x/2x、Custom 与 Make Square 同理,必须来自本次任务可验证的 Action 或受支持的 Editor 参数。
幂等、并发与回调乱序
执行接口需要接收 Idempotency-Key。后端在同一租户内对"父任务 + Action + 输入摘要"建立唯一约束,第一次请求创建 Job,后续相同请求直接返回原 Job。若用户对同一父图同时执行向左 Pan 和 Vary Region,它们可以成为两个独立分支;若两个请求都试图更新同一个等待 Mask 的临时会话,则需要乐观锁或会话版本号拒绝覆盖。
回调消费者必须做到至少一次投递下的结果一致。事件表可以用 (provider, upstreamEventId) 去重;没有事件 ID 时,用消息 ID、状态、资源哈希和时间窗构造弱去重键。状态只允许合法迁移,例如 RUNNING -> SUCCEEDED,不允许 SUCCEEDED -> RUNNING。图片下载成功、内容哈希落库且安全扫描通过后,任务才进入可交付状态,不能把"收到一个 URL"当作完整成功。
安全与合规边界
customId、账号令牌和会话凭证都不能进入前端、分析平台或普通应用日志。Action API 必须校验父任务归属,防止攻击者枚举 actionId 操作他人图片;Mask 上传要限制格式、像素数和压缩炸弹;Prompt 与结果图要经过内容策略;回调端点要验证签名或来源,并设置重放窗口。服务还应遵守平台条款,不把未公开或未授权的交互方式包装成所谓"官方 API"。工程便利不能替代授权。
可观测性至少覆盖 traceId、内部 Job、上游任务、父任务和资产哈希五种关联键。日志只记录 Action 类型、状态、耗时、错误分类和 customIdFingerprint,不记录原句柄。告警应区分限流、鉴权、动作过期、版本不兼容、回调缺失、资源下载失败和内容审核失败,因为它们的恢复策略完全不同:限流可退避,过期句柄需要重新获取能力,鉴权错误则应立即停止重试。
七、闭环实战:基于 Action 链条的高精度商业海报二次精修工作流
下面以一张竖版香水商业海报为例。目标是保留玻璃瓶造型、冷银色灯光和同一位模特,同时修正持瓶手、替换耳饰、向上扩出标题留白,最后导出高清母版。流程的关键不是把所有问题一次交给模型,而是让每个 Action 只承担一种风险,并给每一步设置通过条件。
第一步:生成可编辑的母版
初始 Prompt 需要明确主体、镜头、材质、灯光、构图与禁止元素,但不要提前塞入难以同时满足的十几个版式变体。示例:
text
luxury perfume campaign, waist-up portrait of the same female model holding
a transparent geometric perfume bottle, cool silver studio lighting,
clean charcoal background, realistic skin texture, premium editorial photo,
product label area unobstructed, vertical composition, negative space above,
85mm lens, controlled reflections --ar 4:5 --style raw
先从候选中选择"结构最正确"的图,而不是纹理最华丽的图。评审顺序应是产品轮廓与手部接触关系、人物身份和姿态、标题留白、光线方向,最后才是皮肤与材质细节。结构错误很难靠 Upscale 修复;细节不足却可以在后续放大和传统后期中补偿。
任务完成后立即保存原图二进制、Prompt、参数、模型别名、消息 ID、Action 列表和时间戳。将选中图标记为 MASTER_01,其他候选仍保留但不进入主分支。若四张图整体都不满足镜头与主体关系,执行 Reroll;若只有风格强弱需要探索,选择 Vary Subtle 或 Vary Strong,并记录选择原因。
第二步:用 Vary Region 修正持瓶手
Mask 覆盖整只手、部分袖口、瓶颈接触区以及少量背景,边缘做羽化。不要只涂错误的两根手指,否则新手指无法与掌心和瓶体建立合理遮挡。局部 Prompt 使用 anatomically correct hand gently holding the same perfume bottle, five natural fingers, same cool studio lighting,避免重复描述整个人物和背景。
一次返回多个结果时,检查五个项目:手指数量与关节方向、瓶体几何是否变形、标签区是否被遮挡、肤色和光线是否接续、Mask 外人脸是否漂移。只有五项都通过,才能把结果设为 MASTER_02。如果手正确但瓶体被改坏,应缩小瓶身 Mask 或切换到可用结构控制的修复流程,而不是接受一个新缺陷。
第三步:单独替换耳饰
从 MASTER_02 创建新分支,Mask 覆盖耳朵、耳饰、邻近头发和阴影,用短 Prompt 指定 small silver geometric earring, realistic metal reflection, same hairstyle。手部不在 Mask 内,因此重点监控其感知差异。如果平台仍让人脸明显变化,说明选区上下文过大或生成自由度过高,应回退并调整 Mask,不要继续在漂移结果上叠加操作。
把手部和耳饰拆成两次的好处是可定位失败。如果两处一起选中,模型可能只修好一处,团队无法判断重试应改 Prompt 还是改 Mask。串行小步虽然多一次任务,却能提高每次评审的信息量,通常降低总重试次数。
第四步:Zoom 与 Pan 适配版式
主海报需要在顶部放品牌标题,可优先向上 Pan,让模型延续背景和顶光,Prompt 补充 continuous charcoal studio backdrop, clean empty negative space, no additional objects。如果还需要从 4:5 生成 16:9 横版,不要直接拉伸竖图;从已通过局部修复的母版创建独立 Zoom 分支,再根据主体位置向左右扩展。
扩图验收包括:人物比例是否符合版式、背景渐变是否连续、是否凭空增加第二个瓶子或人物、光线方向是否一致、标题安全区是否真正干净。可在系统中叠加版式安全线,但安全线只是前端辅助层,不应烘焙进生成图片。不同尺寸是兄弟分支,不要让横版由竖版连续 Pan 多次再裁切,否则误差会持续累积。
第五步:选择 Upscale 策略
若当前界面提供 Subtle 和 Creative,产品海报通常先选 Subtle,因为瓶体边缘、标签承载面和人物身份比"新增细节"更重要;若小范围皮肤、头发仍显得塑料,可对副本尝试 Creative,并并排比较。若 WebUI 显示 4x,应先确认它是上游原生能力、旧版能力还是两次 2x 编排,再决定是否接受额外生成与费用。
Upscale 后不能只看像素尺寸。把结果以 100% 和目标投放尺寸分别检查:高倍查看手、眼、耳饰、瓶盖和反射边缘,实际尺寸查看整体锐度与对比度;用边缘检测检查双轮廓,用 OCR 或人工检查文字承载区,比较放大前后主体特征。需要真实品牌文字时,在此之后进入确定性的排版和色彩管理,不再交给生成模型重写。

第六步:交付、回放与成本复盘
最终资产包应包含无字高清母版、各渠道版式、带字交付稿、色彩空间说明、生成谱系和版权审核记录。谱系中列出 MASTER_01 -> HAND_FIX -> EARRING_FIX -> PAN_TOP -> UPSCALE_SUBTLE,每个箭头对应一个 Action Job。任何审阅者都能回到某一步比较父子图,而不需要猜测"最终版到底从哪张图改来"。
复盘时计算总任务数、失败任务数、每种 Action 的成功率、人工处理时间和单位交付成本。若初图生成 8 张、局部重绘 5 次、扩图 2 次、放大 2 次,最终只交付 1 套素材,就不能用"生成了 17 张"描述效率;应计算一次有效交付消耗的 GPU 时间与人工分钟数。下一轮可据此改进 Prompt 模板、Mask 规范和动作决策树。
这条闭环也适用于人物写真、游戏概念图、电商主图和建筑氛围图,但验收权重不同。人物图优先身份与肢体,电商图优先产品几何和文字真实性,建筑图优先透视与结构连续,概念图则可给风格探索更多自由。Action 本身没有"最佳顺序",只有与业务风险相匹配的顺序。
八、总结与展望:AIGC 交互范式从 Command Line 向 Visual UI 演进
Midjourney 二次操作的意义,不只是多了几个按钮,而是把生成式图像从一次性结果提升为可持续编辑的任务图。Vary Region 把修改范围收缩到局部,Zoom 与 Pan 把画布之外变成可设计空间,Reroll 与 Vary 管理探索,Upscale 负责最终尺寸与细节策略;customId 则在交互层连接了结果、权限与下一步动作。它应该被当作不透明、受保护、可过期的能力句柄,而不是可以随意伪造的"万能参数"。
对 WebUI 工程而言,真正可靠的实现包括动态能力发现、事件驱动状态机、幂等执行、父子任务谱系、句柄加密、租户授权、回调去重、资产归档和质量指标。前端显示什么,不由产品经理在枚举表里永久写死,而由这张图、这个版本、这个账号实际返回的 Action 决定。这样才能容纳 V6/v6.1 兼容路径与后续版本变化,也能避免把"2x/4x"等界面标签错误包装成普遍承诺。
下一代 AIGC 视觉工具会进一步走向 Canvas:用户在同一画布上涂 Mask、拖拽边界、锁定对象、建立图层和参考关系,系统在背后选择 Inpainting、Outpainting、结构控制或传统图像算法。届时 Prompt 仍然重要,但它不再独自承担全部控制;空间选择、对象约束、历史版本和评审反馈会共同成为模型条件。
从"输入一句话等待结果"到"在可回放状态机中逐步收敛",是生成式视觉真正进入生产系统的分水岭。高质量工作流不追求每一步都充满惊喜,而是让正确部分被稳定保留,让错误部分被精确修改,让每次尝试都有来源、成本和退路。当生成、编辑、治理和交付形成闭环,AI 图像才从灵感工具变成可管理的视觉工程能力。