fix: centralize canvas agent pending requests
This commit is contained in:
parent
914e09af73
commit
b7ff4e68eb
@ -136,6 +136,17 @@ import {
|
|||||||
mergePendingCanvasAgentTargetNodeIds,
|
mergePendingCanvasAgentTargetNodeIds,
|
||||||
resolvePendingCanvasAgentTargetNode,
|
resolvePendingCanvasAgentTargetNode,
|
||||||
} from "@/components/canvas/chat-panel/canvas-agent-targets";
|
} from "@/components/canvas/chat-panel/canvas-agent-targets";
|
||||||
|
import {
|
||||||
|
CANVAS_AGENT_ANNOTATION_EDIT_EVENT,
|
||||||
|
CANVAS_AGENT_SLOT_FILL_EVENT,
|
||||||
|
consumeCanvasAgentAnnotationEditRequest,
|
||||||
|
consumeCanvasAgentSlotFillRequest,
|
||||||
|
getPendingCanvasAgentAnnotationEditRequest,
|
||||||
|
getPendingCanvasAgentSlotFillRequest,
|
||||||
|
type CanvasAgentPendingRequestWindow,
|
||||||
|
type PendingCanvasAgentAnnotationEditRequest,
|
||||||
|
type PendingCanvasAgentSlotFillRequest,
|
||||||
|
} from "@/lib/canvas/agent-pending-requests";
|
||||||
import {
|
import {
|
||||||
type DetailArchive,
|
type DetailArchive,
|
||||||
type DetailBriefForm,
|
type DetailBriefForm,
|
||||||
@ -3594,19 +3605,7 @@ export function CanvasChatPanel({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!effectiveAgentEnabled || typeof window === "undefined") return;
|
if (!effectiveAgentEnabled || typeof window === "undefined") return;
|
||||||
type PendingCanvasAgentSlotFillRequest = {
|
const targetWindow = window as CanvasAgentPendingRequestWindow;
|
||||||
nodeId?: string;
|
|
||||||
label?: string;
|
|
||||||
};
|
|
||||||
type PendingCanvasAgentAnnotationEditRequest = {
|
|
||||||
nodeId?: string;
|
|
||||||
title?: string;
|
|
||||||
annotationCount?: number;
|
|
||||||
};
|
|
||||||
const targetWindow = window as Window & {
|
|
||||||
__canvasAgentPendingSlotFillRequest?: PendingCanvasAgentSlotFillRequest;
|
|
||||||
__canvasAgentPendingAnnotationEditRequest?: PendingCanvasAgentAnnotationEditRequest;
|
|
||||||
};
|
|
||||||
|
|
||||||
const focusComposerWithPrompt = (prompt: string) => {
|
const focusComposerWithPrompt = (prompt: string) => {
|
||||||
handleSelectGenerationMode("agent");
|
handleSelectGenerationMode("agent");
|
||||||
@ -3630,11 +3629,8 @@ export function CanvasChatPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAgentSlotFillRequest = (event: Event) => {
|
const handleAgentSlotFillRequest = (event: Event) => {
|
||||||
const detail = (event as CustomEvent<{ nodeId?: string; label?: string }>).detail;
|
const detail = (event as CustomEvent<PendingCanvasAgentSlotFillRequest>).detail;
|
||||||
const pending = targetWindow.__canvasAgentPendingSlotFillRequest;
|
consumeCanvasAgentSlotFillRequest(targetWindow, detail);
|
||||||
if (!pending?.nodeId || pending.nodeId === detail?.nodeId) {
|
|
||||||
delete targetWindow.__canvasAgentPendingSlotFillRequest;
|
|
||||||
}
|
|
||||||
const label = String(detail?.label || "这个槽位").trim();
|
const label = String(detail?.label || "这个槽位").trim();
|
||||||
const prompt = `在${label === "这个槽位" ? label : `「${label}」`}生成一张图`;
|
const prompt = `在${label === "这个槽位" ? label : `「${label}」`}生成一张图`;
|
||||||
|
|
||||||
@ -3644,17 +3640,9 @@ export function CanvasChatPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAgentAnnotationEditRequest = (event: Event) => {
|
const handleAgentAnnotationEditRequest = (event: Event) => {
|
||||||
const detail = (
|
const detail = (event as CustomEvent<PendingCanvasAgentAnnotationEditRequest>)
|
||||||
event as CustomEvent<{
|
.detail;
|
||||||
nodeId?: string;
|
consumeCanvasAgentAnnotationEditRequest(targetWindow, detail);
|
||||||
title?: string;
|
|
||||||
annotationCount?: number;
|
|
||||||
}>
|
|
||||||
).detail;
|
|
||||||
const pending = targetWindow.__canvasAgentPendingAnnotationEditRequest;
|
|
||||||
if (!pending?.nodeId || pending.nodeId === detail?.nodeId) {
|
|
||||||
delete targetWindow.__canvasAgentPendingAnnotationEditRequest;
|
|
||||||
}
|
|
||||||
const nodeId = String(detail?.nodeId || "").trim();
|
const nodeId = String(detail?.nodeId || "").trim();
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
pendingCanvasAgentAnnotationEditTargetRef.current = {
|
pendingCanvasAgentAnnotationEditTargetRef.current = {
|
||||||
@ -3674,37 +3662,37 @@ export function CanvasChatPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"canvas-agent-slot-fill-request",
|
CANVAS_AGENT_SLOT_FILL_EVENT,
|
||||||
handleAgentSlotFillRequest
|
handleAgentSlotFillRequest
|
||||||
);
|
);
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"canvas-agent-annotation-edit-request",
|
CANVAS_AGENT_ANNOTATION_EDIT_EVENT,
|
||||||
handleAgentAnnotationEditRequest
|
handleAgentAnnotationEditRequest
|
||||||
);
|
);
|
||||||
const pendingSlotFillRequest = targetWindow.__canvasAgentPendingSlotFillRequest;
|
const pendingSlotFillRequest = getPendingCanvasAgentSlotFillRequest(targetWindow);
|
||||||
if (pendingSlotFillRequest) {
|
if (pendingSlotFillRequest) {
|
||||||
handleAgentSlotFillRequest(
|
handleAgentSlotFillRequest(
|
||||||
new CustomEvent("canvas-agent-slot-fill-request", {
|
new CustomEvent(CANVAS_AGENT_SLOT_FILL_EVENT, {
|
||||||
detail: pendingSlotFillRequest,
|
detail: pendingSlotFillRequest,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const pendingAnnotationEditRequest =
|
const pendingAnnotationEditRequest =
|
||||||
targetWindow.__canvasAgentPendingAnnotationEditRequest;
|
getPendingCanvasAgentAnnotationEditRequest(targetWindow);
|
||||||
if (pendingAnnotationEditRequest) {
|
if (pendingAnnotationEditRequest) {
|
||||||
handleAgentAnnotationEditRequest(
|
handleAgentAnnotationEditRequest(
|
||||||
new CustomEvent("canvas-agent-annotation-edit-request", {
|
new CustomEvent(CANVAS_AGENT_ANNOTATION_EDIT_EVENT, {
|
||||||
detail: pendingAnnotationEditRequest,
|
detail: pendingAnnotationEditRequest,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
"canvas-agent-slot-fill-request",
|
CANVAS_AGENT_SLOT_FILL_EVENT,
|
||||||
handleAgentSlotFillRequest
|
handleAgentSlotFillRequest
|
||||||
);
|
);
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
"canvas-agent-annotation-edit-request",
|
CANVAS_AGENT_ANNOTATION_EDIT_EVENT,
|
||||||
handleAgentAnnotationEditRequest
|
handleAgentAnnotationEditRequest
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -60,6 +60,12 @@ import {
|
|||||||
LOCAL_UPLOAD_PENDING_METADATA_KEY,
|
LOCAL_UPLOAD_PENDING_METADATA_KEY,
|
||||||
LOCAL_UPLOAD_PREVIEW_METADATA_KEY,
|
LOCAL_UPLOAD_PREVIEW_METADATA_KEY,
|
||||||
} from "@/lib/canvas/local-upload-state";
|
} from "@/lib/canvas/local-upload-state";
|
||||||
|
import {
|
||||||
|
dispatchCanvasAgentOpenChatPanelRequest,
|
||||||
|
dispatchCanvasAgentSlotFillRequest,
|
||||||
|
storeCanvasAgentSlotFillRequest,
|
||||||
|
type CanvasAgentPendingRequestWindow,
|
||||||
|
} from "@/lib/canvas/agent-pending-requests";
|
||||||
import { inferOriginalImageUrl } from "@/lib/image-url";
|
import { inferOriginalImageUrl } from "@/lib/image-url";
|
||||||
|
|
||||||
const CANVAS_NO_DRAG_SELECTOR =
|
const CANVAS_NO_DRAG_SELECTOR =
|
||||||
@ -691,24 +697,14 @@ export function CanvasWorkspace({
|
|||||||
nodeId: agentSlotContextMenu.nodeId,
|
nodeId: agentSlotContextMenu.nodeId,
|
||||||
label: agentSlotContextMenu.label,
|
label: agentSlotContextMenu.label,
|
||||||
};
|
};
|
||||||
const targetWindow = window as Window & {
|
const targetWindow = window as CanvasAgentPendingRequestWindow;
|
||||||
__canvasAgentPendingSlotFillRequest?: typeof detail;
|
|
||||||
};
|
|
||||||
selectNode(agentSlotContextMenu.nodeId, false);
|
selectNode(agentSlotContextMenu.nodeId, false);
|
||||||
targetWindow.__canvasAgentPendingSlotFillRequest = detail;
|
storeCanvasAgentSlotFillRequest(targetWindow, detail);
|
||||||
targetWindow.dispatchEvent(
|
dispatchCanvasAgentOpenChatPanelRequest(targetWindow, {
|
||||||
new CustomEvent("canvas-open-chat-panel-request", {
|
reason: "agent-slot-fill",
|
||||||
detail: {
|
nodeId: detail.nodeId,
|
||||||
reason: "agent-slot-fill",
|
});
|
||||||
nodeId: detail.nodeId,
|
dispatchCanvasAgentSlotFillRequest(targetWindow, detail);
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
targetWindow.dispatchEvent(
|
|
||||||
new CustomEvent("canvas-agent-slot-fill-request", {
|
|
||||||
detail,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setAgentSlotContextMenu(null);
|
setAgentSlotContextMenu(null);
|
||||||
}, [agentSlotContextMenu, selectNode]);
|
}, [agentSlotContextMenu, selectNode]);
|
||||||
|
|
||||||
|
|||||||
@ -125,6 +125,12 @@ import {
|
|||||||
syncCanvasLocalEditRegionAnnotationsMetadata,
|
syncCanvasLocalEditRegionAnnotationsMetadata,
|
||||||
syncCanvasNodeNoteAnnotationMetadata,
|
syncCanvasNodeNoteAnnotationMetadata,
|
||||||
} from "@/lib/canvas/agent-annotation-metadata";
|
} from "@/lib/canvas/agent-annotation-metadata";
|
||||||
|
import {
|
||||||
|
dispatchCanvasAgentAnnotationEditRequest,
|
||||||
|
dispatchCanvasAgentOpenChatPanelRequest,
|
||||||
|
storeCanvasAgentAnnotationEditRequest,
|
||||||
|
type CanvasAgentPendingRequestWindow,
|
||||||
|
} from "@/lib/canvas/agent-pending-requests";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -6685,25 +6691,15 @@ export function ImageNode({
|
|||||||
title: displayTitle,
|
title: displayTitle,
|
||||||
annotationCount: canvasAgentAnnotations.length,
|
annotationCount: canvasAgentAnnotations.length,
|
||||||
};
|
};
|
||||||
const targetWindow = window as Window & {
|
const targetWindow = window as CanvasAgentPendingRequestWindow;
|
||||||
__canvasAgentPendingAnnotationEditRequest?: typeof detail;
|
|
||||||
};
|
|
||||||
|
|
||||||
selectNode(node.id, false);
|
selectNode(node.id, false);
|
||||||
targetWindow.__canvasAgentPendingAnnotationEditRequest = detail;
|
storeCanvasAgentAnnotationEditRequest(targetWindow, detail);
|
||||||
targetWindow.dispatchEvent(
|
dispatchCanvasAgentOpenChatPanelRequest(targetWindow, {
|
||||||
new CustomEvent("canvas-open-chat-panel-request", {
|
reason: "agent-annotation-edit",
|
||||||
detail: {
|
nodeId: detail.nodeId,
|
||||||
reason: "agent-annotation-edit",
|
});
|
||||||
nodeId: detail.nodeId,
|
dispatchCanvasAgentAnnotationEditRequest(targetWindow, detail);
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
targetWindow.dispatchEvent(
|
|
||||||
new CustomEvent("canvas-agent-annotation-edit-request", {
|
|
||||||
detail,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [
|
}, [
|
||||||
canvasAgentAnnotations.length,
|
canvasAgentAnnotations.length,
|
||||||
displayTitle,
|
displayTitle,
|
||||||
|
|||||||
103
lib/canvas/agent-pending-requests.ts
Normal file
103
lib/canvas/agent-pending-requests.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
export type PendingCanvasAgentSlotFillRequest = {
|
||||||
|
nodeId?: string;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PendingCanvasAgentAnnotationEditRequest = {
|
||||||
|
nodeId?: string;
|
||||||
|
title?: string;
|
||||||
|
annotationCount?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CanvasAgentPendingRequestWindow = Window & {
|
||||||
|
__canvasAgentPendingSlotFillRequest?: PendingCanvasAgentSlotFillRequest;
|
||||||
|
__canvasAgentPendingAnnotationEditRequest?: PendingCanvasAgentAnnotationEditRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CANVAS_AGENT_OPEN_CHAT_PANEL_EVENT = "canvas-open-chat-panel-request";
|
||||||
|
export const CANVAS_AGENT_SLOT_FILL_EVENT = "canvas-agent-slot-fill-request";
|
||||||
|
export const CANVAS_AGENT_ANNOTATION_EDIT_EVENT =
|
||||||
|
"canvas-agent-annotation-edit-request";
|
||||||
|
|
||||||
|
function normalizeNodeId(value: unknown) {
|
||||||
|
return String(value || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storeCanvasAgentSlotFillRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow,
|
||||||
|
request: PendingCanvasAgentSlotFillRequest
|
||||||
|
) {
|
||||||
|
targetWindow.__canvasAgentPendingSlotFillRequest = { ...request };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPendingCanvasAgentSlotFillRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow
|
||||||
|
) {
|
||||||
|
return targetWindow.__canvasAgentPendingSlotFillRequest || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function consumeCanvasAgentSlotFillRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow,
|
||||||
|
detail?: PendingCanvasAgentSlotFillRequest | null
|
||||||
|
) {
|
||||||
|
const pending = getPendingCanvasAgentSlotFillRequest(targetWindow);
|
||||||
|
if (!pending) return null;
|
||||||
|
const pendingNodeId = normalizeNodeId(pending.nodeId);
|
||||||
|
const detailNodeId = normalizeNodeId(detail?.nodeId);
|
||||||
|
if (!pendingNodeId || pendingNodeId === detailNodeId) {
|
||||||
|
delete targetWindow.__canvasAgentPendingSlotFillRequest;
|
||||||
|
}
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storeCanvasAgentAnnotationEditRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow,
|
||||||
|
request: PendingCanvasAgentAnnotationEditRequest
|
||||||
|
) {
|
||||||
|
targetWindow.__canvasAgentPendingAnnotationEditRequest = { ...request };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPendingCanvasAgentAnnotationEditRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow
|
||||||
|
) {
|
||||||
|
return targetWindow.__canvasAgentPendingAnnotationEditRequest || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function consumeCanvasAgentAnnotationEditRequest(
|
||||||
|
targetWindow: CanvasAgentPendingRequestWindow,
|
||||||
|
detail?: PendingCanvasAgentAnnotationEditRequest | null
|
||||||
|
) {
|
||||||
|
const pending = getPendingCanvasAgentAnnotationEditRequest(targetWindow);
|
||||||
|
if (!pending) return null;
|
||||||
|
const pendingNodeId = normalizeNodeId(pending.nodeId);
|
||||||
|
const detailNodeId = normalizeNodeId(detail?.nodeId);
|
||||||
|
if (!pendingNodeId || pendingNodeId === detailNodeId) {
|
||||||
|
delete targetWindow.__canvasAgentPendingAnnotationEditRequest;
|
||||||
|
}
|
||||||
|
return pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispatchCanvasAgentOpenChatPanelRequest(
|
||||||
|
targetWindow: Window,
|
||||||
|
detail: { reason: "agent-slot-fill" | "agent-annotation-edit"; nodeId?: string }
|
||||||
|
) {
|
||||||
|
targetWindow.dispatchEvent(
|
||||||
|
new CustomEvent(CANVAS_AGENT_OPEN_CHAT_PANEL_EVENT, { detail })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispatchCanvasAgentSlotFillRequest(
|
||||||
|
targetWindow: Window,
|
||||||
|
detail: PendingCanvasAgentSlotFillRequest
|
||||||
|
) {
|
||||||
|
targetWindow.dispatchEvent(new CustomEvent(CANVAS_AGENT_SLOT_FILL_EVENT, { detail }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispatchCanvasAgentAnnotationEditRequest(
|
||||||
|
targetWindow: Window,
|
||||||
|
detail: PendingCanvasAgentAnnotationEditRequest
|
||||||
|
) {
|
||||||
|
targetWindow.dispatchEvent(
|
||||||
|
new CustomEvent(CANVAS_AGENT_ANNOTATION_EDIT_EVENT, { detail })
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -16,6 +16,15 @@ import {
|
|||||||
mergePendingCanvasAgentTargetNodeIds,
|
mergePendingCanvasAgentTargetNodeIds,
|
||||||
resolvePendingCanvasAgentTargetNode,
|
resolvePendingCanvasAgentTargetNode,
|
||||||
} from "@/components/canvas/chat-panel/canvas-agent-targets";
|
} from "@/components/canvas/chat-panel/canvas-agent-targets";
|
||||||
|
import {
|
||||||
|
consumeCanvasAgentAnnotationEditRequest,
|
||||||
|
consumeCanvasAgentSlotFillRequest,
|
||||||
|
getPendingCanvasAgentAnnotationEditRequest,
|
||||||
|
getPendingCanvasAgentSlotFillRequest,
|
||||||
|
storeCanvasAgentAnnotationEditRequest,
|
||||||
|
storeCanvasAgentSlotFillRequest,
|
||||||
|
type CanvasAgentPendingRequestWindow,
|
||||||
|
} from "@/lib/canvas/agent-pending-requests";
|
||||||
import {
|
import {
|
||||||
writeCanvasMediaChunk,
|
writeCanvasMediaChunk,
|
||||||
writeCanvasOperationChunk,
|
writeCanvasOperationChunk,
|
||||||
@ -2675,6 +2684,51 @@ test.describe("canvas chat pure helpers", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps pending canvas agent UI requests until the matching panel event consumes them", () => {
|
||||||
|
const targetWindow = {} as CanvasAgentPendingRequestWindow;
|
||||||
|
const slotRequest = {
|
||||||
|
nodeId: "slot-1",
|
||||||
|
label: "主图槽",
|
||||||
|
};
|
||||||
|
const annotationRequest = {
|
||||||
|
nodeId: "image-annotated",
|
||||||
|
title: "白底主图",
|
||||||
|
annotationCount: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
storeCanvasAgentSlotFillRequest(targetWindow, slotRequest);
|
||||||
|
storeCanvasAgentAnnotationEditRequest(targetWindow, annotationRequest);
|
||||||
|
|
||||||
|
expect(getPendingCanvasAgentSlotFillRequest(targetWindow)).toEqual(slotRequest);
|
||||||
|
expect(getPendingCanvasAgentAnnotationEditRequest(targetWindow)).toEqual(
|
||||||
|
annotationRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
consumeCanvasAgentSlotFillRequest(targetWindow, { nodeId: "other-slot" })
|
||||||
|
).toEqual(slotRequest);
|
||||||
|
expect(
|
||||||
|
consumeCanvasAgentAnnotationEditRequest(targetWindow, {
|
||||||
|
nodeId: "other-image",
|
||||||
|
})
|
||||||
|
).toEqual(annotationRequest);
|
||||||
|
expect(getPendingCanvasAgentSlotFillRequest(targetWindow)).toEqual(slotRequest);
|
||||||
|
expect(getPendingCanvasAgentAnnotationEditRequest(targetWindow)).toEqual(
|
||||||
|
annotationRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
consumeCanvasAgentSlotFillRequest(targetWindow, { nodeId: "slot-1" })
|
||||||
|
).toEqual(slotRequest);
|
||||||
|
expect(
|
||||||
|
consumeCanvasAgentAnnotationEditRequest(targetWindow, {
|
||||||
|
nodeId: "image-annotated",
|
||||||
|
})
|
||||||
|
).toEqual(annotationRequest);
|
||||||
|
expect(getPendingCanvasAgentSlotFillRequest(targetWindow)).toBeNull();
|
||||||
|
expect(getPendingCanvasAgentAnnotationEditRequest(targetWindow)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test("keeps pending annotation edit targets eligible for the next canvas context", () => {
|
test("keeps pending annotation edit targets eligible for the next canvas context", () => {
|
||||||
const nodes = [
|
const nodes = [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user