tangled
alpha
login
or
join now
indexx.dev
/
tweets2bsky
forked from
j4ck.xyz/tweets2bsky
0
fork
atom
A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
0
fork
atom
overview
issues
pulls
pipelines
chore: shorten alt text prompt
Jack G
1 month ago
ba862fc0
86015a55
+28
-12
1 changed file
expand all
collapse all
unified
split
src
ai-manager.ts
+28
-12
src/ai-manager.ts
···
43
43
}
44
44
45
45
try {
46
46
+
const prompt = buildAltTextPrompt(contextText);
46
47
switch (provider) {
47
48
case 'gemini':
48
49
// apiKey is guaranteed by check above
49
49
-
return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, contextText);
50
50
+
return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, prompt);
50
51
case 'openai':
51
52
case 'custom':
52
52
-
return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, contextText);
53
53
+
return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, prompt);
53
54
case 'anthropic':
54
55
// apiKey is guaranteed by check above
55
56
return await callAnthropic(
···
58
59
baseUrl,
59
60
buffer,
60
61
mimeType,
61
61
-
contextText,
62
62
+
prompt,
62
63
);
63
64
default:
64
65
console.warn(`[AI] ⚠️ Unknown provider: ${provider}`);
···
70
71
}
71
72
}
72
73
74
74
+
const ALT_TEXT_CONTEXT_MAX_CHARS = 400;
75
75
+
76
76
+
function buildAltTextPrompt(contextText: string): string {
77
77
+
const normalized = contextText.replace(/\s+/g, ' ').trim();
78
78
+
const trimmed =
79
79
+
normalized.length > ALT_TEXT_CONTEXT_MAX_CHARS
80
80
+
? `${normalized.slice(0, ALT_TEXT_CONTEXT_MAX_CHARS).trim()}...`
81
81
+
: normalized;
82
82
+
83
83
+
return [
84
84
+
'Write alt text (1-2 sentences).',
85
85
+
'Describe only what is visible.',
86
86
+
'Use context to identify people/places/objects if relevant.',
87
87
+
'Include key names for search.',
88
88
+
'No hashtags or emojis.',
89
89
+
`Context: "${trimmed}"`,
90
90
+
].join(' ');
91
91
+
}
92
92
+
73
93
async function callGemini(
74
94
apiKey: string,
75
95
modelName: string,
76
96
buffer: Buffer,
77
97
mimeType: string,
78
78
-
contextText: string,
98
98
+
prompt: string,
79
99
): Promise<string | undefined> {
80
100
const genAI = new GoogleGenerativeAI(apiKey);
81
101
const model = genAI.getGenerativeModel({ model: modelName });
82
102
83
83
-
const prompt = `Describe this image for alt text. Be concise but descriptive.
84
84
-
Context from the tweet text: "${contextText}".
85
85
-
Use the context to identify specific people, objects, or context mentioned, but describe what is visually present in the image.`;
86
86
-
87
103
const result = await model.generateContent([
88
104
prompt,
89
105
{
···
103
119
baseUrl: string | undefined,
104
120
buffer: Buffer,
105
121
mimeType: string,
106
106
-
contextText: string,
122
122
+
prompt: string,
107
123
): Promise<string | undefined> {
108
124
const url = baseUrl
109
125
? `${baseUrl.replace(/\/+$/, '')}/chat/completions`
···
119
135
content: [
120
136
{
121
137
type: 'text',
122
122
-
text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`,
138
138
+
text: prompt,
123
139
},
124
140
{
125
141
type: 'image_url',
···
158
174
baseUrl: string | undefined,
159
175
buffer: Buffer,
160
176
mimeType: string,
161
161
-
contextText: string,
177
177
+
prompt: string,
162
178
): Promise<string | undefined> {
163
179
const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages';
164
180
···
181
197
},
182
198
{
183
199
type: 'text',
184
184
-
text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`,
200
200
+
text: prompt,
185
201
},
186
202
],
187
203
},