Add hybrid R4R media conversation mode
This commit is contained in:
1 parent
927636ec8b
commit
b73e305cf3
15 files changed
+357
-35
No files matched your search
@@ -77,11 +77,11 @@ export function SettingsEditor({ config, section }: SettingsEditorProps) {
|
||||
reasons.push(`The limit will allow ${validation.data.maxMessagesPerHour} messages per hour.`);
|
||||
}
|
||||
const enablesAutonomousR4R = validation.data.payload.subreddits?.some((subreddit, index) => (
|
||||
subreddit.conversation.mode === 'r4r'
|
||||
&& persistedDraft.subreddits[index]?.conversationMode !== 'r4r'
|
||||
(subreddit.conversation.mode === 'r4r' || subreddit.conversation.mode === 'r4r-media')
|
||||
&& persistedDraft.subreddits[index]?.conversationMode !== subreddit.conversation.mode
|
||||
));
|
||||
if (enablesAutonomousR4R) {
|
||||
reasons.push('Autonomous R4R will send conversational replies without approval for the selected subreddit. Existing conversations keep their snapshotted policy.');
|
||||
reasons.push('Autonomous R4R will send conversational replies without approval for the selected subreddit. The hybrid mode can make one optional image request after rapport. Existing conversations keep their snapshotted policy.');
|
||||
}
|
||||
|
||||
if (reasons.length > 0) {
|
||||
|
||||
@@ -14,8 +14,9 @@ export interface SubredditDraft {
|
||||
weight: string;
|
||||
tone: z.infer<typeof ToneKeySchema>;
|
||||
scripted: string;
|
||||
conversationMode: 'legacy' | 'r4r' | 'manual';
|
||||
conversationMode: 'legacy' | 'r4r' | 'r4r-media' | 'manual';
|
||||
maxAutomatedTurns: string;
|
||||
rapportTurnsBeforeMediaAsk: string;
|
||||
}
|
||||
|
||||
export interface SettingsDraft {
|
||||
@@ -77,6 +78,7 @@ export function createSettingsDraft(config: ConfigResponse): SettingsDraft {
|
||||
scripted: subreddit.scripted ?? '',
|
||||
conversationMode: subreddit.conversation.mode,
|
||||
maxAutomatedTurns: String(subreddit.conversation.maxAutomatedTurns),
|
||||
rapportTurnsBeforeMediaAsk: String(subreddit.conversation.rapportTurnsBeforeMediaAsk),
|
||||
})),
|
||||
advancedConfigJson: config.advanced ? JSON.stringify(config.advanced, null, 2) : '',
|
||||
};
|
||||
@@ -93,9 +95,13 @@ export function validateSettingsDraft(draft: SettingsDraft): SettingsValidationR
|
||||
if (subreddit.weight.trim() !== '' && Number(subreddit.weight) <= 0) {
|
||||
requiredErrors[`subreddits.${index}.weight`] = 'Subreddit weight must be greater than 0.';
|
||||
}
|
||||
if (subreddit.conversationMode === 'r4r' && subreddit.maxAutomatedTurns.trim() === '') {
|
||||
if ((subreddit.conversationMode === 'r4r' || subreddit.conversationMode === 'r4r-media')
|
||||
&& subreddit.maxAutomatedTurns.trim() === '') {
|
||||
requiredErrors[`subreddits.${index}.maxAutomatedTurns`] = 'Maximum automated turns is required.';
|
||||
}
|
||||
if (subreddit.conversationMode === 'r4r-media' && subreddit.rapportTurnsBeforeMediaAsk.trim() === '') {
|
||||
requiredErrors[`subreddits.${index}.rapportTurnsBeforeMediaAsk`] = 'Rapport turns is required.';
|
||||
}
|
||||
});
|
||||
if (Object.keys(requiredErrors).length > 0) return { errors: requiredErrors };
|
||||
|
||||
@@ -128,9 +134,12 @@ export function validateSettingsDraft(draft: SettingsDraft): SettingsValidationR
|
||||
scripted: subreddit.scripted.trim() === '' ? undefined : subreddit.scripted,
|
||||
conversation: {
|
||||
mode: subreddit.conversationMode,
|
||||
maxAutomatedTurns: subreddit.conversationMode === 'r4r'
|
||||
maxAutomatedTurns: subreddit.conversationMode === 'r4r' || subreddit.conversationMode === 'r4r-media'
|
||||
? Number(subreddit.maxAutomatedTurns)
|
||||
: 3,
|
||||
rapportTurnsBeforeMediaAsk: subreddit.conversationMode === 'r4r-media'
|
||||
? Number(subreddit.rapportTurnsBeforeMediaAsk)
|
||||
: 3,
|
||||
},
|
||||
})),
|
||||
});
|
||||
@@ -138,7 +147,9 @@ export function validateSettingsDraft(draft: SettingsDraft): SettingsValidationR
|
||||
if (!parsed.success) {
|
||||
const errors: Record<string, string> = {};
|
||||
for (const issue of parsed.error.issues) {
|
||||
const path = issue.path.join('.');
|
||||
const pathParts = [...issue.path];
|
||||
if (pathParts[0] === 'subreddits' && pathParts[2] === 'conversation') pathParts.splice(2, 1);
|
||||
const path = pathParts.join('.');
|
||||
if (!(path in errors)) errors[path] = issue.message;
|
||||
}
|
||||
return { errors };
|
||||
|
||||
@@ -122,7 +122,7 @@ export function BehaviourSection({ config, draft, errors, onChange }: SectionPro
|
||||
...draft,
|
||||
subreddits: [
|
||||
...draft.subreddits,
|
||||
{ name: '', enabled: true, type: 'image', weight: '1', tone: 'default', scripted: '', conversationMode: 'legacy', maxAutomatedTurns: '3' },
|
||||
{ name: '', enabled: true, type: 'image', weight: '1', tone: 'default', scripted: '', conversationMode: 'legacy', maxAutomatedTurns: '3', rapportTurnsBeforeMediaAsk: '3' },
|
||||
],
|
||||
})}
|
||||
type="button"
|
||||
@@ -202,15 +202,18 @@ export function BehaviourSection({ config, draft, errors, onChange }: SectionPro
|
||||
>
|
||||
<option value="legacy">Legacy media asks</option>
|
||||
<option value="r4r">Autonomous R4R conversation</option>
|
||||
<option value="r4r-media">R4R then one media ask</option>
|
||||
<option value="manual">Manual only</option>
|
||||
</select>
|
||||
<small>{subreddit.conversationMode === 'r4r'
|
||||
? 'Continues general conversation autonomously. It will not request sexual media.'
|
||||
: subreddit.conversationMode === 'r4r-media'
|
||||
? 'Builds rapport autonomously, then makes one optional, non-explicit image request.'
|
||||
: subreddit.conversationMode === 'manual'
|
||||
? 'Incoming replies are marked for human handling and are never answered automatically.'
|
||||
: 'Preserves the existing three-turn media-request flow.'}</small>
|
||||
</label>
|
||||
{subreddit.conversationMode === 'r4r' ? (
|
||||
{subreddit.conversationMode === 'r4r' || subreddit.conversationMode === 'r4r-media' ? (
|
||||
<label className="settings-field">
|
||||
<span>Maximum automated turns</span>
|
||||
<input
|
||||
@@ -227,6 +230,23 @@ export function BehaviourSection({ config, draft, errors, onChange }: SectionPro
|
||||
{errors[`subreddits.${index}.maxAutomatedTurns`] ? <small className="field-error" id={`subreddit-${index}-max-turns-error`}>{errors[`subreddits.${index}.maxAutomatedTurns`]}</small> : null}
|
||||
</label>
|
||||
) : null}
|
||||
{subreddit.conversationMode === 'r4r-media' ? (
|
||||
<label className="settings-field">
|
||||
<span>Rapport turns before media ask</span>
|
||||
<input
|
||||
aria-describedby={errors[`subreddits.${index}.rapportTurnsBeforeMediaAsk`] ? `subreddit-${index}-rapport-turns-error` : undefined}
|
||||
aria-invalid={errors[`subreddits.${index}.rapportTurnsBeforeMediaAsk`] ? 'true' : undefined}
|
||||
aria-label={`Subreddit ${index + 1} rapport turns before media ask`}
|
||||
max="19"
|
||||
min="1"
|
||||
onChange={(event) => onChange(updateSubreddit(draft, index, { ...subreddit, rapportTurnsBeforeMediaAsk: event.target.value }))}
|
||||
step="1"
|
||||
type="number"
|
||||
value={subreddit.rapportTurnsBeforeMediaAsk}
|
||||
/>
|
||||
{errors[`subreddits.${index}.rapportTurnsBeforeMediaAsk`] ? <small className="field-error" id={`subreddit-${index}-rapport-turns-error`}>{errors[`subreddits.${index}.rapportTurnsBeforeMediaAsk`]}</small> : null}
|
||||
</label>
|
||||
) : null}
|
||||
<label className="settings-field scripted-field">
|
||||
<span>Scripted message (optional)</span>
|
||||
<textarea
|
||||
|
||||
Reference in new issue
Block a user