feat: expand outreach targeting and messaging
This commit is contained in:
1 parent
7defff422d
commit
d0a8bd0e2f
40 files changed
+890
-82
No files matched your search
+51
-42
@@ -1,16 +1,21 @@
|
||||
import type { RedditApiClient, RedditPost } from '../reddit/api.js';
|
||||
import type { OpenRouterClient, DetectedGender } from '../ai/openrouter.js';
|
||||
import type { Logger } from '../utils/logger.js';
|
||||
import type { Config } from '../config/index.js';
|
||||
import type { Config, PhysiqueTargeting } from '../config/index.js';
|
||||
import type { StrategySelector, SelectedStrategy } from './strategy-selector.js';
|
||||
import { downloadImage, bufferToBase64, isValidImageMimeType, type DownloadedImage } from '../utils/image.js';
|
||||
import { parseGenderTag } from '../utils/gender-tag.js';
|
||||
import { resolveTone, type ToneProfile, type ToneKey } from '../config/tone-profiles.js';
|
||||
import { evaluatePhysiqueTargeting } from './physique-targeting.js';
|
||||
|
||||
export interface ProcessedPost {
|
||||
post: RedditPost;
|
||||
imageAnalysis: string;
|
||||
detectedGender: DetectedGender;
|
||||
attractivenessScore: number | null;
|
||||
physiqueScore: number | null;
|
||||
physiqueTargeting: PhysiqueTargeting | null;
|
||||
sourceImageUrl: string | null;
|
||||
generatedMessage: string;
|
||||
strategyId: string | null;
|
||||
experimentId: string | null;
|
||||
@@ -150,7 +155,28 @@ export class ProcessorService {
|
||||
this.logger.debug(`Gender check passed: ${detectedGender}`);
|
||||
}
|
||||
|
||||
// Select a strategy for A/B testing
|
||||
// Analyze before strategy selection and opener generation so a rejected
|
||||
// physique score costs only the analysis request.
|
||||
let analysis;
|
||||
try {
|
||||
analysis = await this.aiClient.analyzeImage(base64, imageData.mimeType);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('AI_REFUSAL')) {
|
||||
this.logger.warn(`AI refused to analyze post ${post.id} by u/${post.author} - skipping`);
|
||||
return { skipReason: 'AI refused to analyze image' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const physiqueDecision = evaluatePhysiqueTargeting(
|
||||
analysis.physiqueScore,
|
||||
this.config.reddit.physiqueTargeting,
|
||||
);
|
||||
if (!physiqueDecision.accepted) {
|
||||
this.logger.info(`Skipping post ${post.id} - ${physiqueDecision.reason}`);
|
||||
return { skipReason: physiqueDecision.reason };
|
||||
}
|
||||
|
||||
let selectedStrategy: SelectedStrategy | null = null;
|
||||
if (this.strategySelector) {
|
||||
selectedStrategy = this.strategySelector.selectStrategy();
|
||||
@@ -159,7 +185,6 @@ export class ProcessorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve subreddit-specific tone profile
|
||||
const tone = this.resolveTone(post.subreddit);
|
||||
if (tone.scripted) {
|
||||
this.logger.info(`Using scripted message for r/${post.subreddit}`);
|
||||
@@ -167,47 +192,23 @@ export class ProcessorService {
|
||||
this.logger.info(`Using tone "${tone.profile.label}" for r/${post.subreddit}`);
|
||||
}
|
||||
|
||||
// Full analysis and message generation (only for matching posts)
|
||||
let analysis;
|
||||
let message;
|
||||
try {
|
||||
if (tone.scripted) {
|
||||
// Replace substitution tokens in the scripted prompt with post-specific data
|
||||
let scriptedPrompt = tone.scripted
|
||||
const strategyPrompt = tone.scripted
|
||||
? tone.scripted
|
||||
.replace('{title}', post.title)
|
||||
.replace('{author}', post.author)
|
||||
.replace('{flair}', post.link_flair_text || '(none)');
|
||||
({ analysis, message } = await this.aiClient.analyzeAndGenerateMessage(
|
||||
base64,
|
||||
imageData.mimeType,
|
||||
post.title,
|
||||
post.author,
|
||||
this.successfulExamples,
|
||||
scriptedPrompt,
|
||||
post.link_flair_text ?? undefined,
|
||||
undefined,
|
||||
this.recentMessageProvider('image'),
|
||||
));
|
||||
} else {
|
||||
({ analysis, message } = await this.aiClient.analyzeAndGenerateMessage(
|
||||
base64,
|
||||
imageData.mimeType,
|
||||
post.title,
|
||||
post.author,
|
||||
this.successfulExamples,
|
||||
selectedStrategy?.promptOverride,
|
||||
post.link_flair_text ?? undefined,
|
||||
tone.profile.promptGuidance || undefined,
|
||||
this.recentMessageProvider('image'),
|
||||
));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('AI_REFUSAL')) {
|
||||
this.logger.warn(`AI refused to analyze post ${post.id} by u/${post.author} - skipping`);
|
||||
return { skipReason: 'AI refused to analyze image' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
.replace('{flair}', post.link_flair_text || '(none)')
|
||||
: selectedStrategy?.promptOverride;
|
||||
const message = await this.aiClient.generateMessage(
|
||||
analysis.description,
|
||||
post.title,
|
||||
post.author,
|
||||
this.successfulExamples,
|
||||
strategyPrompt,
|
||||
analysis.attractivenessScore,
|
||||
post.link_flair_text ?? undefined,
|
||||
tone.scripted ? undefined : tone.profile.promptGuidance || undefined,
|
||||
this.recentMessageProvider('image'),
|
||||
);
|
||||
|
||||
if (!message || message.length < 10) {
|
||||
this.logger.warn(
|
||||
@@ -220,6 +221,10 @@ export class ProcessorService {
|
||||
post,
|
||||
imageAnalysis: analysis.description,
|
||||
detectedGender: analysis.detectedGender,
|
||||
attractivenessScore: analysis.attractivenessScore,
|
||||
physiqueScore: analysis.physiqueScore,
|
||||
physiqueTargeting: { ...this.config.reddit.physiqueTargeting },
|
||||
sourceImageUrl: imageUrl,
|
||||
generatedMessage: message,
|
||||
strategyId: selectedStrategy?.id || null,
|
||||
experimentId: selectedStrategy?.experimentId ?? null,
|
||||
@@ -304,6 +309,10 @@ export class ProcessorService {
|
||||
post,
|
||||
imageAnalysis: `[chat] ${post.title}${post.selftext ? ` — ${post.selftext.slice(0, 200)}` : ''}`,
|
||||
detectedGender: detectedGender ?? 'unknown',
|
||||
attractivenessScore: null,
|
||||
physiqueScore: null,
|
||||
physiqueTargeting: null,
|
||||
sourceImageUrl: null,
|
||||
generatedMessage: message,
|
||||
strategyId: selectedStrategy?.id || null,
|
||||
experimentId: selectedStrategy?.experimentId ?? null,
|
||||
|
||||
Reference in new issue
Block a user