There are **no staged changes** to work with — the working tree is clean and git diff --cached returned nothing.
This commit is contained in:
@@ -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,35 +528,59 @@ 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 {
|
||||||
|
const content = await llmVision(promptText, image.image_url);
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
await upsertCachedMediaAnalysis(
|
||||||
|
cacheKey,
|
||||||
|
content,
|
||||||
|
"vision_llm",
|
||||||
|
Date.now() + 24 * 60 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
} catch (error) {
|
||||||
|
log.warn(
|
||||||
|
{
|
||||||
|
messageId,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
"Vision analysis failed after retries — image not analyzed",
|
||||||
|
);
|
||||||
|
// 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.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
inFlightVisionCalls.set(cacheKey, visionPromise);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = await llmVision(promptText, image.image_url);
|
const content = await visionPromise;
|
||||||
if (!content) return null;
|
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.`;
|
||||||
await upsertCachedMediaAnalysis(
|
}
|
||||||
cacheKey,
|
|
||||||
content,
|
|
||||||
"vision_llm",
|
|
||||||
Date.now() + 24 * 60 * 60 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${content}`;
|
return `[Media analysis for message ${messageId}] ${image.sourceLabel}: ${content}`;
|
||||||
} catch (error) {
|
} finally {
|
||||||
log.warn(
|
inFlightVisionCalls.delete(cacheKey);
|
||||||
{
|
|
||||||
messageId,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
},
|
|
||||||
"Vision analysis failed after retries — image not analyzed",
|
|
||||||
);
|
|
||||||
// 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.
|
|
||||||
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.`;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user