fix: sync detail scale target flow

This commit is contained in:
134 2026-06-22 20:08:14 +08:00
parent bf8410a6d4
commit 088d2c1b74

View File

@ -28,7 +28,6 @@ import {
ImagePlus,
Loader2,
Maximize2,
Minimize2,
Sparkles,
Upload,
UserRound,
@ -247,7 +246,7 @@ type DetailResultLocalEditTarget = {
};
type DetailResultLocalEditMode = "local_edit" | "scale_adjust";
type DetailResultScalePercent = -30 | -20 | -10 | 10 | 20 | 30 | 50 | 100;
type DetailResultScalePercent = number;
type DetailResultLocalEditRect = {
x: number;
@ -258,12 +257,15 @@ type DetailResultLocalEditRect = {
type DetailResultLocalEditRegionHandle = "move" | "nw" | "ne" | "sw" | "se";
type DetailResultLocalEditRegionInteractionTarget = "source" | "scaleTarget";
type DetailResultLocalEditRegionInteraction = {
handle: DetailResultLocalEditRegionHandle;
pointerId: number;
regionId: string;
startPoint: { x: number; y: number };
startRect: DetailResultLocalEditRect;
target: DetailResultLocalEditRegionInteractionTarget;
};
type DetailResultLocalEditSubmitParams = {
@ -291,17 +293,9 @@ const MAX_AGENT_MODE_QUERY_LENGTH = 64;
const MAX_AGENT_MODE_REFERENCE_COUNT = 40;
const DETAIL_LOCAL_EDIT_REFERENCE_MAX_UPLOAD_BYTES = 30 * 1024 * 1024;
const REPLACEMENT_RERUN_MAX_REFERENCE_IMAGES = 4;
const DETAIL_SCALE_ADJUST_OPTIONS: DetailResultScalePercent[] = [
-30,
-20,
-10,
10,
20,
30,
50,
100,
];
const DETAIL_LOCAL_EDIT_MIN_RECT_RATIO = 0.025;
const DETAIL_SCALE_TARGET_MIN_PERCENT = -80;
const DETAIL_SCALE_TARGET_MAX_PERCENT = 200;
const DIRECT_MODEL_UPLOAD_SLOT_COUNT = 4;
const PRODUCT_DETAIL_CHANNEL_OPTIONS: Array<{
value: AgentWorkflowProductDetailChannel;
@ -852,18 +846,22 @@ function getDetailScaleAdjustLabel(scalePercent: DetailResultScalePercent) {
}
function getDetailScaleAdjustTargetLabel(scalePercent: DetailResultScalePercent) {
return `${Math.round((1 + scalePercent / 100) * 100)}%`;
return `${Math.max(5, Math.round((1 + scalePercent / 100) * 100))}%`;
}
function getDetailScaleAdjustRegionPrompt(scalePercent: DetailResultScalePercent | null) {
function getDetailScaleAdjustRegionPrompt(
scalePercent: DetailResultScalePercent | null,
hasTargetPosition = false
) {
if (!scalePercent) return "比例调整";
const actionLabel = getDetailScaleAdjustLabel(scalePercent);
const targetLabel = getDetailScaleAdjustTargetLabel(scalePercent);
const scaleVerb = scalePercent > 0 ? "放大" : "缩小";
return [
`对框选范围内的主物体执行等比${actionLabel},调整后主体视觉尺寸必须约为原来的 ${targetLabel},需要肉眼明显看出已经${scaleVerb}。`,
"必须锁定主体自身原始宽高比和透视形体,宽度与高度用同一个倍率同步变化;严禁宽度变窄但高度变高、严禁横向挤压或纵向拉伸。",
"框选区域只用于识别要调整的主体,不是放大后的边界;放大时主体可以自然超出原始框选范围,并由周边背景补全衔接。",
hasTargetPosition
? "主体框只用于识别要调整的主体;青色目标框表示调整后的目标大小和目标位置,放大后的主体可以自然超出主体框,并由周边背景补全衔接。"
: "框选区域只用于识别要调整的主体,不是放大后的边界;放大时主体可以自然超出原始框选范围,并由周边背景补全衔接。",
"只改变目标主体的尺寸,不改变主体外观、颜色、材质、图案和文字内容;不要把整个框选矩形当成图片块缩放。",
scalePercent < 0
? "主体缩小后露出的背景、桌面、墙面、阴影和遮挡关系必须自然补全。"
@ -871,6 +869,23 @@ function getDetailScaleAdjustRegionPrompt(scalePercent: DetailResultScalePercent
].join(" ");
}
function deriveDetailScalePercentFromRects(
sourceRect: DetailResultLocalEditRect,
targetRect: DetailResultLocalEditRect | null | undefined
) {
if (!targetRect || sourceRect.width <= 0 || sourceRect.height <= 0) {
return 0;
}
const widthRatio = Math.max(0.05, targetRect.width / sourceRect.width);
const heightRatio = Math.max(0.05, targetRect.height / sourceRect.height);
const uniformRatio = Math.sqrt(widthRatio * heightRatio);
const percent = Math.round((uniformRatio - 1) * 100);
return Math.min(
DETAIL_SCALE_TARGET_MAX_PERCENT,
Math.max(DETAIL_SCALE_TARGET_MIN_PERCENT, percent)
);
}
function DetailResultLocalEditDialog({
previewResult,
submitting,
@ -894,8 +909,7 @@ function DetailResultLocalEditDialog({
const [regionInteraction, setRegionInteraction] =
useState<DetailResultLocalEditRegionInteraction | null>(null);
const [uploadingRegionId, setUploadingRegionId] = useState<string | null>(null);
const [selectedScalePercent, setSelectedScalePercent] =
useState<DetailResultScalePercent | null>(null);
const [drawingTargetRegionId, setDrawingTargetRegionId] = useState<string | null>(null);
const dragStartRef = useRef<{ x: number; y: number } | null>(null);
const editSurfaceRef = useRef<HTMLDivElement | null>(null);
const mode: DetailResultLocalEditMode =
@ -906,6 +920,10 @@ function DetailResultLocalEditDialog({
? previewResult
: null;
const isBusy = submitting || Boolean(uploadingRegionId);
const pendingScaleTargetRegion = isScaleAdjustMode
? regions.find((region) => !region.targetRect)
: undefined;
const isDrawingScaleTarget = isScaleAdjustMode && Boolean(drawingTargetRegionId);
useEffect(() => {
setRegions([]);
@ -913,7 +931,7 @@ function DetailResultLocalEditDialog({
setActiveRegionId(null);
setRegionInteraction(null);
setUploadingRegionId(null);
setSelectedScalePercent(null);
setDrawingTargetRegionId(null);
dragStartRef.current = null;
}, [target?.cardId, target?.imageUrl, target?.mode]);
@ -970,15 +988,40 @@ function DetailResultLocalEditDialog({
}, []);
const updateRegionRect = useCallback(
(regionId: string, rect: DetailResultLocalEditRect) => {
(
regionId: string,
rect: DetailResultLocalEditRect,
targetKind: DetailResultLocalEditRegionInteractionTarget = "source"
) => {
const nextRect = clampRegionRect(rect);
setRegions((current) =>
current.map((region) =>
region.id === regionId ? { ...region, rect: nextRect } : region
region.id === regionId
? targetKind === "scaleTarget"
? {
...region,
targetRect: nextRect,
prompt: getDetailScaleAdjustRegionPrompt(
deriveDetailScalePercentFromRects(region.rect, nextRect),
true
),
}
: {
...region,
rect: nextRect,
prompt:
isScaleAdjustMode && region.targetRect
? getDetailScaleAdjustRegionPrompt(
deriveDetailScalePercentFromRects(nextRect, region.targetRect),
true
)
: region.prompt,
}
: region
)
);
},
[clampRegionRect]
[clampRegionRect, isScaleAdjustMode]
);
const calculateInteractionRect = useCallback(
@ -1138,7 +1181,10 @@ function DetailResultLocalEditDialog({
regions.map((region) => ({
...region,
prompt: isScaleAdjustMode
? getDetailScaleAdjustRegionPrompt(selectedScalePercent)
? getDetailScaleAdjustRegionPrompt(
deriveDetailScalePercentFromRects(region.rect, region.targetRect),
Boolean(region.targetRect)
)
: region.prompt.trim(),
referenceImages: (region.referenceImages || []).slice(
0,
@ -1160,8 +1206,18 @@ function DetailResultLocalEditDialog({
return;
}
if (isScaleAdjustMode) {
if (!selectedScalePercent) {
toast.error("请选择放大或缩小比例");
const missingTargetRegion = validRegions.find((region) => !region.targetRect);
if (missingTargetRegion) {
setActiveRegionId(missingTargetRegion.id);
toast.error(`请为区域 ${missingTargetRegion.index} 框选青色目标框`);
return;
}
const unchangedRegion = validRegions.find(
(region) => deriveDetailScalePercentFromRects(region.rect, region.targetRect) === 0
);
if (unchangedRegion) {
setActiveRegionId(unchangedRegion.id);
toast.error(`区域 ${unchangedRegion.index} 的目标框大小变化不明显,请重新框选`);
return;
}
} else {
@ -1175,11 +1231,17 @@ function DetailResultLocalEditDialog({
onSubmit({
imageUrl: target.imageUrl,
mode,
scalePercent: isScaleAdjustMode ? selectedScalePercent || undefined : undefined,
scalePercent:
isScaleAdjustMode && validRegions[0]
? deriveDetailScalePercentFromRects(
validRegions[0].rect,
validRegions[0].targetRect
) || undefined
: undefined,
title: target.title,
regions: validRegions,
});
}, [isScaleAdjustMode, mode, onSubmit, regions, selectedScalePercent, target, uploadingRegionId]);
}, [isScaleAdjustMode, mode, onSubmit, regions, target, uploadingRegionId]);
const completeDraftRegion = useCallback(
(event: React.PointerEvent<HTMLDivElement>) => {
@ -1188,8 +1250,10 @@ function DetailResultLocalEditDialog({
const point = getPoint(event);
const nextRect = createRectFromPoints(start, point);
const targetRegionId = drawingTargetRegionId;
dragStartRef.current = null;
setDraftRect(null);
setDrawingTargetRegionId(null);
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId);
@ -1203,13 +1267,32 @@ function DetailResultLocalEditDialog({
return;
}
if (isScaleAdjustMode && targetRegionId) {
setRegions((current) =>
reindexLocalEditRegions(
current.map((region) =>
region.id === targetRegionId
? {
...region,
targetRect: nextRect,
prompt: getDetailScaleAdjustRegionPrompt(
deriveDetailScalePercentFromRects(region.rect, nextRect),
true
),
}
: region
)
)
);
setActiveRegionId(targetRegionId);
return;
}
const nextRegion: LocalEditRegion = {
id: createLocalEditRegionId(),
index: regions.length + 1,
color: LOCAL_EDIT_COLORS[regions.length % LOCAL_EDIT_COLORS.length],
prompt: isScaleAdjustMode
? getDetailScaleAdjustRegionPrompt(selectedScalePercent)
: "",
prompt: isScaleAdjustMode ? getDetailScaleAdjustRegionPrompt(null) : "",
rect: nextRect,
referenceImages: [],
createdAt: Date.now(),
@ -1220,14 +1303,16 @@ function DetailResultLocalEditDialog({
});
setActiveRegionId(nextRegion.id);
},
[createRectFromPoints, getPoint, isScaleAdjustMode, regions.length, selectedScalePercent]
[createRectFromPoints, drawingTargetRegionId, getPoint, isScaleAdjustMode, regions.length]
);
const handleRegionInteractionStart = useCallback(
(
event: React.PointerEvent<HTMLButtonElement>,
region: LocalEditRegion,
fallbackHandle: DetailResultLocalEditRegionHandle = "move"
fallbackHandle: DetailResultLocalEditRegionHandle = "move",
targetKind: DetailResultLocalEditRegionInteractionTarget = "source",
targetRect?: DetailResultLocalEditRect | null
) => {
if (isBusy) return;
const targetElement = event.target as HTMLElement | null;
@ -1239,13 +1324,15 @@ function DetailResultLocalEditDialog({
setActiveRegionId(region.id);
dragStartRef.current = null;
setDraftRect(null);
setDrawingTargetRegionId(null);
event.currentTarget.setPointerCapture(event.pointerId);
setRegionInteraction({
handle,
pointerId: event.pointerId,
regionId: region.id,
startPoint: getPoint(event),
startRect: region.rect,
startRect: targetKind === "scaleTarget" ? targetRect || region.rect : region.rect,
target: targetKind,
});
},
[getPoint, isBusy]
@ -1262,7 +1349,11 @@ function DetailResultLocalEditDialog({
}
event.preventDefault();
event.stopPropagation();
updateRegionRect(regionId, calculateInteractionRect(regionInteraction, getPoint(event)));
updateRegionRect(
regionId,
calculateInteractionRect(regionInteraction, getPoint(event)),
regionInteraction.target
);
},
[calculateInteractionRect, getPoint, regionInteraction, updateRegionRect]
);
@ -1276,7 +1367,11 @@ function DetailResultLocalEditDialog({
) {
event.preventDefault();
event.stopPropagation();
updateRegionRect(regionId, calculateInteractionRect(regionInteraction, getPoint(event)));
updateRegionRect(
regionId,
calculateInteractionRect(regionInteraction, getPoint(event)),
regionInteraction.target
);
setRegionInteraction(null);
}
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
@ -1292,7 +1387,7 @@ function DetailResultLocalEditDialog({
: "处理完成后会加入 AI详情结果区。";
const dialogTitle = isScaleAdjustMode ? "比例调整详情图" : "局部编辑详情图";
const dialogDescription = isScaleAdjustMode
? "框选要调整比例的物体,选择固定放大或缩小幅度后生成。"
? "先框选要调整比例的物体,再框选目标大小和位置后生成。"
: "在图片上拖拽框选编辑位置,可添加多个区域,并为每个区域上传专属参考图。";
return (
@ -1329,24 +1424,30 @@ function DetailResultLocalEditDialog({
updateDraftRect(event);
dragStartRef.current = null;
setDraftRect(null);
setDrawingTargetRegionId(null);
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId);
}
}}
onPointerDown={(event) => {
if (event.button !== 0 || isBusy) return;
if (regions.length >= LOCAL_EDIT_MAX_REGIONS) {
toast.info(
`${isScaleAdjustMode ? "比例调整" : "局部编辑"}最多支持 ${LOCAL_EDIT_MAX_REGIONS} 个区域`
);
return;
}
onPointerDown={(event) => {
if (event.button !== 0 || isBusy) return;
const targetRegion = isScaleAdjustMode
? regions.find((region) => !region.targetRect)
: undefined;
if (!targetRegion && regions.length >= LOCAL_EDIT_MAX_REGIONS) {
toast.info(
`${isScaleAdjustMode ? "比例调整" : "局部编辑"}最多支持 ${LOCAL_EDIT_MAX_REGIONS} 个区域`
);
return;
}
event.preventDefault();
event.currentTarget.setPointerCapture(event.pointerId);
const point = getPoint(event);
dragStartRef.current = point;
setDraftRect({ x: point.x, y: point.y, width: 0.01, height: 0.01 });
}}
const point = getPoint(event);
dragStartRef.current = point;
setDrawingTargetRegionId(targetRegion?.id || null);
if (targetRegion) setActiveRegionId(targetRegion.id);
setDraftRect({ x: point.x, y: point.y, width: 0.01, height: 0.01 });
}}
onPointerMove={(event) => {
if (!dragStartRef.current) return;
event.preventDefault();
@ -1446,11 +1547,111 @@ function DetailResultLocalEditDialog({
))
: null}
</button>
);
})}
);
})}
{isScaleAdjustMode
? regions
.filter((region) => region.targetRect)
.map((region) => {
const targetRect = region.targetRect as DetailResultLocalEditRect;
const scalePercent = deriveDetailScalePercentFromRects(
region.rect,
targetRect
);
const isActive = region.id === activeRegionId;
return (
<button
className={cn(
"absolute z-20 rounded-sm border-2 border-dashed border-cyan-300 bg-cyan-300/10 text-left transition",
regionInteraction?.regionId === region.id &&
regionInteraction.target === "scaleTarget"
? "cursor-grabbing"
: "cursor-grab",
isActive ? "shadow-[0_0_0_3px_rgba(103,232,249,0.28)]" : ""
)}
key={`target-${region.id}`}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
setActiveRegionId(region.id);
}}
onPointerCancel={(event) =>
handleRegionInteractionEnd(event, region.id)
}
onPointerDown={(event) =>
handleRegionInteractionStart(
event,
region,
"move",
"scaleTarget",
targetRect
)
}
onPointerMove={(event) =>
handleRegionInteractionMove(event, region.id)
}
onPointerUp={(event) =>
handleRegionInteractionEnd(event, region.id)
}
style={{
left: `${targetRect.x * 100}%`,
top: `${targetRect.y * 100}%`,
width: `${targetRect.width * 100}%`,
height: `${targetRect.height * 100}%`,
}}
title={`目标框:${getDetailScaleAdjustLabel(scalePercent)}`}
type="button"
>
<span className="absolute -right-2 -top-2 rounded-full border border-cyan-200/70 bg-cyan-950/85 px-2 py-0.5 text-[11px] font-semibold text-cyan-50 shadow-lg">
目标 {scalePercent > 0 ? `+${scalePercent}%` : `${scalePercent}%`}
</span>
{isActive
? (
[
{
handle: "nw",
className:
"-left-[6px] -top-[6px] cursor-nwse-resize",
},
{
handle: "ne",
className:
"-right-[6px] -top-[6px] cursor-nesw-resize",
},
{
handle: "sw",
className:
"-bottom-[6px] -left-[6px] cursor-nesw-resize",
},
{
handle: "se",
className:
"-bottom-[6px] -right-[6px] cursor-nwse-resize",
},
] as const
).map((item) => (
<span
className={cn(
"absolute h-3 w-3 rounded-[2px] border-2 border-cyan-300 bg-white shadow-[0_3px_10px_rgba(0,0,0,0.28)]",
item.className
)}
data-detail-region-handle={item.handle}
key={item.handle}
/>
))
: null}
</button>
);
})
: null}
{draftRect ? (
<div
className="pointer-events-none absolute border-2 border-sky-300 bg-sky-400/20 shadow-[0_0_0_9999px_rgba(0,0,0,0.24)]"
className={cn(
"pointer-events-none absolute border-2 shadow-[0_0_0_9999px_rgba(0,0,0,0.24)]",
isDrawingScaleTarget
? "border-dashed border-cyan-300 bg-cyan-400/18"
: "border-sky-300 bg-sky-400/20"
)}
style={{
left: `${draftRect.x * 100}%`,
top: `${draftRect.y * 100}%`,
@ -1469,47 +1670,25 @@ function DetailResultLocalEditDialog({
) : null}
</div>
<div className="flex min-h-0 flex-col gap-3">
<div className="rounded-2xl border border-white/10 bg-white/[0.04] p-3 text-sm text-white/58">
{isScaleAdjustMode
? `拖拽框选物体,建议留出少量周边背景,最多 ${LOCAL_EDIT_MAX_REGIONS} 个区域。${completionCopy}`
: `拖拽图片添加区域。最多 ${LOCAL_EDIT_MAX_REGIONS} 个区域,每个区域最多 ${LOCAL_EDIT_MAX_REFERENCE_IMAGES_PER_REGION} 张参考图。${completionCopy}`}
</div>
{!activePreviewResult && isScaleAdjustMode ? (
<div className="rounded-2xl border border-white/10 bg-white/[0.035] p-3">
<div className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-white/38">
Scale
</div>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{DETAIL_SCALE_ADJUST_OPTIONS.map((option) => {
const active = selectedScalePercent === option;
const Icon = option > 0 ? Maximize2 : Minimize2;
const labelAction = option > 0 ? "放大" : "缩小";
const labelValue = `${Math.abs(option)}%`;
return (
<button
aria-label={getDetailScaleAdjustLabel(option)}
className={cn(
"grid h-12 grid-cols-[16px_minmax(0,1fr)] items-center gap-1 rounded-full border px-2 text-xs font-semibold leading-none transition disabled:cursor-not-allowed disabled:opacity-45",
active
? "border-emerald-300/70 bg-emerald-400/18 text-emerald-50"
: "border-white/10 bg-black/20 text-white/68 hover:border-white/24 hover:bg-white/[0.08] hover:text-white"
)}
disabled={isBusy}
key={option}
onClick={() => setSelectedScalePercent(option)}
type="button"
>
<Icon className="h-3.5 w-3.5 justify-self-center" />
<span className="flex min-w-0 flex-col items-center justify-center gap-0.5 text-center leading-[1.08]">
<span>{labelAction}</span>
<span>{labelValue}</span>
</span>
</button>
);
})}
</div>
</div>
) : null}
<div className="rounded-2xl border border-white/10 bg-white/[0.04] p-3 text-sm text-white/58">
{isScaleAdjustMode
? `先拖拽框选主体,再拖拽青色目标框设置大小和位置,最多 ${LOCAL_EDIT_MAX_REGIONS} 个区域。${completionCopy}`
: `拖拽图片添加区域。最多 ${LOCAL_EDIT_MAX_REGIONS} 个区域,每个区域最多 ${LOCAL_EDIT_MAX_REFERENCE_IMAGES_PER_REGION} 张参考图。${completionCopy}`}
</div>
{!activePreviewResult && isScaleAdjustMode ? (
<div className="rounded-2xl border border-white/10 bg-white/[0.035] p-3 text-sm text-white/58">
<div className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-white/38">
Target Box
</div>
<div className="leading-6">
{pendingScaleTargetRegion
? `继续在左侧为区域 ${pendingScaleTargetRegion.index} 拖拽青色目标框。`
: regions.length > 0
? "目标框已补齐,可拖动或拉伸青色框继续调整位置和大小。"
: "先在左侧拖拽框选要调整比例的主体。"}
</div>
</div>
) : null}
{activePreviewResult ? (
<div className="flex min-h-[260px] flex-col justify-center rounded-2xl border border-emerald-200/18 bg-emerald-400/[0.07] p-4">
<div className="mb-2 flex items-center gap-2 text-sm font-semibold text-emerald-50">
@ -1521,11 +1700,11 @@ function DetailResultLocalEditDialog({
</div>
</div>
) : regions.length === 0 ? (
<div className="flex min-h-[260px] items-center justify-center rounded-2xl border border-dashed border-white/12 bg-white/[0.02] px-5 text-center text-sm text-white/38">
{isScaleAdjustMode
? "在左侧图片上拖拽框选物体后,选择一个比例即可生成。"
: "在左侧图片上拖拽框选后,这里会显示区域提示词和参考图。"}
</div>
<div className="flex min-h-[260px] items-center justify-center rounded-2xl border border-dashed border-white/12 bg-white/[0.02] px-5 text-center text-sm text-white/38">
{isScaleAdjustMode
? "在左侧图片上先框主体,再框青色目标大小和位置。"
: "在左侧图片上拖拽框选后,这里会显示区域提示词和参考图。"}
</div>
) : (
<div className="max-h-[52vh] space-y-3 overflow-y-auto pr-1">
{regions.map((region) => {
@ -1556,12 +1735,17 @@ function DetailResultLocalEditDialog({
<span className="truncate text-sm font-semibold text-white">
区域 {region.index}
</span>
{isScaleAdjustMode ? (
<span className="text-xs text-white/36">
{selectedScalePercent
? getDetailScaleAdjustLabel(selectedScalePercent)
: "待选比例"}
</span>
{isScaleAdjustMode ? (
<span className="text-xs text-white/36">
{region.targetRect
? getDetailScaleAdjustLabel(
deriveDetailScalePercentFromRects(
region.rect,
region.targetRect
)
)
: "待框目标框"}
</span>
) : (
<span className="text-xs text-white/36">
参考图 {references.length}/
@ -1706,12 +1890,12 @@ function DetailResultLocalEditDialog({
</Button>
<Button
className="bg-white text-black hover:bg-white/90"
disabled={
submitting ||
Boolean(uploadingRegionId) ||
regions.length === 0 ||
(isScaleAdjustMode && !selectedScalePercent)
}
disabled={
submitting ||
Boolean(uploadingRegionId) ||
regions.length === 0 ||
(isScaleAdjustMode && regions.some((region) => !region.targetRect))
}
onClick={handleSubmit}
type="button"
>
@ -6334,6 +6518,7 @@ export function AgentGenerationWorkspaceDarkV2({
color: region.color,
prompt: region.prompt,
rect: region.rect,
targetRect: region.targetRect,
referenceImages: (region.referenceImages || []).map((reference) => ({
id: reference.id,
url: reference.url,