forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {DEFAULT_ALT_TEXT_AI_MODEL, MAX_ALT_TEXT} from '#/lib/constants'
2import {logger} from '#/logger'
3
4export async function generateAltText(
5 apiKey: string,
6 model: string,
7 imageBase64: string,
8 imageMimeType: string,
9): Promise<string> {
10 const response = await fetch(
11 'https://openrouter.ai/api/v1/chat/completions',
12 {
13 method: 'POST',
14 headers: {
15 Authorization: `Bearer ${apiKey}`,
16 'Content-Type': 'application/json',
17 'HTTP-Referer': 'https://witchsky.app',
18 'X-Title': 'Witchsky',
19 },
20 body: JSON.stringify({
21 model: model || DEFAULT_ALT_TEXT_AI_MODEL,
22 messages: [
23 {
24 role: 'user',
25 content: [
26 {
27 type: 'text',
28 text: `Generate a concise, descriptive alt text for this image, also extract text if needed. The alt text should be clear and helpful for screen readers. Keep it under ${MAX_ALT_TEXT} characters. Only respond with the alt text itself, no explanations or quotes.`,
29 },
30 {
31 type: 'image_url',
32 image_url: {
33 url: `data:${imageMimeType};base64,${imageBase64}`,
34 },
35 },
36 ],
37 },
38 ],
39 max_tokens: MAX_ALT_TEXT,
40 }),
41 },
42 )
43
44 if (!response.ok) {
45 const errorText = await response.text()
46 logger.error('OpenRouter API error', {
47 status: response.status,
48 error: errorText,
49 })
50 throw new Error(`OpenRouter API error: ${response.status}`)
51 }
52
53 const data = await response.json()
54 const altText = data.choices?.[0]?.message?.content?.trim()
55
56 if (!altText) {
57 throw new Error('No alt text generated')
58 }
59
60 return altText
61}