fix(canvas): 视频生成 - 显式 @文本 引用内联真实内容
普通模型视频渠道 promptOnly 门槛此前连显式 @文本N 引用一并拦截,
提示词只剩【文本N】占位符、文本节点正文未进上游报文。现 textBlocks
始终追加;promptOnly 拦自动上游连线文本的 8f20240d 语义保持不变。
补 3 例专项测试并同步 pending-test 待验收清单。
This commit is contained in:
@@ -1644,3 +1644,11 @@ v2(用户反馈首版难看后重做):菜单改用 antd Dropdown(placeme
|
||||
- 验收用户身份、订单号、渠道交易号和完整用户 ID 查询,与状态过滤、分页组合使用。
|
||||
- 验收用户详情、订单详情、订单号复制和用户不存在的历史订单;关闭侧栏保留列表状态。
|
||||
- 检查同步状态、关单确认、明暗主题、长文本和窄屏固定操作列。
|
||||
## 视频生成显式 @文本 引用内联(2026-09-11)
|
||||
|
||||
修复普通模型视频渠道(非声明式工作流)丢弃显式 @文本N 引用内容的问题:`buildComposerGenerationContext` 此前受 `promptOnly` 门槛限制,提示词里的 @文本N 只被改写成「【文本N】」占位标记,文本节点真实内容不追加,导致发给上游的视频提示词只剩无意义占位符(如「任务要求提示词:【文本1】」)。现在显式 @文本 引用始终内联真实内容并在提示词末尾以「【文本N】\n正文」块追加;`promptOnly` 拦截自动上游连线文本的原有语义(提交 8f20240d)不变——未显式引用的连线文本在视频模式下仍不自动拼接,图片仍走结构化参考图通道。提示词超长时按既有校验明确报错,不静默截断。专项测试(canvas-node-generation-mentions.test.ts,新增 3 例)与前端全量测试通过(与 main 基线相比无新增失败),需浏览器验收:
|
||||
|
||||
- [ ] 画布视频节点提示词同时 @图片1 和 @文本1(文本节点含多行正文)后用方舟或普通视频渠道生成:抓上游请求,text 为「原提示词 + 【文本1】标记 + 空行 + 【文本1】\n文本正文」,参考图仍为结构化 image_url(方舟素材绑定时为 asset:// 引用)。
|
||||
- [ ] 视频节点只连图片和文本节点、提示词不写任何 @ 引用时:text 仍只有输入框原文,连线文本不自动拼入(8f20240d 行为保持)。
|
||||
- [ ] 图片、文本、音频模式生成行为不变;显式 @文本 引用的图片节点重试同样内联文本内容。
|
||||
- [ ] 引用超长文本(超过模型提示词上限)时提交被拦截并提示具体字符数,不出现静默截断或直接丢弃。
|
||||
|
||||
@@ -229,7 +229,8 @@ function buildComposerGenerationContext(
|
||||
}
|
||||
|
||||
nextPrompt += normalizedPrompt.slice(lastIndex);
|
||||
if (textBlocks.length && !promptOnly) nextPrompt = `${nextPrompt.trim()}\n\n${textBlocks.join("\n\n")}`;
|
||||
// 显式 @文本 引用是用户写进输入框的内容,必须内联真实文本;promptOnly 只拦自动上游文本。
|
||||
if (textBlocks.length) nextPrompt = `${nextPrompt.trim()}\n\n${textBlocks.join("\n\n")}`;
|
||||
if (autoIncludeWorkflowMedia) {
|
||||
// RunningHub/ComfyUI 工作流按保存的字段槽位接收图片、视频和音频;
|
||||
// 配置节点不能因为提示词里没有逐个 @ 就丢失已连接媒体。
|
||||
|
||||
@@ -143,7 +143,7 @@ export function useCanvasGenerationExecutor({
|
||||
// AutoDL/其他声明式视频协议需要结构化参考素材;只有普通
|
||||
// 模型视频接口才把提示词视为纯文本输入。
|
||||
const usesWorkflowProvider = Boolean(mode !== "text" && generationConfig.taskWorkflowProvider && generationConfig.taskWorkflowProvider !== "model");
|
||||
// 普通视频协议只保留输入框文本;声明式工作流还要保留连接媒体。
|
||||
// 普通视频协议只保留输入框文本(显式 @文本 引用仍会展开为真实内容);声明式工作流还要保留连接媒体。
|
||||
const promptOnly = mode === "video" && !usesWorkflowProvider;
|
||||
try {
|
||||
const baseContext = buildNodeGenerationContext(
|
||||
|
||||
@@ -106,3 +106,38 @@ describe("canvas node generation position mentions", () => {
|
||||
expect(context.prompt).toBe("让 进入画面");
|
||||
});
|
||||
});
|
||||
|
||||
describe("canvas node generation video text references", () => {
|
||||
test("视频 promptOnly 下显式 @文本 引用仍内联真实内容,图片保持结构化引用", () => {
|
||||
const target = targetNode();
|
||||
const image = node("image-a", CanvasNodeType.Image, "data:image/png;base64,a");
|
||||
const note = node("note", CanvasNodeType.Text, "角色设定:禾禾\n光影氛围:暗调");
|
||||
const context = buildNodeGenerationContext(target.id, [image, note, target], [connection(image.id), connection(note.id)], "参考图:@图片1\n任务要求:@文本1", [], true);
|
||||
|
||||
expect(context.prompt).toBe("参考图:@图片1\n任务要求:【文本1】\n\n【文本1】\n角色设定:禾禾\n光影氛围:暗调");
|
||||
expect(context.referenceImages.map((item) => item.id)).toEqual(["image-a"]);
|
||||
expect(context.textCount).toBe(1);
|
||||
expect(context.imageCount).toBe(1);
|
||||
});
|
||||
|
||||
test("视频 promptOnly 下未显式引用的连线文本不会自动拼进提示词", () => {
|
||||
const target = targetNode();
|
||||
const image = node("image-a", CanvasNodeType.Image, "data:image/png;base64,a");
|
||||
const note = node("note", CanvasNodeType.Text, "角色设定:禾禾");
|
||||
const context = buildNodeGenerationContext(target.id, [image, note, target], [connection(image.id), connection(note.id)], "生成一段暗调舞蹈视频", [], true);
|
||||
|
||||
expect(context.prompt).toBe("生成一段暗调舞蹈视频");
|
||||
expect(context.referenceImages.map((item) => item.id)).toEqual(["image-a"]);
|
||||
expect(context.textCount).toBe(0);
|
||||
});
|
||||
|
||||
test("视频 promptOnly 非显式引用路径仍丢弃上游文本,关闭后恢复自动拼接", () => {
|
||||
const target = targetNode();
|
||||
const note = node("note", CanvasNodeType.Text, "角色设定:禾禾");
|
||||
const nodes = [note, target];
|
||||
const connections = [connection(note.id)];
|
||||
|
||||
expect(buildNodeGenerationContext(target.id, nodes, connections, "跳舞", [], true).prompt).toBe("跳舞");
|
||||
expect(buildNodeGenerationContext(target.id, nodes, connections, "跳舞", [], false).prompt).toBe("跳舞\n\n角色设定:禾禾");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user