解码 Midjourney V6 的精准控制力:从 customId 状态机制到 Web 端 Action 二次编辑工程全解析

过去,评价文生图系统往往只看"第一张图是否惊艳";进入商业生产后,真正决定可用性的却是另一组问题:人物右手能否单独修正,产品位置能否保持不变,横版主视觉能否扩成竖版海报,放大后文字边缘和材质纹理是否仍然稳定,以及一次成功的修改能否继续成为下一次修改的起点。生成不再是单次采样,而是一条需要状态、版本、审计和回退能力的编辑链。

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_MASKWAITING_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 spacecontinuous studio backdropsame 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 并默认不可执行,记录脱敏指纹供开发者分析;不能因为标签里包含 UV 等字符就盲目启用。映射规则需要带 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 图像才从灵感工具变成可管理的视觉工程能力。

相关推荐
陈随易2 小时前
FFmpeg 9.0 发布,代号 Lei,音视频处理再升级
前端·后端·程序员
爱丶狸2 小时前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin
北辰alk3 小时前
用 Seed-Evolving 当「脑洞合伙人」:我用 AI 搭了个想象力健身房
ai编程
用户059540174465 小时前
把AI对话记忆存储测试从手工改成Playwright+pytest,覆盖率从20%提到96%,回归时间缩短90%
前端·css
kyriewen5 小时前
别再这样写TypeScript了——Code Review中最常见的8个反模式
前端·javascript·typescript
名字还没想好☜5 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
午安~婉5 小时前
GitHub Token/ GitHub Stats统计显示图异常
前端·github·vercel·github token
后端小肥肠5 小时前
我做了个能一键搭建个人工作台的 Skill,已开源
人工智能·aigc·agent
码云之上6 小时前
AI Agent 工程化总览篇:从 Prompt 到 Harness
前端·人工智能
sg_knight6 小时前
ZCode 3.0 安装全攻略:下载、配置、跑通第一个任务
llm·agent·ai编程·coding·ai工具·智谱·zcode