This commit is contained in:
jjbrunton committed 2026-01-04 19:16:31 +00:00
commit c552956516
23 files changed
+5193

No files matched your search

+173
View File
@@ -0,0 +1,173 @@
import OpenAI from 'openai';
import type { Config } from '../config/index.js';
import type { Logger } from '../utils/logger.js';
export type DetectedGender = 'male' | 'female' | 'other' | 'unknown';
export interface ImageAnalysisResult {
description: string;
detectedGender: DetectedGender;
}
export class OpenRouterClient {
private client: OpenAI;
constructor(
private config: Config,
private logger: Logger
) {
this.client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: config.openrouter.apiKey,
defaultHeaders: {
'HTTP-Referer': 'https://github.com/reddit-bot',
'X-Title': 'Reddit Bot',
},
});
}
async detectGenderOnly(imageBase64: string, mimeType: string): Promise<DetectedGender> {
try {
this.logger.debug('Quick gender detection...');
const response = await this.client.chat.completions.create({
model: this.config.openrouter.model,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'What is the gender of the person in this image? Reply with ONLY one word: male, female, other, or unknown',
},
{
type: 'image_url',
image_url: {
url: `data:${mimeType};base64,${imageBase64}`,
},
},
],
},
],
max_tokens: 10,
});
const result = response.choices[0]?.message?.content?.toLowerCase().trim() || 'unknown';
if (result.includes('female')) return 'female';
if (result.includes('male') && !result.includes('female')) return 'male';
if (result.includes('other')) return 'other';
return 'unknown';
} catch (error) {
this.logger.error('Gender detection failed:', error);
return 'unknown';
}
}
async analyzeImage(imageBase64: string, mimeType: string): Promise<ImageAnalysisResult> {
try {
this.logger.debug('Analyzing image with AI...');
const genderPrompt = `First, identify the gender of the person in this image. Respond with EXACTLY one of these on the first line: GENDER:male, GENDER:female, GENDER:other, or GENDER:unknown
Then provide your analysis:
${this.config.openrouter.imageAnalysisPrompt}`;
const response = await this.client.chat.completions.create({
model: this.config.openrouter.model,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: genderPrompt,
},
{
type: 'image_url',
image_url: {
url: `data:${mimeType};base64,${imageBase64}`,
},
},
],
},
],
max_tokens: 600,
});
const fullResponse = response.choices[0]?.message?.content || '';
// Parse gender from response
let detectedGender: DetectedGender = 'unknown';
const genderMatch = fullResponse.match(/GENDER:(male|female|other|unknown)/i);
if (genderMatch) {
detectedGender = genderMatch[1].toLowerCase() as DetectedGender;
}
// Remove gender line from description
const description = fullResponse.replace(/GENDER:(male|female|other|unknown)\n?/i, '').trim();
this.logger.debug(`Image analysis complete - Gender: ${detectedGender}`);
this.logger.debug(`Description: ${description}`);
return { description, detectedGender };
} catch (error) {
this.logger.error('Image analysis failed:', error);
throw error;
}
}
async generateMessage(
imageAnalysis: string,
postTitle: string,
authorName: string,
successfulExamples: string[] = []
): Promise<string> {
try {
this.logger.debug('Generating message...');
let prompt = this.config.openrouter.messageGenerationPrompt
.replace('{analysis}', imageAnalysis)
.replace('{title}', postTitle)
.replace('{author}', authorName);
// Add successful examples if available
if (successfulExamples.length > 0) {
const examplesText = successfulExamples
.map((ex, i) => `Example ${i + 1}:\n${ex}`)
.join('\n\n');
prompt = `Here are some examples of messages that got positive responses:\n\n${examplesText}\n\n---\n\nNow generate a new message in a similar style:\n\n${prompt}`;
this.logger.debug(`Including ${successfulExamples.length} successful examples in prompt`);
}
const response = await this.client.chat.completions.create({
model: this.config.openrouter.model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 300,
temperature: 0.8, // Add some creativity
});
const message = response.choices[0]?.message?.content || '';
this.logger.debug(`Message generated: ${message}`);
return message.trim();
} catch (error) {
this.logger.error('Message generation failed:', error);
throw error;
}
}
async analyzeAndGenerateMessage(
imageBase64: string,
mimeType: string,
postTitle: string,
authorName: string,
successfulExamples: string[] = []
): Promise<{ analysis: ImageAnalysisResult; message: string }> {
const analysis = await this.analyzeImage(imageBase64, mimeType);
const message = await this.generateMessage(analysis.description, postTitle, authorName, successfulExamples);
return { analysis, message };
}
}