allow agent multi image generation

This commit is contained in:
134 2026-07-08 14:37:07 +08:00
parent e0c277c76a
commit 599abc5021
4 changed files with 102 additions and 16 deletions

View File

@ -905,11 +905,17 @@ export const Response = ({
),
};
const renderImagePreview = (src: string, alt: string | undefined, key: string) => (
const renderImagePreview = (
src: string,
alt: string | undefined,
key: string,
compactGrid = false
) => (
<ImagePreview
key={key}
src={src}
alt={alt}
compactGrid={compactGrid}
onImageEdit={onImageEdit}
onDrawingComplete={onDrawingComplete}
onImageEnhance={onImageEnhance}
@ -925,6 +931,49 @@ export const Response = ({
/>
);
const renderImageGrid = (
images: Array<{ alt?: string; src: string }>,
keyPrefix: string
) => {
if (images.length === 0) return null;
if (images.length === 1) {
const image = images[0];
return renderImagePreview(
image.src,
image.alt,
`${image.src}-${processingKey}-${keyPrefix}-single`
);
}
const gridClassName =
images.length === 2
? "grid-cols-2 sm:max-w-[640px]"
: images.length === 3
? "grid-cols-2 sm:grid-cols-3 sm:max-w-[780px]"
: "grid-cols-2 sm:grid-cols-4 sm:max-w-[920px]";
return (
<div
className={cn(
"not-prose my-3 grid w-full gap-1.5 overflow-hidden rounded-lg",
gridClassName
)}
key={`${keyPrefix}-image-grid`}
>
{images.map((image, index) => (
<div className="min-w-0" key={`${image.src}-${index}`}>
{renderImagePreview(
image.src,
image.alt,
`${image.src}-${processingKey}-${keyPrefix}-grid-${index}`,
true
)}
</div>
))}
</div>
);
};
const renderTextSegment = (text: string, keyPrefix: string): ReactNode => {
if (!text.trim()) return null;
@ -937,20 +986,31 @@ export const Response = ({
const hasImageSegments = segments.some((segment) => segment.type === "image");
if (hasImageSegments) {
return segments.map((segment, index) => {
const nodes: ReactNode[] = [];
let pendingImages: Array<{ alt?: string; src: string }> = [];
const flushImages = (index: number) => {
if (pendingImages.length === 0) return;
nodes.push(
renderImageGrid(pendingImages, `${keyPrefix}-images-${index}`)
);
pendingImages = [];
};
segments.forEach((segment, index) => {
if (segment.type === "image") {
return renderImagePreview(
segment.src,
segment.alt,
`${segment.src}-${processingKey}-${keyPrefix}-${index}`
);
pendingImages.push({
alt: segment.alt,
src: segment.src,
});
return;
}
if (!segment.value.trim()) {
return null;
return;
}
return (
flushImages(index);
nodes.push(
<Streamdown
className={streamdownClassName}
components={streamdownComponents}
@ -960,6 +1020,8 @@ export const Response = ({
</Streamdown>
);
});
flushImages(segments.length);
return nodes;
}
}

View File

@ -42,7 +42,8 @@ export function ImagePreview({
isProcessingExtend,
isProcessingAngleEdit,
isProcessingWatermarkRemove,
imageIndex
imageIndex,
compactGrid = false
}: {
src: string;
alt?: string;
@ -64,6 +65,7 @@ export function ImagePreview({
isProcessingAngleEdit?: boolean;
isProcessingWatermarkRemove?: boolean;
imageIndex?: number;
compactGrid?: boolean;
}) {
const [isOpen, setIsOpen] = useState(false);
const [isAnnotationMode, setIsAnnotationMode] = useState(false);
@ -293,16 +295,26 @@ export function ImagePreview({
return (
<>
{/* 缩略图(显示压缩后的缩略图)*/}
<div className="group relative inline-block max-w-full mr-3 mb-3">
<div
className={
compactGrid
? "group relative block aspect-[3/4] w-full overflow-hidden rounded-lg"
: "group relative inline-block max-w-full mr-3 mb-3"
}
>
<img
src={previewSrc}
alt="Generated Image"
className="cursor-pointer rounded-lg transition-opacity hover:opacity-90"
className={
compactGrid
? "h-full w-full cursor-pointer rounded-lg object-cover transition-opacity hover:opacity-90"
: "cursor-pointer rounded-lg transition-opacity hover:opacity-90"
}
style={{
maxWidth: `${maxWidth}px`,
height: "auto",
maxWidth: compactGrid ? "none" : `${maxWidth}px`,
height: compactGrid ? "100%" : "auto",
width: "100%",
objectFit: "contain"
objectFit: compactGrid ? "cover" : "contain"
}}
loading="lazy"
decoding="async"

View File

@ -169,15 +169,23 @@ export async function executeDirectAgentImageGeneration(
params.imageParams?.imageSize || params.defaultImageQualityParams?.imageSize;
const imageCount =
params.imageParams?.imageCount || params.defaultImageQualityParams?.imageCount;
const shouldIgnoreModelMaxImages =
typeof imageCount === "number" && Number.isFinite(imageCount) && imageCount > 1;
const result = await generateCanvasImage({
prompt,
negativePrompt: params.imageParams?.negativePrompt,
model: params.finalModelId,
ownerKey: params.userId,
debugLabel: "direct-agent-image-generation",
debugMeta: {
agentIgnoreModelMaxImages: shouldIgnoreModelMaxImages,
requestedAgentImageCount: imageCount ?? null,
},
aspectRatio:
params.imageParams?.aspectRatio || params.defaultImageQualityParams?.aspectRatio,
imageSize,
imageCount,
ignoreModelMaxImages: shouldIgnoreModelMaxImages,
referenceImages: params.referenceImageUrls,
});

View File

@ -116,6 +116,7 @@ export interface CanvasGenerateImageRequest {
ownerKey?: string;
debugLabel?: string;
debugMeta?: Record<string, unknown>;
ignoreModelMaxImages?: boolean;
localEditComposite?: CanvasLocalEditCompositeConfig;
scaleAdjustPatchComposite?: CanvasScaleAdjustPatchCompositeConfig;
scaleAdjustVerification?: CanvasScaleAdjustVerificationConfig;
@ -3167,12 +3168,14 @@ function normalizeCanvasImageParamsByModel(params: {
imageCount: number;
imageSize: string;
imageGenConfig: CanvasImageGenConfig | null | undefined;
ignoreModelMaxImages?: boolean;
}) {
const maxImages =
const configuredMaxImages =
typeof params.imageGenConfig?.maxImages === "number" &&
Number.isFinite(params.imageGenConfig.maxImages)
? Math.max(1, Math.floor(params.imageGenConfig.maxImages))
: 12;
const maxImages = params.ignoreModelMaxImages ? 12 : configuredMaxImages;
const imageCount = Math.max(1, Math.min(maxImages, Math.floor(params.imageCount)));
const requestedSize = normalizeCanvasImageSizeValue(params.imageSize);
@ -3635,6 +3638,7 @@ export async function generateCanvasImage(
imageCount: requestedImageCount,
imageSize: requestedImageSize,
imageGenConfig: selectedModel.model.imageGenConfig as CanvasImageGenConfig | null | undefined,
ignoreModelMaxImages: request.ignoreModelMaxImages === true,
});
const imageSize = normalizedImageParams.imageSize;
const imageCount =