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>
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
TONE_KEYS,
|
|
TONE_PROFILES,
|
|
resolveTone,
|
|
type ToneKey,
|
|
} from '../src/config/tone-profiles.js';
|
|
|
|
describe('Tone profiles', () => {
|
|
describe('TONE_KEYS', () => {
|
|
it('includes default', () => {
|
|
expect(TONE_KEYS).toContain('default');
|
|
});
|
|
|
|
it('includes all expected profile keys', () => {
|
|
const expected: ToneKey[] = [
|
|
'default',
|
|
'dominant',
|
|
'submissive',
|
|
'playful',
|
|
'direct',
|
|
'gentle',
|
|
'kink-friendly',
|
|
'teasing',
|
|
];
|
|
expect(TONE_KEYS).toEqual(expect.arrayContaining(expected));
|
|
});
|
|
|
|
it('defines no duplicate keys', () => {
|
|
expect(new Set(TONE_KEYS).size).toBe(TONE_KEYS.length);
|
|
});
|
|
});
|
|
|
|
describe('TONE_PROFILES', () => {
|
|
for (const key of TONE_KEYS) {
|
|
it(`has a profile for ${key}`, () => {
|
|
expect(TONE_PROFILES[key]).toBeDefined();
|
|
expect(TONE_PROFILES[key].id).toBe(key);
|
|
expect(typeof TONE_PROFILES[key].label).toBe('string');
|
|
expect(typeof TONE_PROFILES[key].promptGuidance).toBe('string');
|
|
});
|
|
}
|
|
|
|
it('default profile has empty guidance', () => {
|
|
expect(TONE_PROFILES.default.promptGuidance).toBe('');
|
|
});
|
|
|
|
it('non-default profiles have non-empty guidance', () => {
|
|
for (const key of TONE_KEYS) {
|
|
if (key === 'default') continue;
|
|
expect(TONE_PROFILES[key].promptGuidance.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('resolveTone', () => {
|
|
it('returns default for unknown tone string', () => {
|
|
const result = resolveTone('nonexistent');
|
|
expect(result.id).toBe('default');
|
|
});
|
|
|
|
it('returns the matching profile for valid keys', () => {
|
|
expect(resolveTone('dominant').id).toBe('dominant');
|
|
expect(resolveTone('submissive').id).toBe('submissive');
|
|
expect(resolveTone('kink-friendly').id).toBe('kink-friendly');
|
|
expect(resolveTone('gentle').id).toBe('gentle');
|
|
expect(resolveTone('teasing').id).toBe('teasing');
|
|
expect(resolveTone('playful').id).toBe('playful');
|
|
expect(resolveTone('direct').id).toBe('direct');
|
|
});
|
|
});
|
|
});
|