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
}
44
45
try {
0
46
switch (provider) {
47
case 'gemini':
48
// apiKey is guaranteed by check above
49
-
return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, contextText);
50
case 'openai':
51
case 'custom':
52
-
return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, contextText);
53
case 'anthropic':
54
// apiKey is guaranteed by check above
55
return await callAnthropic(
···
58
baseUrl,
59
buffer,
60
mimeType,
61
-
contextText,
62
);
63
default:
64
console.warn(`[AI] ⚠️ Unknown provider: ${provider}`);
···
70
}
71
}
72
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
73
async function callGemini(
74
apiKey: string,
75
modelName: string,
76
buffer: Buffer,
77
mimeType: string,
78
-
contextText: string,
79
): Promise<string | undefined> {
80
const genAI = new GoogleGenerativeAI(apiKey);
81
const model = genAI.getGenerativeModel({ model: modelName });
82
83
-
const prompt = `Describe this image for alt text. Be concise but descriptive.
84
-
Context from the tweet text: "${contextText}".
85
-
Use the context to identify specific people, objects, or context mentioned, but describe what is visually present in the image.`;
86
-
87
const result = await model.generateContent([
88
prompt,
89
{
···
103
baseUrl: string | undefined,
104
buffer: Buffer,
105
mimeType: string,
106
-
contextText: string,
107
): Promise<string | undefined> {
108
const url = baseUrl
109
? `${baseUrl.replace(/\/+$/, '')}/chat/completions`
···
119
content: [
120
{
121
type: 'text',
122
-
text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`,
123
},
124
{
125
type: 'image_url',
···
158
baseUrl: string | undefined,
159
buffer: Buffer,
160
mimeType: string,
161
-
contextText: string,
162
): Promise<string | undefined> {
163
const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages';
164
···
181
},
182
{
183
type: 'text',
184
-
text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`,
185
},
186
],
187
},
···
43
}
44
45
try {
46
+
const prompt = buildAltTextPrompt(contextText);
47
switch (provider) {
48
case 'gemini':
49
// apiKey is guaranteed by check above
50
+
return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, prompt);
51
case 'openai':
52
case 'custom':
53
+
return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, prompt);
54
case 'anthropic':
55
// apiKey is guaranteed by check above
56
return await callAnthropic(
···
59
baseUrl,
60
buffer,
61
mimeType,
62
+
prompt,
63
);
64
default:
65
console.warn(`[AI] ⚠️ Unknown provider: ${provider}`);
···
71
}
72
}
73
74
+
const ALT_TEXT_CONTEXT_MAX_CHARS = 400;
75
+
76
+
function buildAltTextPrompt(contextText: string): string {
77
+
const normalized = contextText.replace(/\s+/g, ' ').trim();
78
+
const trimmed =
79
+
normalized.length > ALT_TEXT_CONTEXT_MAX_CHARS
80
+
? `${normalized.slice(0, ALT_TEXT_CONTEXT_MAX_CHARS).trim()}...`
81
+
: normalized;
82
+
83
+
return [
84
+
'Write alt text (1-2 sentences).',
85
+
'Describe only what is visible.',
86
+
'Use context to identify people/places/objects if relevant.',
87
+
'Include key names for search.',
88
+
'No hashtags or emojis.',
89
+
`Context: "${trimmed}"`,
90
+
].join(' ');
91
+
}
92
+
93
async function callGemini(
94
apiKey: string,
95
modelName: string,
96
buffer: Buffer,
97
mimeType: string,
98
+
prompt: string,
99
): Promise<string | undefined> {
100
const genAI = new GoogleGenerativeAI(apiKey);
101
const model = genAI.getGenerativeModel({ model: modelName });
102
0
0
0
0
103
const result = await model.generateContent([
104
prompt,
105
{
···
119
baseUrl: string | undefined,
120
buffer: Buffer,
121
mimeType: string,
122
+
prompt: string,
123
): Promise<string | undefined> {
124
const url = baseUrl
125
? `${baseUrl.replace(/\/+$/, '')}/chat/completions`
···
135
content: [
136
{
137
type: 'text',
138
+
text: prompt,
139
},
140
{
141
type: 'image_url',
···
174
baseUrl: string | undefined,
175
buffer: Buffer,
176
mimeType: string,
177
+
prompt: string,
178
): Promise<string | undefined> {
179
const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages';
180
···
197
},
198
{
199
type: 'text',
200
+
text: prompt,
201
},
202
],
203
},