diff --git a/src/vision.ts b/src/vision.ts index 7a088ec..1266232 100644 --- a/src/vision.ts +++ b/src/vision.ts @@ -7,47 +7,55 @@ export async function applyVisionInterceptor(messages: AnthropicMessage[]): Prom const config = getConfig(); if (!config.vision?.enabled) return; - for (const msg of messages) { - if (msg.role !== 'user') continue; - if (!Array.isArray(msg.content)) continue; - - let hasImages = false; - const newContent: AnthropicContentBlock[] = []; - const imagesToAnalyze: AnthropicContentBlock[] = []; - - for (const block of msg.content) { - if (block.type === 'image') { - hasImages = true; - imagesToAnalyze.push(block); - } else { - newContent.push(block); - } + // ★ 仅处理最后一条 user 消息中的图片 + // 历史消息的图片已在前几轮被转换为文本描述,无需重复处理 + // 这避免了多轮对话中重复消耗 Vision API 配额和增加延迟 + let lastUserMsg: AnthropicMessage | null = null; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === 'user') { + lastUserMsg = messages[i]; + break; } + } - if (hasImages && imagesToAnalyze.length > 0) { - try { - let descriptions = ''; - if (config.vision.mode === 'ocr') { - descriptions = await processWithLocalOCR(imagesToAnalyze); - } else { - descriptions = await callVisionAPI(imagesToAnalyze); - } + if (!lastUserMsg || !Array.isArray(lastUserMsg.content)) return; - // Add descriptions as a simulated system text block - newContent.push({ - type: 'text', - text: `\n\n[System: The user attached ${imagesToAnalyze.length} image(s). Visual analysis/OCR extracted the following context:\n${descriptions}]\n\n` - }); + let hasImages = false; + const newContent: AnthropicContentBlock[] = []; + const imagesToAnalyze: AnthropicContentBlock[] = []; - msg.content = newContent; - } catch (e) { - console.error("[Vision API Error]", e); - newContent.push({ - type: 'text', - text: `\n\n[System: The user attached image(s), but the Vision interceptor failed to process them. Error: ${(e as Error).message}]\n\n` - }); - msg.content = newContent; + for (const block of lastUserMsg.content) { + if (block.type === 'image') { + hasImages = true; + imagesToAnalyze.push(block); + } else { + newContent.push(block); + } + } + + if (hasImages && imagesToAnalyze.length > 0) { + try { + let descriptions = ''; + if (config.vision.mode === 'ocr') { + descriptions = await processWithLocalOCR(imagesToAnalyze); + } else { + descriptions = await callVisionAPI(imagesToAnalyze); } + + // Add descriptions as a simulated system text block + newContent.push({ + type: 'text', + text: `\n\n[System: The user attached ${imagesToAnalyze.length} image(s). Visual analysis/OCR extracted the following context:\n${descriptions}]\n\n` + }); + + lastUserMsg.content = newContent; + } catch (e) { + console.error("[Vision API Error]", e); + newContent.push({ + type: 'text', + text: `\n\n[System: The user attached image(s), but the Vision interceptor failed to process them. Error: ${(e as Error).message}]\n\n` + }); + lastUserMsg.content = newContent; } } }