There are **no staged changes** to work with — the working tree is clean and git diff --cached returned nothing.

This commit is contained in:
MythEclipse
2026-06-02 22:16:06 +07:00
parent 9e8881f4f0
commit 00851e76da
@@ -504,6 +504,14 @@ function hasMediaContent(
// Single-image vision analysis (reused by both text-only and media paths) // Single-image vision analysis (reused by both text-only and media paths)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* In-flight deduplication map — prevents concurrent vision API calls for
* the same cache key. Multiple concurrent requests for an identical image
* share the same promise, eliminating the race condition between cache
* check and cache write.
*/
const inFlightVisionCalls = new Map<string, Promise<string | null>>();
const analyzeSingleMediaImage = async ( const analyzeSingleMediaImage = async (
messageId: string, messageId: string,
image: MessageImagePart, image: MessageImagePart,
@@ -520,12 +528,23 @@ const analyzeSingleMediaImage = async (
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${cached}`; return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${cached}`;
} }
// Deduplicate in-flight vision calls: if another caller is already
// processing this exact image, wait for it instead of starting a duplicate.
const existing = inFlightVisionCalls.get(cacheKey);
if (existing) {
log.debug({ cacheKey }, "Media analysis in-flight dedupe — waiting for existing call");
const result = await existing;
if (!result) return null;
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${result}`;
}
const promptText = image.stickerName const promptText = image.stickerName
? buildStickerVisionPrompt(image.stickerName, messageId) ? buildStickerVisionPrompt(image.stickerName, messageId)
: image.customEmojiName : image.customEmojiName
? buildCustomEmojiVisionPrompt(image.customEmojiName, messageId) ? buildCustomEmojiVisionPrompt(image.customEmojiName, messageId)
: buildGeneralImageVisionPrompt(image.sourceLabel, messageId); : buildGeneralImageVisionPrompt(image.sourceLabel, messageId);
const visionPromise = (async (): Promise<string | null> => {
try { try {
const content = await llmVision(promptText, image.image_url); const content = await llmVision(promptText, image.image_url);
if (!content) return null; if (!content) return null;
@@ -537,7 +556,7 @@ const analyzeSingleMediaImage = async (
Date.now() + 24 * 60 * 60 * 1000, Date.now() + 24 * 60 * 60 * 1000,
); );
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${content}`; return content;
} catch (error) { } catch (error) {
log.warn( log.warn(
{ {
@@ -548,8 +567,21 @@ const analyzeSingleMediaImage = async (
); );
// Return a clear signal that vision analysis FAILED — so batch LLM knows // Return a clear signal that vision analysis FAILED — so batch LLM knows
// the image could not be described and must NOT assume it's clean. // the image could not be described and must NOT assume it's clean.
return null;
}
})();
inFlightVisionCalls.set(cacheKey, visionPromise);
try {
const content = await visionPromise;
if (!content) {
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: GAGAL DIANALISIS — gambar tidak dapat diunduh atau vision API gagal setelah 3x percobaan. JANGAN mengasumsikan gambar aman hanya karena gagal dianalisis. Gunakan metadata URL/nama file saja sebagai petunjuk.`; return `[Media analysis for message ${messageId}] ${image.sourceLabel}: GAGAL DIANALISIS — gambar tidak dapat diunduh atau vision API gagal setelah 3x percobaan. JANGAN mengasumsikan gambar aman hanya karena gagal dianalisis. Gunakan metadata URL/nama file saja sebagai petunjuk.`;
} }
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${content}`;
} finally {
inFlightVisionCalls.delete(cacheKey);
}
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------