Add subreddit tone profiles for message style guidance

Introduces a tone-profile system that lets the processor adapt outgoing
message style per-subreddit (and per-flair) instead of using one tone
for everything. Profiles act as a "tone mic" layered after the base
prompt and strategy override, so they shape voice without redefining
the whole message.

- src/config/tone-profiles.ts defines the catalog (default, dominant,
  submissive, playful, direct, ...) and a resolveTone() helper that
  falls back to the default profile for unknown keys.
- config.yaml maps subreddits to tone keys; the schema in
  src/config/index.ts validates it.
- src/services/processor.ts threads the resolved tone into the prompt
  build, picking based on subreddit and optional link flair.
- src/reddit/api.ts adds link_flair_text to RedditPost so the
  processor can use it for tone selection.
- src/ai/openrouter.ts accepts and forwards the tone-shaped prompt.
- public/index.html surfaces the active tone in the dashboard.
- Tests cover the catalog, config validation, and processor integration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.7 committed 2026-05-22 22:29:01 +01:00
1 parent 0c7df6e1d3
commit 46a213e3e0
10 files changed
+497 -51

No files matched your search

+3
View File
@@ -3,6 +3,7 @@ import fs from 'fs';
import path from 'path';
import YAML from 'yaml';
import dotenv from 'dotenv';
import { TONE_KEYS } from './tone-profiles.js';
dotenv.config();
@@ -10,6 +11,8 @@ export const SubredditConfigSchema = z.object({
name: z.string(),
type: z.enum(['image', 'chat']).default('image'),
weight: z.number().positive().optional(),
tone: z.enum(TONE_KEYS).default('default'),
scripted: z.string().optional(),
});
export type SubredditConfig = z.infer<typeof SubredditConfigSchema>;
+98
View File
@@ -0,0 +1,98 @@
/**
* Tone profiles for subreddit-specific message style guidance.
*
* Each profile defines body-language instructions the LLM uses to adapt the
* message to the subreddit's culture. Profiles are applied AFTER the base
* prompt and strategy override, so they act as a tone mic rather than
* redefining the entire message.
*/
export const TONE_KEYS = [
'default',
'dominant',
'submissive',
'playful',
'direct',
'gentle',
'kink-friendly',
'teasing',
] as const;
export type ToneKey = (typeof TONE_KEYS)[number];
export interface ToneProfile {
id: ToneKey;
/** One-line label shown in logs. */
label: string;
/** Instructions injected into the prompt to shape the AI's tone. */
promptGuidance: string;
}
export const TONE_PROFILES: Record<ToneKey, ToneProfile> = {
default: {
id: 'default',
label: 'Standard',
promptGuidance: '',
},
dominant: {
id: 'dominant',
label: 'Dominant',
promptGuidance:
'STYLE TONE: Be direct, firm, and commanding. Use imperative language ("send me", "I want", "show me"). ' +
'Don\'t ask hesitantly — tell. Don\'t apologise or hedge. ' +
'Be clear about what you want from her. Make it feel like an order wrapped in lust, not a request.',
},
submissive: {
id: 'submissive',
label: 'Submissive',
promptGuidance:
'STYLE TONE: Be deferential and eager to please. Ask what she wants and how you can make her feel good. ' +
'Use language like "I\'d love to" "please" "if you\'d let me". ' +
'Show enthusiasm for following her lead. Be warm, attentive, and completely focused on her pleasure.',
},
playful: {
id: 'playful',
label: 'Playful',
promptGuidance:
'STYLE TONE: Be fun, teasing, and light-hearted. Make her smile. Use playful challenges, light teasing, ' +
'and cheeky banter. Keep it hot but with a grin — like you\'re having fun while flirting.',
},
direct: {
id: 'direct',
label: 'Direct',
promptGuidance:
'STYLE TONE: Cut straight to the point. No preamble, no dance. Say exactly what you noticed and what you want. ' +
'Be honest and raw — she posted nudes to get a reaction, give her one. One or two sentences max.',
},
gentle: {
id: 'gentle',
label: 'Gentle',
promptGuidance:
'STYLE TONE: Be warm, complimenting, and let her feel desired without being crude. ' +
'Focus on how her body makes you feel rather than explicit demands. ' +
'Use phrases like "you\'re amazing", "I love your curves", "you look incredible". ' +
'Still flirty and sexual but in a softer, more admiring way.',
},
'kink-friendly': {
id: 'kink-friendly',
label: 'Kink-Friendly',
promptGuidance:
'STYLE TONE: Be shameless and open about kinks. This is a kink-friendly subspace — ' +
'don\'t hold back. Use explicit language freely. If the post hints at something specific, ' +
'respond to that directly. Be bold about desires, into anything vibe. ' +
'Reference kink-aware language naturally (dom/sub, bdsm, control, discipline, force) if it fits.',
},
teasing: {
id: 'teasing',
label: 'Teasing',
promptGuidance:
'STYLE TONE: Set up a payoff but don\'t deliver it straight away. Tease, provoke, make her ask for more. ' +
'Use challenges ("bet you won\'t"), withhold compliments slightly, make her work for it. ' +
'Leave her wanting to prove something or respond.',
},
};
export function resolveTone(tone: string): ToneProfile {
const key = TONE_KEYS.find((k) => k === tone) ?? 'default';
return TONE_PROFILES[key];
}