fix(canvas): 视频生成 - 显式 @文本 引用内联真实内容

普通模型视频渠道 promptOnly 门槛此前连显式 @文本N 引用一并拦截,
提示词只剩【文本N】占位符、文本节点正文未进上游报文。现 textBlocks
始终追加;promptOnly 拦自动上游连线文本的 8d4f656a 语义保持不变。
补 3 例专项测试并同步 pending-test 待验收清单。
This commit is contained in:
VicoHu
2026-09-11 18:05:38 +08:00
committed by ddCat
parent 1de105b537
commit 7ebbd9e003
4 changed files with 46 additions and 2 deletions

View File

@@ -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 工作流按保存的字段槽位接收图片、视频和音频;
// 配置节点不能因为提示词里没有逐个 @ 就丢失已连接媒体。

View File

@@ -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(

View File

@@ -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角色设定:禾禾");
});
});