feat: refine canvas shortcuts and image candidate selection
This commit is contained in:
parent
d8af4bb48d
commit
e3f5b9720e
@ -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()
|
||||
|
||||
@ -1873,6 +1873,7 @@ export function CanvasChatPanel({
|
||||
const [isSessionMenuOpen, setIsSessionMenuOpen] = useState(false);
|
||||
const [isComposerModeMenuOpen, setIsComposerModeMenuOpen] = useState(false);
|
||||
const [selectedSkillTagId, setSelectedSkillTagId] = useState<string | null>(null);
|
||||
const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false);
|
||||
const [messageViewportKey, setMessageViewportKey] = useState(0);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(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 ? (
|
||||
<div className="mt-8 rounded-[26px] border border-[#e4e7ec] bg-white px-5 py-6 text-center dark:border-white/10 dark:bg-zinc-950/70">
|
||||
<h3 className="text-[16px] font-semibold leading-none text-[#1f2328] dark:text-zinc-100">
|
||||
预设快捷功能
|
||||
{canvasSkillTagsTitle}
|
||||
</h3>
|
||||
<div className="mt-6 flex flex-wrap justify-center gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"mt-6 flex flex-wrap justify-center gap-2 transition-[max-height] duration-200 ease-out",
|
||||
areCanvasSkillTagsExpanded
|
||||
? "max-h-none overflow-visible"
|
||||
: "max-h-[76px] overflow-hidden"
|
||||
)}
|
||||
>
|
||||
{canvasSkillTags.map((tag) => {
|
||||
const isActive = selectedSkillTag?.id === tag.id;
|
||||
const TagIcon = getCanvasSkillIconComponent(tag.iconName);
|
||||
@ -12177,6 +12186,22 @@ export function CanvasChatPanel({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{shouldShowCanvasSkillTagsToggle ? (
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 inline-flex h-7 items-center justify-center gap-1 rounded-full px-3 text-[12px] font-medium text-[#7b8494] transition-colors hover:bg-[#f4f5f7] hover:text-[#1f2328] dark:text-zinc-400 dark:hover:bg-white/10 dark:hover:text-zinc-100"
|
||||
onClick={() => setAreCanvasSkillTagsExpanded((expanded) => !expanded)}
|
||||
aria-expanded={areCanvasSkillTagsExpanded}
|
||||
>
|
||||
{areCanvasSkillTagsExpanded ? "收起" : "展开全部"}
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-3.5 w-3.5 transition-transform duration-200",
|
||||
areCanvasSkillTagsExpanded ? "rotate-180" : ""
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@ -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<typeof getScaleAdjustOutsidePixelChangeStats>
|
||||
>;
|
||||
}> = [];
|
||||
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: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user