fix: filter handled canvas media batches
This commit is contained in:
parent
cb44b2237f
commit
6d5604a84b
@ -133,6 +133,7 @@ import {
|
||||
type CanvasOperationStreamPayload,
|
||||
type CanvasViewOperationStreamPayload,
|
||||
type CanvasMediaStreamPayload,
|
||||
filterCanvasMediaPayloadForUnhandledItems,
|
||||
getAgentProgressFallbackTitle,
|
||||
getAgentTargetLockTitle,
|
||||
getAgentTargetResolutionLabel,
|
||||
@ -4461,36 +4462,44 @@ export function CanvasChatPanel({
|
||||
|
||||
if (dataPart.type === "data-canvasMedia") {
|
||||
try {
|
||||
const payload: CanvasMediaStreamPayload =
|
||||
typeof dataPart.data === "string" ? JSON.parse(dataPart.data) : dataPart.data;
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
const handledIds = getCanvasMediaPayloadHandledItemIds(payload);
|
||||
handledIds.forEach((id) => handledCanvasMediaItemIdsRef.current.add(id));
|
||||
if (
|
||||
shouldSkipCanvasMediaPayloadForHandledItems({
|
||||
payload,
|
||||
handledCanvasMediaItemIds: handledCanvasMediaItemIdsRef.current,
|
||||
})
|
||||
) {
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
return;
|
||||
}
|
||||
const imageUrls = Array.isArray(payload.imageUrls)
|
||||
? payload.imageUrls.filter(
|
||||
const payload: CanvasMediaStreamPayload =
|
||||
typeof dataPart.data === "string" ? JSON.parse(dataPart.data) : dataPart.data;
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
const handledIds = getCanvasMediaPayloadHandledItemIds(payload);
|
||||
handledIds.forEach((id) => handledCanvasMediaItemIdsRef.current.add(id));
|
||||
const unhandledPayload = filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload,
|
||||
handledCanvasMediaItemIds: handledCanvasMediaItemIdsRef.current,
|
||||
});
|
||||
if (
|
||||
shouldSkipCanvasMediaPayloadForHandledItems({
|
||||
payload: unhandledPayload,
|
||||
handledCanvasMediaItemIds: handledCanvasMediaItemIdsRef.current,
|
||||
})
|
||||
) {
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
return;
|
||||
}
|
||||
const imageUrls = Array.isArray(unhandledPayload.imageUrls)
|
||||
? unhandledPayload.imageUrls.filter(
|
||||
(url): url is string =>
|
||||
typeof url === "string" && /^https?:\/\//i.test(url)
|
||||
)
|
||||
: [];
|
||||
const videoUrls = Array.isArray(payload.videoUrls)
|
||||
? payload.videoUrls.filter(
|
||||
const videoUrls = Array.isArray(unhandledPayload.videoUrls)
|
||||
? unhandledPayload.videoUrls.filter(
|
||||
(url): url is string =>
|
||||
typeof url === "string" && /^https?:\/\//i.test(url)
|
||||
)
|
||||
: [];
|
||||
if (imageUrls.length === 0 && videoUrls.length === 0) {
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
return;
|
||||
}
|
||||
if (isEventForActiveChat && (imageUrls.length > 0 || videoUrls.length > 0)) {
|
||||
const allUrls = [...imageUrls, ...videoUrls];
|
||||
const fallbackMessageId =
|
||||
String(payload.messageId || "").trim() ||
|
||||
String(unhandledPayload.messageId || "").trim() ||
|
||||
buildCanvasMediaFallbackMessageId(eventChatId, allUrls);
|
||||
const mediaMarkdown = buildCanvasMediaMarkdown({ imageUrls, videoUrls });
|
||||
const mediaUrlKeys = new Set(
|
||||
@ -4546,7 +4555,7 @@ export function CanvasChatPanel({
|
||||
]);
|
||||
});
|
||||
}
|
||||
void appendStreamMediaToCanvas(payload).finally(() => {
|
||||
void appendStreamMediaToCanvas(unhandledPayload).finally(() => {
|
||||
clearStreamImageGenerationStateForChat(eventChatId);
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@ -80,6 +80,49 @@ export function shouldSkipCanvasMediaPayloadForHandledItems(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCanvasStreamMediaUrls(value: unknown) {
|
||||
return Array.isArray(value)
|
||||
? value
|
||||
.map((url) => String(url || "").trim())
|
||||
.filter((url) => /^https?:\/\//i.test(url))
|
||||
: [];
|
||||
}
|
||||
|
||||
export function filterCanvasMediaPayloadForUnhandledItems(params: {
|
||||
payload: CanvasMediaStreamPayload;
|
||||
handledCanvasMediaItemIds: ReadonlySet<string>;
|
||||
}): CanvasMediaStreamPayload {
|
||||
const mediaItemIds = normalizeCanvasStreamIdList(params.payload.mediaItemIds);
|
||||
if (mediaItemIds.length === 0 || params.handledCanvasMediaItemIds.size === 0) {
|
||||
return params.payload;
|
||||
}
|
||||
|
||||
const imageUrls = normalizeCanvasStreamMediaUrls(params.payload.imageUrls);
|
||||
const videoUrls = normalizeCanvasStreamMediaUrls(params.payload.videoUrls);
|
||||
const mediaItems = [
|
||||
...imageUrls.map((url) => ({ type: "image" as const, url })),
|
||||
...videoUrls.map((url) => ({ type: "video" as const, url })),
|
||||
];
|
||||
if (mediaItems.length === 0 || mediaItems.length !== mediaItemIds.length) {
|
||||
return params.payload;
|
||||
}
|
||||
|
||||
const unhandledItems = mediaItems
|
||||
.map((item, index) => ({ ...item, mediaItemId: mediaItemIds[index]! }))
|
||||
.filter((item) => !params.handledCanvasMediaItemIds.has(item.mediaItemId));
|
||||
|
||||
return {
|
||||
...params.payload,
|
||||
imageUrls: unhandledItems
|
||||
.filter((item) => item.type === "image")
|
||||
.map((item) => item.url),
|
||||
videoUrls: unhandledItems
|
||||
.filter((item) => item.type === "video")
|
||||
.map((item) => item.url),
|
||||
mediaItemIds: unhandledItems.map((item) => item.mediaItemId),
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldRetryCanvasViewOperationAfterRefresh(
|
||||
payload: CanvasViewOperationStreamPayload,
|
||||
currentProjectId?: string
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
stripMediaMarkup,
|
||||
} from "@/components/canvas/chat-panel/canvas-chat-media";
|
||||
import {
|
||||
filterCanvasMediaPayloadForUnhandledItems,
|
||||
getCanvasMediaPayloadHandledItemIds,
|
||||
getCanvasOperationHandledMediaItemIds,
|
||||
isCanvasAgentViewOperationClientEnabled,
|
||||
@ -1885,6 +1886,19 @@ test.describe("canvas chat pure helpers", () => {
|
||||
handledCanvasMediaItemIds: handled,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload: {
|
||||
mediaItemIds: ["media-2", "media-3"],
|
||||
imageUrls: ["https://cdn.example.com/b.png", "https://cdn.example.com/c.png"],
|
||||
},
|
||||
handledCanvasMediaItemIds: handled,
|
||||
})
|
||||
).toMatchObject({
|
||||
mediaItemIds: ["media-3"],
|
||||
imageUrls: ["https://cdn.example.com/c.png"],
|
||||
videoUrls: [],
|
||||
});
|
||||
|
||||
const shadowHandledIds = getCanvasOperationHandledMediaItemIds({
|
||||
status: "shadowed",
|
||||
@ -1924,6 +1938,75 @@ test.describe("canvas chat pure helpers", () => {
|
||||
).toEqual(["media-4", "media-5"]);
|
||||
});
|
||||
|
||||
test("filters handled items out of partially applied canvasMedia batches", () => {
|
||||
const handled = new Set(["media-2"]);
|
||||
|
||||
expect(
|
||||
filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload: {
|
||||
mediaItemIds: ["media-1", "media-2", "media-3"],
|
||||
imageUrls: [
|
||||
"https://cdn.example.com/a.png",
|
||||
"https://cdn.example.com/b.png",
|
||||
"https://cdn.example.com/c.png",
|
||||
],
|
||||
prompt: "keep prompt",
|
||||
},
|
||||
handledCanvasMediaItemIds: handled,
|
||||
})
|
||||
).toMatchObject({
|
||||
mediaItemIds: ["media-1", "media-3"],
|
||||
imageUrls: [
|
||||
"https://cdn.example.com/a.png",
|
||||
"https://cdn.example.com/c.png",
|
||||
],
|
||||
videoUrls: [],
|
||||
prompt: "keep prompt",
|
||||
});
|
||||
|
||||
expect(
|
||||
filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload: {
|
||||
mediaItemIds: ["media-2"],
|
||||
imageUrls: ["https://cdn.example.com/b.png"],
|
||||
},
|
||||
handledCanvasMediaItemIds: handled,
|
||||
})
|
||||
).toMatchObject({
|
||||
mediaItemIds: [],
|
||||
imageUrls: [],
|
||||
videoUrls: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload: {
|
||||
mediaItemIds: ["media-image", "media-video"],
|
||||
imageUrls: ["https://cdn.example.com/image.png"],
|
||||
videoUrls: ["https://cdn.example.com/video.mp4"],
|
||||
},
|
||||
handledCanvasMediaItemIds: new Set(["media-image"]),
|
||||
})
|
||||
).toMatchObject({
|
||||
mediaItemIds: ["media-video"],
|
||||
imageUrls: [],
|
||||
videoUrls: ["https://cdn.example.com/video.mp4"],
|
||||
});
|
||||
|
||||
expect(
|
||||
filterCanvasMediaPayloadForUnhandledItems({
|
||||
payload: {
|
||||
mediaItemIds: ["media-1", "media-2"],
|
||||
imageUrls: ["https://cdn.example.com/a.png"],
|
||||
},
|
||||
handledCanvasMediaItemIds: handled,
|
||||
})
|
||||
).toMatchObject({
|
||||
mediaItemIds: ["media-1", "media-2"],
|
||||
imageUrls: ["https://cdn.example.com/a.png"],
|
||||
});
|
||||
});
|
||||
|
||||
test("writes applied, failed, and shadowed canvas operation stream chunks", () => {
|
||||
const chunks: Array<{ type: string; data: string }> = [];
|
||||
const writer = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user