The staging area is empty — there are no staged changes to summarize. Stage some files first with git add and I can help craft the message.

This commit is contained in:
MythEclipse
2026-06-02 22:58:41 +07:00
parent 9f0d0d06ad
commit c18954a263
@@ -1094,7 +1094,14 @@ async function _runSingleMediaAnalysis(
const maxDimension = config.AI_LLM_IMAGE_MAX_DIMENSION ?? 1024; const maxDimension = config.AI_LLM_IMAGE_MAX_DIMENSION ?? 1024;
// ── 1. Download attachments for this message (with resize — R5) ── // ── 1-3. Parallel download of ALL media sources ──
// Build all download promises upfront and execute them in one Promise.all.
// Attachment, URL, sticker/emoji downloads are fully independent of each other.
// An 8-image cap is enforced across all sources combined.
const downloadPromises: Array<Promise<void>> = [];
// ── Attachment downloads ──
const msgAttachments = (allAttachments ?? []) const msgAttachments = (allAttachments ?? [])
.filter( .filter(
(att) => (att) =>
@@ -1104,99 +1111,100 @@ async function _runSingleMediaAnalysis(
) )
.slice(0, 8); .slice(0, 8);
await Promise.all( for (const att of msgAttachments) {
msgAttachments.map(async (att) => { downloadPromises.push(
const urlToUse = getAttachmentImageUrl(att); (async () => {
if (!urlToUse) return; const urlToUse = getAttachmentImageUrl(att);
if (!urlToUse) return;
// Check vision cache BEFORE downloading // Check vision cache BEFORE downloading
const attVisionKey = makeImageCacheKey(urlToUse); const attVisionKey = makeImageCacheKey(urlToUse);
const cachedVision = await getCachedMediaAnalysis(attVisionKey); const cachedVision = await getCachedMediaAnalysis(attVisionKey);
if (cachedVision) { if (cachedVision) {
log.debug( log.debug(
{ attachmentId: att.id, cacheKey: attVisionKey }, { attachmentId: att.id, cacheKey: attVisionKey },
"Vision cache HIT for attachment — skipped download", "Vision cache HIT for attachment — skipped download",
);
const sourceLabel = `[gambar di atas adalah attachment ${att.filename} dari pesan id=${att.message_id}]`;
const analysisText = `[Media analysis for message ${att.message_id}] ${sourceLabel}: ${cachedVision}`;
const existing = mediaAnalysisMap.get(targetId) ?? [];
existing.push(analysisText);
mediaAnalysisMap.set(targetId, existing);
return;
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const res = await fetch(urlToUse, { signal: controller.signal });
if (!res.ok || !res.body) return;
let totalBytes = 0;
const chunks: Uint8Array[] = [];
const reader = res.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
totalBytes += value.length;
if (totalBytes > 10 * 1024 * 1024) {
reader.cancel();
return;
}
chunks.push(value);
}
}
const imageBytes = Buffer.concat(chunks);
const sniffedMime = sniffImageMimeType(imageBytes);
if (!sniffedMime) {
log.warn(
{ attachmentId: att.id },
"Skipping attachment: not a recognised image format",
); );
const sourceLabel = `[gambar di atas adalah attachment ${att.filename} dari pesan id=${att.message_id}]`;
const analysisText = `[Media analysis for message ${att.message_id}] ${sourceLabel}: ${cachedVision}`;
const existing = mediaAnalysisMap.get(targetId) ?? [];
existing.push(analysisText);
mediaAnalysisMap.set(targetId, existing);
return; return;
} }
// Resize before base64 encoding (R5) const controller = new AbortController();
const { data: resizedBuffer, mimeType: resizedMime } = const timeoutId = setTimeout(() => controller.abort(), 15000);
await resizeImageForVision(imageBytes, maxDimension);
const dataUrl = `data:${resizedMime};base64,${resizedBuffer.toString("base64")}`; try {
const part: MessageImagePart = { const res = await fetch(urlToUse, { signal: controller.signal });
type: "image_url", if (!res.ok || !res.body) return;
image_url: { url: dataUrl },
sourceLabel: `[gambar di atas adalah attachment ${att.filename} dari pesan id=${att.message_id}]`,
};
const existing = imageMap.get(targetId) ?? [];
existing.push(part);
imageMap.set(targetId, existing);
} catch (err) {
log.warn(
{
attachmentId: att.id,
error: err instanceof Error ? err.message : String(err),
},
"Error downloading attachment",
);
} finally {
clearTimeout(timeoutId);
}
}),
);
// ── 2. Fetch URLs found in message text ── let totalBytes = 0;
const content = target.edited_content ?? target.content; const chunks: Uint8Array[] = [];
const reader = res.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
totalBytes += value.length;
if (totalBytes > 10 * 1024 * 1024) {
reader.cancel();
return;
}
chunks.push(value);
}
}
const imageBytes = Buffer.concat(chunks);
const sniffedMime = sniffImageMimeType(imageBytes);
if (!sniffedMime) {
log.warn(
{ attachmentId: att.id },
"Skipping attachment: not a recognised image format",
);
return;
}
const { data: resizedBuffer, mimeType: resizedMime } =
await resizeImageForVision(imageBytes, maxDimension);
const dataUrl = `data:${resizedMime};base64,${resizedBuffer.toString("base64")}`;
const part: MessageImagePart = {
type: "image_url",
image_url: { url: dataUrl },
sourceLabel: `[gambar di atas adalah attachment ${att.filename} dari pesan id=${att.message_id}]`,
};
const existing = imageMap.get(targetId) ?? [];
if (existing.length < 8) {
existing.push(part);
imageMap.set(targetId, existing);
}
} catch (err) {
log.warn(
{
attachmentId: att.id,
error: err instanceof Error ? err.message : String(err),
},
"Error downloading attachment",
);
} finally {
clearTimeout(timeoutId);
}
})(),
);
}
// ── URL fetch promises ──
const urls = extractUrlsFromText(content).slice(0, 3); const urls = extractUrlsFromText(content).slice(0, 3);
const urlWebTexts: string[] = [];
if (urls.length > 0) { for (const url of urls) {
const webTexts: string[] = []; downloadPromises.push(
await Promise.all( (async () => {
urls.map(async (url) => {
const result = await fetchUrlSafely(url); const result = await fetchUrlSafely(url);
if (result.type === "image" && result.data && result.mimeType) { if (result.type === "image" && result.data && result.mimeType) {
// Resize fetched images too (R5)
const { data: resizedBuffer, mimeType: resizedMime } = const { data: resizedBuffer, mimeType: resizedMime } =
await resizeImageForVision(result.data, maxDimension); await resizeImageForVision(result.data, maxDimension);
@@ -1207,17 +1215,18 @@ async function _runSingleMediaAnalysis(
sourceLabel: `[gambar di atas berasal dari link ${url} pada pesan id=${targetId}]`, sourceLabel: `[gambar di atas berasal dari link ${url} pada pesan id=${targetId}]`,
}; };
const existing = imageMap.get(targetId) ?? []; const existing = imageMap.get(targetId) ?? [];
existing.push(part); if (existing.length < 8) {
imageMap.set(targetId, existing); existing.push(part);
imageMap.set(targetId, existing);
}
} else if (result.type === "text" && result.textContent) { } else if (result.type === "text" && result.textContent) {
webTexts.push(`[Isi Web dari ${url}]: ${result.textContent}`); urlWebTexts.push(`[Isi Web dari ${url}]: ${result.textContent}`);
} }
}), })(),
); );
if (webTexts.length > 0) webTextMap.set(targetId, webTexts);
} }
// ── 3. Sticker / embed / custom emoji images ── // ── Sticker / embed / custom emoji download promises ──
const mediaEvidence = extractMessageMediaEvidence(target.metadata); const mediaEvidence = extractMessageMediaEvidence(target.metadata);
const mediaCandidates: Array<{ const mediaCandidates: Array<{
messageId: string; messageId: string;
@@ -1273,81 +1282,91 @@ async function _runSingleMediaAnalysis(
})), })),
]; ];
const remainingSlots = Math.max(0, 8 - (imageMap.get(targetId)?.length ?? 0)); for (const candidate of mediaCandidates) {
downloadPromises.push(
(async () => {
// Skip if we already have 8 images
if ((imageMap.get(targetId)?.length ?? 0) >= 8) return;
await Promise.all( // Vision cache check before download
mediaCandidates.slice(0, remainingSlots).map(async (candidate) => { const visionCacheKey = candidate.customEmojiId
// Vision cache check before download ? makeCustomEmojiCacheKey(candidate.customEmojiId)
const visionCacheKey = candidate.customEmojiId : candidate.stickerName
? makeCustomEmojiCacheKey(candidate.customEmojiId) ? makeStickerCacheKey(candidate.stickerName)
: candidate.stickerName : makeImageCacheKey(candidate.url);
? makeStickerCacheKey(candidate.stickerName) const cachedVision = await getCachedMediaAnalysis(visionCacheKey);
: makeImageCacheKey(candidate.url); if (cachedVision) {
const cachedVision = await getCachedMediaAnalysis(visionCacheKey); log.debug(
if (cachedVision) { { cacheKey: visionCacheKey },
log.debug( "Vision cache HIT for media candidate — skipped download",
{ cacheKey: visionCacheKey }, );
"Vision cache HIT for media candidate — skipped download", const analysisText = `[Media analysis for message ${candidate.messageId}] ${candidate.label}: ${cachedVision}`;
); const existing = mediaAnalysisMap.get(targetId) ?? [];
const analysisText = `[Media analysis for message ${candidate.messageId}] ${candidate.label}: ${cachedVision}`; existing.push(analysisText);
const existing = mediaAnalysisMap.get(targetId) ?? []; mediaAnalysisMap.set(targetId, existing);
existing.push(analysisText); return;
mediaAnalysisMap.set(targetId, existing);
return;
}
// Sticker download cache
if (candidate.stickerName && isStickerCacheReady()) {
try {
const cached = await getStickerFromCache(candidate.stickerName);
if (cached) {
const part: MessageImagePart = {
type: "image_url",
image_url: {
url: `data:${cached.mimeType};base64,${cached.base64}`,
},
sourceLabel: candidate.label,
stickerName: candidate.stickerName,
};
const existing = imageMap.get(targetId) ?? [];
existing.push(part);
imageMap.set(targetId, existing);
return;
}
} catch {
// Fall through to fetch
} }
}
const result = await fetchUrlSafely(candidate.url); // Sticker download cache
if (result.type !== "image" || !result.data || !result.mimeType) return; if (candidate.stickerName && isStickerCacheReady()) {
try {
const cached = await getStickerFromCache(candidate.stickerName);
if (cached) {
const part: MessageImagePart = {
type: "image_url",
image_url: {
url: `data:${cached.mimeType};base64,${cached.base64}`,
},
sourceLabel: candidate.label,
stickerName: candidate.stickerName,
};
const existing = imageMap.get(targetId) ?? [];
if (existing.length < 8) {
existing.push(part);
imageMap.set(targetId, existing);
}
return;
}
} catch {
// Fall through to fetch
}
}
// Resize sticker/emoji images too (R5) const result = await fetchUrlSafely(candidate.url);
const { data: resizedBuffer, mimeType: resizedMime } = if (result.type !== "image" || !result.data || !result.mimeType) return;
await resizeImageForVision(result.data, maxDimension);
const base64 = resizedBuffer.toString("base64"); const { data: resizedBuffer, mimeType: resizedMime } =
if (candidate.stickerName) { await resizeImageForVision(result.data, maxDimension);
setStickerInCache(candidate.stickerName, base64, resizedMime).catch(
() => {},
);
}
const part: MessageImagePart = { const base64 = resizedBuffer.toString("base64");
type: "image_url", if (candidate.stickerName) {
image_url: { setStickerInCache(candidate.stickerName, base64, resizedMime).catch(
url: `data:${resizedMime};base64,${base64}`, () => {},
}, );
sourceLabel: candidate.label, }
stickerName: candidate.stickerName,
customEmojiId: candidate.customEmojiId, const part: MessageImagePart = {
customEmojiName: candidate.customEmojiName, type: "image_url",
}; image_url: { url: `data:${resizedMime};base64,${base64}` },
const existing = imageMap.get(targetId) ?? []; sourceLabel: candidate.label,
existing.push(part); stickerName: candidate.stickerName,
imageMap.set(targetId, existing); customEmojiId: candidate.customEmojiId,
}), customEmojiName: candidate.customEmojiName,
); };
const existing = imageMap.get(targetId) ?? [];
if (existing.length < 8) {
existing.push(part);
imageMap.set(targetId, existing);
}
})(),
);
}
// Execute ALL media downloads in parallel
await Promise.all(downloadPromises);
// Collect web text results from URL fetches
if (urlWebTexts.length > 0) webTextMap.set(targetId, urlWebTexts);
// ── 4. Vision analysis for every image ── // ── 4. Vision analysis for every image ──
await Promise.all( await Promise.all(