Files
memind/vision-image-thumb.mjs
john 32fb2cdeaf feat: chat uploads, vision turn isolation, and MindSpace agent improvements
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 00:23:01 +08:00

74 lines
2.1 KiB
JavaScript

import sharp from 'sharp';
export const VISION_THUMB_MAX_SIDE = 1280;
export const VISION_THUMB_MAX_BYTES = 600 * 1024;
export const VISION_THUMB_JPEG_QUALITY = 78;
/**
* Build a smaller JPEG buffer for vision-model analysis only.
* Page generation still uses the full public standard image elsewhere.
*/
export async function buildVisionThumbnailBuffer(
buffer,
mimeType = 'image/jpeg',
{
maxSide = VISION_THUMB_MAX_SIDE,
maxBytes = VISION_THUMB_MAX_BYTES,
quality = VISION_THUMB_JPEG_QUALITY,
} = {},
) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
throw new Error('vision thumb: empty buffer');
}
const metadata = await sharp(buffer, { sequentialRead: true }).metadata();
const width = Number(metadata.width ?? 0);
const height = Number(metadata.height ?? 0);
if (!width || !height) {
throw new Error('vision thumb: invalid dimensions');
}
const longest = Math.max(width, height);
const resizeWidth = longest > maxSide ? Math.max(1, Math.round((width * maxSide) / longest)) : null;
const resizeHeight = longest > maxSide ? Math.max(1, Math.round((height * maxSide) / longest)) : null;
let pipeline = sharp(buffer, { sequentialRead: true }).rotate();
if (resizeWidth && resizeHeight) {
pipeline = pipeline.resize({
width: resizeWidth,
height: resizeHeight,
fit: 'inside',
withoutEnlargement: true,
});
}
let nextQuality = quality;
let candidate = await pipeline
.flatten({ background: '#ffffff' })
.jpeg({ quality: nextQuality, mozjpeg: true })
.toBuffer();
while (candidate.length > maxBytes && nextQuality > 52) {
nextQuality = Math.max(52, nextQuality - 8);
candidate = await sharp(buffer, { sequentialRead: true })
.rotate()
.resize({
width: resizeWidth ?? width,
height: resizeHeight ?? height,
fit: 'inside',
withoutEnlargement: true,
})
.flatten({ background: '#ffffff' })
.jpeg({ quality: nextQuality, mozjpeg: true })
.toBuffer();
}
return candidate;
}
export const visionThumbInternals = {
VISION_THUMB_MAX_SIDE,
VISION_THUMB_MAX_BYTES,
VISION_THUMB_JPEG_QUALITY,
};