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}