From e3f5b9720e6bbedb78f5df6e96d84b22392a3c54 Mon Sep 17 00:00:00 2001 From: 134 Date: Fri, 26 Jun 2026 19:24:53 +0800 Subject: [PATCH] feat: refine canvas shortcuts and image candidate selection --- app/api/canvas/local-edit/route.ts | 2 +- components/canvas/CanvasChatPanel.tsx | 29 ++++++++++- lib/canvas/generate-image.ts | 71 +++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 530df3a5c..8a087308f 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -1395,7 +1395,7 @@ export async function POST(request: NextRequest) { sourceWidth, }, displayPrompt, - imageCount: 1, + imageCount: useFalScaleAdjustMockup ? 4 : 1, imageSize: typeof body.imageSize === "string" && body.imageSize.trim() ? body.imageSize.trim() diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index b05ee6812..ed546c782 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -1873,6 +1873,7 @@ export function CanvasChatPanel({ const [isSessionMenuOpen, setIsSessionMenuOpen] = useState(false); const [isComposerModeMenuOpen, setIsComposerModeMenuOpen] = useState(false); const [selectedSkillTagId, setSelectedSkillTagId] = useState(null); + const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false); const [messageViewportKey, setMessageViewportKey] = useState(0); const messagesEndRef = useRef(null); const fileInputRef = useRef(null); @@ -3794,6 +3795,7 @@ export function CanvasChatPanel({ () => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null, [canvasSkillTags, selectedSkillTagId] ); + const shouldShowCanvasSkillTagsToggle = canvasSkillTags.length > 4; const activeChatSession = useMemo( () => chatSessions.find((session) => session.id === activeChatId) || null, [activeChatId, chatSessions] @@ -12138,9 +12140,16 @@ export function CanvasChatPanel({ {canvasSkillTags.length > 0 ? (

- 预设快捷功能 + {canvasSkillTagsTitle}

-
+
{canvasSkillTags.map((tag) => { const isActive = selectedSkillTag?.id === tag.id; const TagIcon = getCanvasSkillIconComponent(tag.iconName); @@ -12177,6 +12186,22 @@ export function CanvasChatPanel({ ); })}
+ {shouldShowCanvasSkillTagsToggle ? ( + + ) : null}
) : null}
diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts index 11513fd90..5c9e9b5b4 100644 --- a/lib/canvas/generate-image.ts +++ b/lib/canvas/generate-image.ts @@ -973,7 +973,18 @@ async function applyScaleAdjustVerificationToImages(params: { return images; } - const processedImages = [...images]; + const evaluatedImages: Array<{ + changedInside: boolean; + cleanedImage?: ParsedImage; + diffScores: number[]; + image: ParsedImage; + index: number; + outsideChanged: boolean; + outsideDiffScores: number[]; + outsidePixelStats: Awaited< + ReturnType + >; + }> = []; for (const [index, image] of images.entries()) { const generatedUrl = image.originalUrl || image.thumbnailUrl; const generatedBuffer = await fetchImageBufferFromUrl(generatedUrl); @@ -1052,6 +1063,7 @@ async function applyScaleAdjustVerificationToImages(params: { }); } + let cleanedImage: ParsedImage | undefined; try { const cleanedImage = await removeScaleAdjustAnnotationArtifacts({ debugLabel, @@ -1063,7 +1075,17 @@ async function applyScaleAdjustVerificationToImages(params: { verification, }); if (cleanedImage) { - processedImages[index] = cleanedImage; + evaluatedImages.push({ + changedInside, + cleanedImage, + diffScores, + image, + index, + outsideChanged, + outsideDiffScores, + outsidePixelStats, + }); + continue; } } catch (cleanupError) { console.warn("[Canvas Generate Image] Failed to clean scale adjust annotation artifacts:", { @@ -1072,9 +1094,52 @@ async function applyScaleAdjustVerificationToImages(params: { imageIndex: index, }); } + + evaluatedImages.push({ + changedInside, + cleanedImage, + diffScores, + image, + index, + outsideChanged, + outsideDiffScores, + outsidePixelStats, + }); } - return processedImages; + if (evaluatedImages.length === 0) return images; + + const scoreEvaluation = (item: (typeof evaluatedImages)[number]) => { + const insideScore = Math.max(...item.diffScores, 0); + const outsideMean = item.outsidePixelStats.meanDiff; + const outsidePenalty = item.outsideChanged ? 100 : 0; + const noChangePenalty = item.changedInside ? 0 : 60; + return insideScore - outsideMean * 0.35 - outsidePenalty - noChangePenalty; + }; + const rankedImages = [...evaluatedImages].sort((left, right) => { + const leftPassed = left.changedInside && !left.outsideChanged; + const rightPassed = right.changedInside && !right.outsideChanged; + if (leftPassed !== rightPassed) return leftPassed ? -1 : 1; + return scoreEvaluation(right) - scoreEvaluation(left); + }); + const selected = rankedImages[0]; + + console.info("[Canvas Generate Image] Scale adjust candidate selected:", { + debugLabel, + candidateCount: evaluatedImages.length, + imageIndex: selected.index, + passed: selected.changedInside && !selected.outsideChanged, + changedInside: selected.changedInside, + outsideChanged: selected.outsideChanged, + regionDiffScores: selected.diffScores.map((score) => Number(score.toFixed(2))), + outsidePixelStats: { + changedRatio: Number(selected.outsidePixelStats.changedRatio.toFixed(4)), + meanDiff: Number(selected.outsidePixelStats.meanDiff.toFixed(2)), + }, + score: Number(scoreEvaluation(selected).toFixed(2)), + }); + + return [selected.cleanedImage || selected.image]; } function buildLocalEditCompositeMaskSvg(params: {