-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvision.js
More file actions
56 lines (46 loc) · 1.52 KB
/
vision.js
File metadata and controls
56 lines (46 loc) · 1.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import config from './config.js';
const IMAGE_URL_RE =
/(https?:\/\/\S+\.(?:png|jpe?g|gif|webp)(?:\?\S*)?)|(\/uploads\/attachments\/[^\s)]+\.(?:png|jpe?g|gif|webp)(?:\?\S*)?)/gi;
const FETCH_TIMEOUT_MS = 8_000;
export function extractImageUrls(text) {
if (!text) return [];
const matches = text.match(IMAGE_URL_RE) || [];
return [...new Set(matches)].map((url) =>
url.startsWith('/uploads/') ? `${config.apiUrl}${url}` : url,
);
}
function mediaTypeFromUrl(url) {
const lower = url.toLowerCase().split('?')[0];
if (lower.endsWith('.png')) return 'image/png';
if (lower.endsWith('.gif')) return 'image/gif';
if (lower.endsWith('.webp')) return 'image/webp';
return 'image/jpeg';
}
export async function fetchImageAsBlock(url) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
const res = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
if (!res.ok) return null;
const buffer = await res.arrayBuffer();
return {
type: 'image',
source: {
type: 'base64',
media_type: mediaTypeFromUrl(url),
data: Buffer.from(buffer).toString('base64'),
},
};
} catch {
return null;
}
}
export async function fetchImagesFromText(text, maxImages = 5) {
const urls = extractImageUrls(text);
if (!urls.length) return [];
const blocks = await Promise.all(
urls.slice(0, maxImages).map((u) => fetchImageAsBlock(u)),
);
return blocks.filter(Boolean);
}