import assert from 'node:assert/strict'; import test from 'node:test'; import { buildVisionPayload, extractImageUrlsFromMessage } from './tkmind-proxy.mjs'; test('extractImageUrlsFromMessage reads metadata image urls', () => { const urls = extractImageUrlsFromMessage({ metadata: { imageUrls: ['/api/mindspace/v1/assets/asset-1/download?inline=1'], }, content: [{ type: 'text', text: '根据图片生成页面' }], }); assert.deepEqual(urls, ['/api/mindspace/v1/assets/asset-1/download?inline=1']); }); test('extractImageUrlsFromMessage falls back to formatted image lines', () => { const urls = extractImageUrlsFromMessage({ metadata: { userVisible: true, agentVisible: true }, content: [ { type: 'text', text: '根据图片生成一个主题页面\n\n' + '[图片1]: https://m.tkmind.cn/api/mindspace/v1/assets/asset-2/download?inline=1&v=123', }, ], }); assert.deepEqual(urls, [ 'https://m.tkmind.cn/api/mindspace/v1/assets/asset-2/download?inline=1&v=123', ]); }); test('extractImageUrlsFromMessage deduplicates image urls', () => { const url = '/api/mindspace/v1/assets/asset-3/download?inline=1'; const urls = extractImageUrlsFromMessage({ metadata: { imageUrls: [url] }, content: [{ type: 'text', text: `[图片1]: ${url}` }], }); assert.deepEqual(urls, [url]); }); test('extractImageUrlsFromMessage ignores content aliases when metadata is present', () => { const urls = extractImageUrlsFromMessage({ metadata: { imageUrls: ['/api/mindspace/v1/assets/asset-4/download?inline=1'], }, content: [ { type: 'text', text: '[图片1]: https://m.tkmind.cn/MindSpace/user/public/images/2026-07-10/asset-4.jpg', }, ], }); assert.deepEqual(urls, ['/api/mindspace/v1/assets/asset-4/download?inline=1']); }); test('buildVisionPayload marks one billable image analysis when vision succeeds', async () => { const result = await buildVisionPayload({ userId: 'user-1', publishLayout: { publicUrl: 'https://m.tkmind.cn/MindSpace/user-1' }, userMessage: { content: [{ type: 'text', text: '根据图片生成页面\n\n[图片1]: /api/mindspace/v1/assets/asset-1/download?inline=1' }], }, localFetchAsset: async () => ({ buffer: Buffer.from('fake-image'), mimeType: 'image/png', }), llmProviderService: { analyzeImagesWithVision: async () => '一位小女孩正在餐厅里开心地吃饭', }, }); assert.equal(result?.billableImageCount, 1); assert.match(result?.userMessage?.content?.[0]?.text ?? '', /Qwen VL 图片描述/); assert.match(result?.userMessage?.content?.[0]?.text ?? '', /本轮图片主题/); assert.equal( result?.userMessage?.metadata?.displayText, '根据图片生成页面\n\n[图片1]: /api/mindspace/v1/assets/asset-1/download?inline=1', ); }); test('buildVisionPayload sends all current-turn images to Qwen in order', async () => { const imageUrls = [ 'https://m.tkmind.cn/MindSpace/user-1/public/wechat-mp/first.jpg', 'https://m.tkmind.cn/MindSpace/user-1/public/wechat-mp/second.jpg', ]; let analyzedImages = []; const result = await buildVisionPayload({ userId: 'user-1', publishLayout: { publicUrl: 'https://m.tkmind.cn/MindSpace/user-1' }, userMessage: { content: [{ type: 'text', text: '请结合两张图片生成页面' }], metadata: { imageUrls }, }, fetchImpl: async () => new Response(Buffer.from('fake-image'), { status: 200, headers: { 'Content-Type': 'image/jpeg' }, }), llmProviderService: { analyzeImagesWithVision: async (images) => { analyzedImages = images; return '第一张是宝塔,第二张是晚霞。'; }, }, }); assert.equal(analyzedImages.length, 2); assert.deepEqual(analyzedImages.map((item) => item.rawUrl), imageUrls); assert.deepEqual(result?.userMessage?.metadata?.imageUrls, [ '/MindSpace/user-1/public/wechat-mp/first.jpg', '/MindSpace/user-1/public/wechat-mp/second.jpg', ]); assert.match(result?.userMessage?.content?.[0]?.text ?? '', /本轮用户仅上传 2 张图片/); assert.match(result?.userMessage?.content?.[0]?.text ?? '', /图片2/); }); test('buildVisionPayload does not mark billable usage when vision analysis fails', async () => { const result = await buildVisionPayload({ userId: 'user-1', publishLayout: { publicUrl: 'https://m.tkmind.cn/MindSpace/user-1' }, userMessage: { content: [{ type: 'text', text: '根据图片生成页面\n\n[图片1]: /api/mindspace/v1/assets/asset-1/download?inline=1' }], }, localFetchAsset: async () => ({ buffer: Buffer.from('fake-image'), mimeType: 'image/png', }), llmProviderService: { analyzeImagesWithVision: async () => { throw new Error('vision unavailable'); }, }, }); assert.equal(result?.billableImageCount, 0); assert.doesNotMatch(result?.userMessage?.content?.[0]?.text ?? '', /Qwen VL 图片描述/); });