chore(release): 版本发布 - 发布 v1.2.8.rc3 候选版本
修复生产前端 Rolldown 分包循环依赖白屏,补齐画布引用排序与素材 payload 合同,并同步工作流生成素材字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { autoMentionCanvasResourceReferences, findCanvasResourceAutoLinkMatch, type CanvasResourceReference, applyCanvasConnectionPromptSync, buildAssetMentionReferences, buildCanvasNodeMentionReferenceMap, buildNodeMentionReferences, buildOrderedCanvasResourceReferences, canvasResourceMentionToken, collectUpstreamVideoNodes, imageGenerationReferenceConnections } from "../src/lib/canvas/canvas-resource-references";
|
||||
import { autoMentionCanvasResourceReferences, findCanvasResourceAutoLinkMatch, type CanvasResourceReference, applyCanvasConnectionPromptSync, buildAssetMentionReferences, buildCanvasNodeMentionReferenceMap, buildNodeMentionReferences, buildOrderedCanvasResourceReferences, canvasResourceMentionToken, collectUpstreamVideoNodes, imageGenerationReferenceConnections, reorderCanvasResourceConnections } from "../src/lib/canvas/canvas-resource-references";
|
||||
import { canvasNodeToAsset } from "../src/lib/canvas/canvas-node-asset";
|
||||
import { buildNodeGenerationInputs } from "../src/components/canvas/canvas-node-generation";
|
||||
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../src/types/canvas";
|
||||
@@ -227,6 +227,21 @@ describe("canvas resource mention slots", () => {
|
||||
expect(uploadedReference?.storageKey).toBe("video:user:source");
|
||||
});
|
||||
|
||||
test("绘图引用保留本地预览定位信息", () => {
|
||||
const drawing: CanvasNodeData = {
|
||||
id: "drawing-a",
|
||||
type: CanvasNodeType.Drawing,
|
||||
title: "草图 A",
|
||||
position: { x: 0, y: 0 },
|
||||
width: 100,
|
||||
height: 100,
|
||||
metadata: { drawingId: "drawing-document-a", drawingRevision: 3 },
|
||||
};
|
||||
const target = videoNode("target");
|
||||
const [reference] = buildNodeMentionReferences(target, [drawing, target], [connection(drawing.id, target.id)]);
|
||||
expect(reference).toMatchObject({ kind: "image", label: "绘图1", drawingId: "drawing-document-a", drawingRevision: 3 });
|
||||
});
|
||||
|
||||
test("画布节点引用只保存类型位置,不保存节点 ID", () => {
|
||||
const target = videoNode("target");
|
||||
const image = imageNode("image-a");
|
||||
@@ -339,3 +354,32 @@ describe("image generation reference connections", () => {
|
||||
expect(copied.every((item) => item.toNodeId === "result")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reorder canvas resource connections", () => {
|
||||
test("直接引用按指定顺序换位并同步编号提示词", () => {
|
||||
const imageA = imageNode("image-a");
|
||||
const imageB = imageNode("image-b");
|
||||
const target = { ...videoNode("target"), metadata: { composerContent: "比较 @图片1 和 @图片2" } };
|
||||
const nodes = [imageA, imageB, target];
|
||||
const previousConnections = [connection(imageA.id, target.id), connection(imageB.id, target.id)];
|
||||
const nextConnections = reorderCanvasResourceConnections(target.id, [imageB.id, imageA.id], nodes, previousConnections);
|
||||
expect(nextConnections.map((item) => item.fromNodeId)).toEqual([imageB.id, imageA.id]);
|
||||
const nextNodes = applyCanvasConnectionPromptSync(nodes, previousConnections, nodes, nextConnections);
|
||||
expect(nextNodes.find((node) => node.id === target.id)?.metadata?.composerContent).toBe("比较 @图片2 和 @图片1");
|
||||
});
|
||||
|
||||
test("配置节点承接的引用可排序且不改变主链和无关连线", () => {
|
||||
const imageA = imageNode("image-a");
|
||||
const imageB = imageNode("image-b");
|
||||
const target = videoNode("target");
|
||||
const config: CanvasNodeData = { id: "config", type: CanvasNodeType.Config, title: "config", position: { x: 0, y: 0 }, width: 100, height: 100, metadata: {} };
|
||||
const other = videoNode("other");
|
||||
const main = connection(target.id, config.id);
|
||||
const unrelated = connection(imageA.id, other.id);
|
||||
const previousConnections = [main, connection(imageA.id, config.id), unrelated, connection(imageB.id, config.id)];
|
||||
const nextConnections = reorderCanvasResourceConnections(target.id, [imageB.id, imageA.id], [imageA, imageB, target, config, other], previousConnections);
|
||||
expect(nextConnections).toEqual([main, connection(imageB.id, config.id), unrelated, connection(imageA.id, config.id)]);
|
||||
expect(nextConnections[0]).toBe(main);
|
||||
expect(nextConnections[2]).toBe(unrelated);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LocalDreaminaGenerationClientError, runLocalDreaminaGenerationTask, typ
|
||||
import { createGenerationBatchRetryContexts, createGenerationRetryContext, generationTaskMetadata, runBackendCanvasGenerationTask, runCanvasGenerationTaskToConsumer } from "../src/lib/canvas/canvas-project-generation";
|
||||
import { runCanvasAgentGenerationOps } from "../src/pages/canvas/use-canvas-agent-operations";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "../src/types/canvas";
|
||||
import { onlineToolToOps } from "../src/components/canvas/canvas-assistant-online-tools";
|
||||
import { buildToolAgentMessages, onlineToolToOps } from "../src/components/canvas/canvas-assistant-online-tools";
|
||||
import { generationTaskShowsProgress, generationTaskStageLabel, generationTaskStatusLabel } from "../src/lib/generation-task-display";
|
||||
import { generationErrorMessage } from "../src/lib/generation-error";
|
||||
import { generationTaskNodeId, syncGenerationTaskToCanvasStore } from "../src/lib/canvas/canvas-generation-task-sync";
|
||||
@@ -339,6 +339,58 @@ test("backend text generation forwards streaming callbacks and thinking options"
|
||||
expect(result).toEqual({ mode: "text", text: "完整正文", reasoning: "思考摘要" });
|
||||
});
|
||||
|
||||
test("online node retry creates a backend task and preserves retry identity in task metadata", async () => {
|
||||
let createdInput: Parameters<NonNullable<Parameters<typeof runBackendGenerationTask>[1]>["createTask"]>[0] | undefined;
|
||||
const running: GenerationTask = { id: "retry-task-new", type: "canvas_image", status: "running", prompt: "retry", attempts: 1, createdAt: "", updatedAt: "" };
|
||||
await runBackendGenerationTask(
|
||||
{
|
||||
projectId: "canvas-retry",
|
||||
mode: "image",
|
||||
prompt: "retry",
|
||||
config: backendModelConfig("retry-image-model"),
|
||||
clientOperationId: "retry:operation-0000000000000001",
|
||||
retryOf: "retry-task-old",
|
||||
attemptGroupId: "retry-group",
|
||||
},
|
||||
{
|
||||
createTask: async (input) => {
|
||||
createdInput = input;
|
||||
return running;
|
||||
},
|
||||
waitTask: async () => ({ ...running, status: "succeeded", resultJson: JSON.stringify({ mode: "image", images: [] }) }),
|
||||
runLocal: async () => { throw new Error("must not use local Runtime"); },
|
||||
createId: () => "unused-retry-id",
|
||||
now: () => "2026-09-10T00:00:00.000Z",
|
||||
},
|
||||
);
|
||||
expect(createdInput?.input?.metadata).toMatchObject({
|
||||
clientOperationId: "retry:operation-0000000000000001",
|
||||
retryOf: "retry-task-old",
|
||||
attemptGroupId: "retry-group",
|
||||
});
|
||||
});
|
||||
|
||||
test("online canvas Agent sends resource references instead of inline image bodies", async () => {
|
||||
const snapshot = { projectId: "canvas-agent", title: "Agent", nodes: [], connections: [], selectedNodeIds: [], viewport: { x: 0, y: 0, zoom: 1 } };
|
||||
const messages = await buildToolAgentMessages(snapshot, [], {
|
||||
id: "message",
|
||||
role: "user",
|
||||
text: "分析图片",
|
||||
references: [{ id: "image", type: CanvasNodeType.Image, title: "参考图", dataUrl: `data:image/png;base64,${"A".repeat(1024)}`, storageKey: "resource:image-remote" }],
|
||||
});
|
||||
const serialized = JSON.stringify(messages);
|
||||
expect(serialized).toContain("resource:image-remote");
|
||||
expect(serialized).not.toContain("data:image/png");
|
||||
expect(serialized.length).toBeLessThan(20_000);
|
||||
|
||||
await expect(buildToolAgentMessages(snapshot, [], {
|
||||
id: "local-message",
|
||||
role: "user",
|
||||
text: "分析本地图片",
|
||||
references: [{ id: "local-image", type: CanvasNodeType.Image, title: "未上传图片", dataUrl: "data:image/png;base64,AAAA" }],
|
||||
})).rejects.toThrow("引用图片尚未同步到服务器");
|
||||
});
|
||||
|
||||
test("the shared local generation entry projects pre-receipt work as submitting without fake progress", async () => {
|
||||
const updates: GenerationTask[] = [];
|
||||
let release!: () => void;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { ImageSizePicker } from "../src/components/image-size-picker";
|
||||
import { applyImageSizeSelection } from "../src/components/image-settings-panel";
|
||||
import { ImageSizePresetsEditor } from "../src/components/image-size-presets-editor";
|
||||
import { ModelCapabilityEditor } from "../src/components/model-capability-editor";
|
||||
import { defaultImageCapabilityConfig, normalizeModelCapabilityConfig } from "../src/lib/model-capabilities";
|
||||
@@ -10,6 +11,12 @@ import { resolveImageRequestSize, validateImageSize } from "../src/services/api/
|
||||
import { buildGeminiImageGenerationConfig } from "../src/lib/gemini-image";
|
||||
|
||||
describe("统一图片分辨率与宽高比", () => {
|
||||
test("比例协议切换 4K 时同时提交尺寸和真实质量档位", () => {
|
||||
const changes: Array<[string, string]> = [];
|
||||
applyImageSizeSelection((key, value) => changes.push([key, value]), "16:9", "high");
|
||||
expect(changes).toEqual([["size", "16:9"], ["quality", "high"]]);
|
||||
});
|
||||
|
||||
test("像素协议从旧比例配置切换 4K 后发送真实像素,不回退到 1K", () => {
|
||||
const profile = defaultImageCapabilityConfig();
|
||||
profile.size = { parameter: "size", values: ["16:9"], default: "16:9", allowCustom: true };
|
||||
|
||||
Reference in New Issue
Block a user