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:
1 parent
0c7df6e1d3
commit
46a213e3e0
10 files changed
+497
-51
No files matched your search
+38
-1
@@ -41,6 +41,43 @@ describe('ConfigSchema', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('applies default tone to subreddits', () => {
|
||||
const result = ConfigSchema.safeParse(minimalConfig({ reddit: { subreddits: ['test'] } }));
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.reddit.subreddits[0].tone).toBe('default');
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts valid tone values', () => {
|
||||
for (const tone of ['default', 'dominant', 'submissive', 'playful', 'direct', 'gentle', 'kink-friendly', 'teasing']) {
|
||||
const result = ConfigSchema.safeParse({
|
||||
reddit: { subreddits: [{ name: 'test', tone }] },
|
||||
openrouter: { apiKey: 'key', imageAnalysisPrompt: 'test', messageGenerationPrompt: 'test' },
|
||||
rateLimit: {}, browser: {}, database: {}, media: {}, strategies: {}, web: {},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid tone', () => {
|
||||
const result = ConfigSchema.safeParse({
|
||||
reddit: { subreddits: [{ name: 'test', tone: 'bananas' }] },
|
||||
openrouter: { apiKey: 'key', imageAnalysisPrompt: 'test', messageGenerationPrompt: 'test' },
|
||||
rateLimit: {}, browser: {}, database: {}, media: {}, strategies: {}, web: {},
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts scripted field', () => {
|
||||
const result = ConfigSchema.safeParse({
|
||||
reddit: { subreddits: [{ name: 'test', scripted: 'Hello {author}' }] },
|
||||
openrouter: { apiKey: 'key', imageAnalysisPrompt: 'test', messageGenerationPrompt: 'test' },
|
||||
rateLimit: {}, browser: {}, database: {}, media: {}, strategies: {}, web: {},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts valid targetGenders', () => {
|
||||
const result = ConfigSchema.safeParse(minimalConfig({
|
||||
reddit: { subreddits: ['test'], targetGenders: ['male', 'female', 'other', 'unknown'] },
|
||||
@@ -306,7 +343,7 @@ openrouter:
|
||||
`);
|
||||
|
||||
const config = loadConfig();
|
||||
expect(config.reddit.subreddits).toEqual([{ name: 'test-sub', type: 'image' }]);
|
||||
expect(config.reddit.subreddits).toEqual([{ name: 'test-sub', type: 'image', tone: 'default' }]);
|
||||
expect(config.reddit.pollIntervalMs).toBe(5000);
|
||||
expect(config.openrouter.apiKey).toBe('yaml-key');
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user