fix: address PR review findings for image pipeline

- Add defense-in-depth count and size validation in InsightsExecutor
  (NEW-005)
- Make image analysis unsupported warning more prominent with icon and
  background styling (REVIEW-001/CMT-001)
- Add inline notice on sent messages that images were not analyzed
  (CMT-001)
- Add i18n keys for image not-analyzed notice (en + fr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AndyMik90
2026-02-17 15:36:31 +01:00
parent 2b8e2af233
commit 8e86bd60ae
4 changed files with 31 additions and 15 deletions
@@ -13,7 +13,7 @@ import type {
InsightsModelConfig,
ImageAttachment
} from '../../shared/types';
import { MODEL_ID_MAP } from '../../shared/constants';
import { MODEL_ID_MAP, MAX_IMAGES_PER_TASK, MAX_IMAGE_SIZE } from '../../shared/constants';
import { InsightsConfig } from './config';
import { detectRateLimit, createSDKRateLimitInfo } from '../rate-limit-detector';
@@ -120,6 +120,14 @@ export class InsightsExecutor extends EventEmitter {
const imagesTempFiles: string[] = [];
let imagesManifestFile: string | undefined;
// Defense-in-depth: cap image count and filter oversized images in the executor
if (images && images.length > MAX_IMAGES_PER_TASK) {
images = images.slice(0, MAX_IMAGES_PER_TASK);
}
if (images) {
images = images.filter(img => !img.data || Buffer.byteLength(img.data, 'base64') <= MAX_IMAGE_SIZE);
}
if (images && images.length > 0) {
try {
const manifest: Array<{ path: string; mimeType: string }> = [];
@@ -526,7 +526,10 @@ export function Insights({ projectId }: InsightsProps) {
{/* Image analysis warning */}
{pendingImages.length > 0 && (
<p className="mt-1 text-xs text-amber-500">{t('insights.images.analysisUnsupported')}</p>
<div className="mt-1 flex items-center gap-1.5 rounded-md bg-amber-500/10 px-2 py-1 text-xs text-amber-500">
<AlertCircle className="h-3 w-3 shrink-0" />
<span>{t('insights.images.analysisUnsupported')}</span>
</div>
)}
{/* Image error */}
@@ -625,17 +628,20 @@ function MessageBubble({
{/* Image attachments for user messages */}
{isUser && message.images && message.images.length > 0 && (
<div className="flex flex-wrap gap-2">
{message.images
.filter(img => img.thumbnail || img.data)
.map((image) => (
<img
key={image.id}
src={image.thumbnail || `data:${image.mimeType};base64,${image.data}`}
alt={image.filename}
className="max-w-[200px] max-h-[200px] rounded-md border border-border object-contain"
/>
))}
<div className="space-y-1.5">
<div className="flex flex-wrap gap-2">
{message.images
.filter(img => img.thumbnail || img.data)
.map((image) => (
<img
key={image.id}
src={image.thumbnail || `data:${image.mimeType};base64,${image.data}`}
alt={image.filename}
className="max-w-[200px] max-h-[200px] rounded-md border border-border object-contain"
/>
))}
</div>
<p className="text-xs text-muted-foreground italic">{t('insights.images.notAnalyzed')}</p>
</div>
)}
@@ -442,7 +442,8 @@
"processFailed": "Failed to process image",
"dragOver": "Drop image to attach",
"analysisUnsupported": "Note: Image analysis is not yet supported. Images are stored for reference but cannot be analyzed by the model.",
"screenshotTooLarge": "Screenshot is too large ({{size}}MB). Maximum size is {{max}}MB. Consider capturing a smaller area."
"screenshotTooLarge": "Screenshot is too large ({{size}}MB). Maximum size is {{max}}MB. Consider capturing a smaller area.",
"notAnalyzed": "Images were stored for reference but not analyzed by the model."
}
},
"ideation": {
@@ -442,7 +442,8 @@
"processFailed": "Échec du traitement de l'image",
"dragOver": "Déposer l'image pour joindre",
"analysisUnsupported": "Note : L'analyse d'images n'est pas encore prise en charge. Les images sont stockées pour référence mais ne peuvent pas être analysées par le modèle.",
"screenshotTooLarge": "La capture d'écran est trop volumineuse ({{size}}Mo). La taille maximale est de {{max}}Mo. Essayez de capturer une zone plus petite."
"screenshotTooLarge": "La capture d'écran est trop volumineuse ({{size}}Mo). La taille maximale est de {{max}}Mo. Essayez de capturer une zone plus petite.",
"notAnalyzed": "Les images ont été stockées pour référence mais n'ont pas été analysées par le modèle."
}
},
"ideation": {