diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 396bd7024..88ada89d4 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -6028,6 +6028,7 @@ export function AgentGenerationWorkspaceDarkV2({ }} sourceImageUrl={psdTemplateDialogSourceUrl} brandName={workflowContext?.brand || null} + sizeInfoText={workflowContext?.summaryText || null} templateMode={psdTemplateDialogMode} /> diff --git a/components/agents/AgentPsdTemplateEditorDialog.tsx b/components/agents/AgentPsdTemplateEditorDialog.tsx index 6c47ad86d..16dafcc30 100644 --- a/components/agents/AgentPsdTemplateEditorDialog.tsx +++ b/components/agents/AgentPsdTemplateEditorDialog.tsx @@ -564,12 +564,14 @@ export function AgentPsdTemplateEditorDialog({ onOpenChange, sourceImageUrl, brandName, + sizeInfoText, templateMode, }: { open: boolean; onOpenChange: (open: boolean) => void; sourceImageUrl: string | null; brandName?: string | null; + sizeInfoText?: string | null; templateMode?: AgentPsdTemplateMode | null; }) { const normalizedBrandName = String(brandName || "").trim(); @@ -2169,6 +2171,7 @@ export function AgentPsdTemplateEditorDialog({ imageAlt="PSD 模板导出结果" imageUrl={renderedResultUrl} key={`${selectedTemplate?.id || "template"}-${renderedResultUrl}`} + sizeInfoText={sizeInfoText} /> ) : selectedTextLayer ? (
diff --git a/components/agents/agent-psd-template-export-preview.tsx b/components/agents/agent-psd-template-export-preview.tsx index 42820d951..309b34912 100644 --- a/components/agents/agent-psd-template-export-preview.tsx +++ b/components/agents/agent-psd-template-export-preview.tsx @@ -21,6 +21,7 @@ type AgentPsdTemplateExportPreviewProps = { imageAlt?: string; enableDimensionAnnotations?: boolean; downloadName?: string; + sizeInfoText?: string | null; }; type ImageSize = { @@ -86,6 +87,15 @@ const DIMENSION_SUFFIX_REGEX = /-尺寸标注(?=\.)/; const DIMENSION_LINE_COLOR = "#666b73"; const DIMENSION_TEXT_COLOR = "#2a2f35"; const DIMENSION_TEXT_HALO_COLOR = "rgba(255,255,255,0.92)"; +const SIZE_INFO_LINE_REGEX = /尺寸信息[::]\s*([^\n]+)/i; +const NUMBER_REGEX = /\d+(?:\.\d+)?/g; + +type AutoDimensionValues = { + widthCm?: number; + depthCm?: number; + heightCm?: number; + rawValues: number[]; +}; function clamp(value: number, min: number, max: number) { return Math.max(min, Math.min(max, value)); @@ -132,13 +142,145 @@ function getDimensionLength(annotation: DimensionAnnotation) { ); } -function getDimensionLabel(annotation: DimensionAnnotation) { +function formatDimensionValue(value: number) { + return Number.isInteger(value) ? `${value}cm` : `${value.toFixed(1)}cm`; +} + +function extractAutoDimensionValues( + sizeInfoText: string | null | undefined +): AutoDimensionValues | null { + const normalizedText = String(sizeInfoText || "").trim(); + if (!normalizedText) { + return null; + } + + const matchedLine = + normalizedText.match(SIZE_INFO_LINE_REGEX)?.[1] || normalizedText; + const values = Array.from(matchedLine.matchAll(NUMBER_REGEX)) + .map((item) => Number(item[0])) + .filter((item) => Number.isFinite(item) && item > 0) + .slice(0, 3); + + if (values.length === 0) { + return null; + } + + if (values.length === 1) { + return { + heightCm: values[0], + rawValues: values, + widthCm: values[0], + }; + } + + if (values.length === 2) { + const [larger, smaller] = [...values].sort((left, right) => right - left); + return { + heightCm: larger, + rawValues: values, + widthCm: smaller, + }; + } + + const sortedValues = [...values].sort((left, right) => right - left); + const [heightCm, widthCm, depthCm] = sortedValues; + + return { + depthCm, + heightCm, + rawValues: values, + widthCm, + }; +} + +function getAnnotationOrientation(annotation: DimensionAnnotation) { + const deltaX = Math.abs(annotation.x2 - annotation.x1); + const deltaY = Math.abs(annotation.y2 - annotation.y1); + + if (deltaY > deltaX * 1.25) { + return "vertical"; + } + + if (deltaX > deltaY * 1.25) { + return "horizontal"; + } + + return "diagonal"; +} + +function getAutoDimensionLabel(params: { + annotation: DimensionAnnotation; + annotations: DimensionAnnotation[]; + autoDimensionValues?: AutoDimensionValues | null; +}) { + const { annotation, annotations, autoDimensionValues } = params; + if (!autoDimensionValues) { + return ""; + } + + if (autoDimensionValues.rawValues.length === 1) { + return formatDimensionValue(autoDimensionValues.rawValues[0]); + } + + const orientation = getAnnotationOrientation(annotation); + if (orientation === "vertical" && autoDimensionValues.heightCm) { + return formatDimensionValue(autoDimensionValues.heightCm); + } + + if (orientation === "diagonal" && autoDimensionValues.depthCm) { + return formatDimensionValue(autoDimensionValues.depthCm); + } + + if (orientation === "horizontal") { + const horizontalAnnotations = annotations + .filter((item) => getAnnotationOrientation(item) === "horizontal") + .sort( + (left, right) => getDimensionLength(right) - getDimensionLength(left) + ); + const currentIndex = horizontalAnnotations.findIndex( + (item) => item.id === annotation.id + ); + + if (currentIndex <= 0 && autoDimensionValues.widthCm) { + return formatDimensionValue(autoDimensionValues.widthCm); + } + + if (autoDimensionValues.depthCm) { + return formatDimensionValue(autoDimensionValues.depthCm); + } + + if (autoDimensionValues.widthCm) { + return formatDimensionValue(autoDimensionValues.widthCm); + } + } + + if (autoDimensionValues.widthCm) { + return formatDimensionValue(autoDimensionValues.widthCm); + } + + if (autoDimensionValues.heightCm) { + return formatDimensionValue(autoDimensionValues.heightCm); + } + + return ""; +} + +function getDimensionLabel(params: { + annotation: DimensionAnnotation; + annotations: DimensionAnnotation[]; + autoDimensionValues?: AutoDimensionValues | null; +}) { + const { annotation, annotations, autoDimensionValues } = params; const customLabel = String(annotation.label || "").trim(); if (customLabel) { return customLabel; } - return `${Math.max(1, Math.round(getDimensionLength(annotation)))} px`; + return getAutoDimensionLabel({ + annotation, + annotations, + autoDimensionValues, + }); } function mapImagePointToPreviewPoint( @@ -205,11 +347,20 @@ function getAnnotationVector(x1: number, y1: number, x2: number, y2: number) { }; } -function getPreviewAnnotationGeometry( - annotation: DimensionAnnotation, - previewRect: PreviewRect, - imageSize: ImageSize -) { +function getPreviewAnnotationGeometry(params: { + annotation: DimensionAnnotation; + annotations: DimensionAnnotation[]; + previewRect: PreviewRect; + imageSize: ImageSize; + autoDimensionValues?: AutoDimensionValues | null; +}) { + const { + annotation, + annotations, + previewRect, + imageSize, + autoDimensionValues, + } = params; const start = mapImagePointToPreviewPoint( { x: annotation.x1, y: annotation.y1 }, previewRect, @@ -221,7 +372,11 @@ function getPreviewAnnotationGeometry( imageSize ); const vector = getAnnotationVector(start.x, start.y, end.x, end.y); - const label = getDimensionLabel(annotation); + const label = getDimensionLabel({ + annotation, + annotations, + autoDimensionValues, + }); const labelOffset = 16; const rawAngleRadians = Math.atan2(vector.dy, vector.dx); const angleRadians = @@ -323,12 +478,20 @@ function drawDimensionCap(params: { context.stroke(); } -function drawAnnotationToCanvas( - context: CanvasRenderingContext2D, - annotation: DimensionAnnotation, - imageSize: ImageSize -) { - const label = getDimensionLabel(annotation); +function drawAnnotationToCanvas(params: { + context: CanvasRenderingContext2D; + annotation: DimensionAnnotation; + imageSize: ImageSize; + annotations: DimensionAnnotation[]; + autoDimensionValues?: AutoDimensionValues | null; +}) { + const { context, annotation, imageSize, annotations, autoDimensionValues } = + params; + const label = getDimensionLabel({ + annotation, + annotations, + autoDimensionValues, + }); const vector = getAnnotationVector( annotation.x1, annotation.y1, @@ -399,6 +562,7 @@ export function AgentPsdTemplateExportPreview({ imageAlt = "PSD 模板导出结果", enableDimensionAnnotations = false, downloadName, + sizeInfoText, }: AgentPsdTemplateExportPreviewProps) { const previewViewportRef = useRef(null); const dragStateRef = useRef(null); @@ -416,6 +580,10 @@ export function AgentPsdTemplateExportPreview({ () => buildImageProxyUrl(imageUrl), [imageUrl] ); + const autoDimensionValues = useMemo( + () => extractAutoDimensionValues(sizeInfoText), + [sizeInfoText] + ); const selectedAnnotation = useMemo( () => annotations.find( @@ -794,9 +962,15 @@ export function AgentPsdTemplateExportPreview({ context.drawImage(image, 0, 0, canvas.width, canvas.height); for (const annotation of annotations) { - drawAnnotationToCanvas(context, annotation, { - width: canvas.width, - height: canvas.height, + drawAnnotationToCanvas({ + annotation, + annotations, + autoDimensionValues, + context, + imageSize: { + width: canvas.width, + height: canvas.height, + }, }); } @@ -824,7 +998,13 @@ export function AgentPsdTemplateExportPreview({ } finally { setIsDownloading(false); } - }, [annotations, downloadName, enableDimensionAnnotations, imageUrl]); + }, [ + annotations, + autoDimensionValues, + downloadName, + enableDimensionAnnotations, + imageUrl, + ]); return (
@@ -915,11 +1095,13 @@ export function AgentPsdTemplateExportPreview({ > {annotations.map((annotation) => { const isSelected = annotation.id === selectedAnnotationId; - const geometry = getPreviewAnnotationGeometry( + const geometry = getPreviewAnnotationGeometry({ annotation, + annotations, + autoDimensionValues, + imageSize, previewRect, - imageSize - ); + }); const strokeWidth = isSelected ? 2 : 1.5; const tickLength = isSelected ? 8 : 6.5; @@ -1068,17 +1250,25 @@ export function AgentPsdTemplateExportPreview({
尺寸文字
- 留空时会自动显示像素长度,可手动改成真实尺寸。 + {autoDimensionValues + ? "留空时会优先使用商品尺寸信息自动显示 cm,可继续手动覆盖。" + : "当前没有拿到商品尺寸信息,请直接手动输入 cm 尺寸。"}
-
- 长度{" "} - {Math.max( - 1, - Math.round(getDimensionLength(selectedAnnotation)) - )}{" "} - px -
+ {getDimensionLabel({ + annotation: selectedAnnotation, + annotations, + autoDimensionValues, + }) ? ( +
+ 建议值{" "} + {getDimensionLabel({ + annotation: selectedAnnotation, + annotations, + autoDimensionValues, + })} +
+ ) : null}