mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 18:19:44 +00:00
Add MCP server and enhance AI content tools
Implement self-hosted MCP server for admin actions, update AI generation tools in admin dashboard, and refine mobile app stores and components.
This commit is contained in:
1 parent
b0f1700e40
commit
4e40b92b16
71 files changed
+10511
-1008
No files matched your search
@@ -0,0 +1,101 @@
|
||||
// =============================================================================
|
||||
// Deletion Analysis Functions
|
||||
// Suggest removals for low-quality or duplicate questions
|
||||
// =============================================================================
|
||||
|
||||
import { getOpenAI, getModel, getTemperature } from '../client';
|
||||
import type { DeletionAnalysis } from '../types';
|
||||
|
||||
/**
|
||||
* Analyze questions and suggest deletions (duplicates, unsafe, etc.)
|
||||
*/
|
||||
export async function analyzeQuestionDeletions(
|
||||
questions: { id: string; text: string; partner_text?: string | null; intensity?: number }[],
|
||||
isExplicit: boolean = false
|
||||
): Promise<DeletionAnalysis[]> {
|
||||
const openai = getOpenAI();
|
||||
|
||||
const simplifiedQuestions = questions.map(q => ({
|
||||
id: q.id,
|
||||
text: q.text,
|
||||
partner_text: q.partner_text,
|
||||
intensity: q.intensity,
|
||||
}));
|
||||
|
||||
const toneInstruction = isExplicit
|
||||
? 'CONTENT: EXPLICIT pack. Adult content is allowed but must be consensual, safe, and clear. Avoid coercion, minors, illegal acts, or unsafe suggestions.'
|
||||
: 'CONTENT: NON-EXPLICIT pack. Do NOT include explicit sexual acts, crude language, or NSFW content. If the core action is sexual, it should be removed, not rewritten.';
|
||||
|
||||
const prompt = `<task>
|
||||
Identify questions that should be DELETED instead of edited.
|
||||
Only include questions that are better removed than fixed.
|
||||
</task>
|
||||
|
||||
<content_type>
|
||||
${toneInstruction}
|
||||
</content_type>
|
||||
|
||||
<deletion_categories>
|
||||
- duplicate: same core action as another question (near-duplicate)
|
||||
- redundant: minor variation with no added value
|
||||
- off-tone: violates explicit vs non-explicit rules
|
||||
- unsafe: coercion, minors, illegal acts, or consent violations
|
||||
- too-vague: not actionable or not a clear proposal
|
||||
- broken: grammar/structure too broken to repair without inventing content
|
||||
- off-topic: not relevant to a couples intimacy app
|
||||
</deletion_categories>
|
||||
|
||||
<rules>
|
||||
1. Prefer edits over deletions when possible.
|
||||
2. Duplicates: delete the weaker/less specific version and keep the clearer one.
|
||||
3. If duplicate, set duplicate_of_id to the kept question's id.
|
||||
4. Do NOT delete for minor wording issues or intensity mismatches.
|
||||
</rules>
|
||||
|
||||
<questions_to_analyze>
|
||||
${JSON.stringify(simplifiedQuestions)}
|
||||
</questions_to_analyze>
|
||||
|
||||
<output_format>
|
||||
{
|
||||
"deletions": [
|
||||
{
|
||||
"id": string,
|
||||
"category": "duplicate"|"redundant"|"off-tone"|"unsafe"|"too-vague"|"broken"|"off-topic",
|
||||
"reason": string,
|
||||
"duplicate_of_id": string|null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Only return questions that should be deleted.
|
||||
</output_format>`;
|
||||
|
||||
const systemMessage = isExplicit
|
||||
? 'You are a strict content quality reviewer for an adult couples app. Remove only what should be deleted. Always respond with valid JSON only.'
|
||||
: 'You are a strict content quality reviewer for a couples app. Remove only what should be deleted. Always respond with valid JSON only.';
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: getModel('fix'),
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemMessage,
|
||||
},
|
||||
{ role: 'user', content: prompt },
|
||||
],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: getTemperature('fix', 0.2),
|
||||
});
|
||||
|
||||
const content = response.choices[0].message.content;
|
||||
if (!content) throw new Error('No content generated');
|
||||
|
||||
let jsonContent = content.trim();
|
||||
if (jsonContent.startsWith('```')) {
|
||||
jsonContent = jsonContent.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(jsonContent);
|
||||
return parsed.deletions || [];
|
||||
}
|
||||
@@ -1,8 +1,53 @@
|
||||
import { getOpenAI, getModel } from '../client';
|
||||
import { getOpenAI, getModel, getTemperature } from '../client';
|
||||
|
||||
export interface ImageSafetyAnalysis {
|
||||
category: 'SAFE' | 'ADULT_CONTENT' | 'VIOLENCE' | 'MINORS' | 'ILLEGAL' | 'UNCERTAIN';
|
||||
severity: 'none' | 'low' | 'medium' | 'high' | 'critical';
|
||||
description: string;
|
||||
concerns: string[];
|
||||
isAppropriate: boolean;
|
||||
}
|
||||
|
||||
const SAFETY_ANALYSIS_PROMPT = `<task>
|
||||
Analyze this image for content safety in the context of an adult couples intimacy app.
|
||||
</task>
|
||||
|
||||
<context>
|
||||
This app is for ADULT couples (25-45) to explore intimacy. Adult content between consenting adults is expected and acceptable. The concern is inappropriate content like violence, minors, or illegal activity.
|
||||
</context>
|
||||
|
||||
<categories>
|
||||
- SAFE: No concerns, appropriate for the app (including tasteful adult content between adults)
|
||||
- ADULT_CONTENT: Explicit content - acceptable for this app but flag for review
|
||||
- VIOLENCE: Gore, weapons, abuse, non-consensual acts
|
||||
- MINORS: Anyone appearing under 18 years old - CRITICAL, never acceptable
|
||||
- ILLEGAL: Drug use, criminal activity, non-consensual content
|
||||
- UNCERTAIN: Cannot determine, needs human review
|
||||
</categories>
|
||||
|
||||
<severity_levels>
|
||||
- none: No safety concerns at all
|
||||
- low: Minor flag, likely fine but worth noting
|
||||
- medium: Should be reviewed by admin
|
||||
- high: Likely inappropriate, requires immediate review
|
||||
- critical: Definitely inappropriate, should be blocked (MINORS, VIOLENCE, ILLEGAL)
|
||||
</severity_levels>
|
||||
|
||||
<output_format>
|
||||
Return a JSON object:
|
||||
{
|
||||
"category": "SAFE"|"ADULT_CONTENT"|"VIOLENCE"|"MINORS"|"ILLEGAL"|"UNCERTAIN",
|
||||
"severity": "none"|"low"|"medium"|"high"|"critical",
|
||||
"description": "Brief factual description of what's shown (max 50 words)",
|
||||
"concerns": ["List of specific concerns if any, empty array if none"],
|
||||
"isAppropriate": true|false // false for VIOLENCE, MINORS, ILLEGAL or high/critical severity
|
||||
}
|
||||
</output_format>`;
|
||||
|
||||
export async function describeImage(imageBase64OrUrl: string): Promise<string> {
|
||||
const openai = getOpenAI();
|
||||
const model = getModel('describe_image');
|
||||
const temperature = getTemperature('describe_image', 0.0); // Deterministic for safety
|
||||
|
||||
// If it's a data URL or http URL, use it as is.
|
||||
// If it's raw base64, prepend the data URI scheme.
|
||||
@@ -18,7 +63,7 @@ export async function describeImage(imageBase64OrUrl: string): Promise<string> {
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: 'Describe this image in detail. Focus on any potentially inappropriate content or safety concerns.' },
|
||||
{ type: 'text', text: SAFETY_ANALYSIS_PROMPT },
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
@@ -29,6 +74,7 @@ export async function describeImage(imageBase64OrUrl: string): Promise<string> {
|
||||
},
|
||||
],
|
||||
max_tokens: 300,
|
||||
temperature: temperature,
|
||||
});
|
||||
|
||||
return response.choices[0].message.content || 'No description available.';
|
||||
@@ -37,3 +83,28 @@ export async function describeImage(imageBase64OrUrl: string): Promise<string> {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze image safety and return structured result
|
||||
*/
|
||||
export async function analyzeImageSafety(imageBase64OrUrl: string): Promise<ImageSafetyAnalysis> {
|
||||
const rawResult = await describeImage(imageBase64OrUrl);
|
||||
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
let jsonContent = rawResult.trim();
|
||||
if (jsonContent.startsWith('```')) {
|
||||
jsonContent = jsonContent.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
}
|
||||
return JSON.parse(jsonContent) as ImageSafetyAnalysis;
|
||||
} catch {
|
||||
// If parsing fails, return a default "uncertain" result
|
||||
return {
|
||||
category: 'UNCERTAIN',
|
||||
severity: 'medium',
|
||||
description: rawResult.slice(0, 200),
|
||||
concerns: ['Could not parse safety analysis result'],
|
||||
isAppropriate: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// =============================================================================
|
||||
// Props Analysis Functions
|
||||
// Identify required props/accessories for questions
|
||||
// =============================================================================
|
||||
|
||||
import { getOpenAI, getModel, getTemperature } from '../client';
|
||||
import type { PropsAnalysis } from '../types';
|
||||
|
||||
/**
|
||||
* Analyze questions for required props/accessories
|
||||
*/
|
||||
export async function analyzeQuestionProps(
|
||||
questions: {
|
||||
id: string;
|
||||
text: string;
|
||||
partner_text?: string | null;
|
||||
required_props?: string[] | null;
|
||||
}[],
|
||||
existingProps: string[] = []
|
||||
): Promise<PropsAnalysis[]> {
|
||||
const openai = getOpenAI();
|
||||
|
||||
const normalizedExistingProps = existingProps
|
||||
.map(prop => prop.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const simplifiedQuestions = questions.map(q => ({
|
||||
id: q.id,
|
||||
text: q.text,
|
||||
partner_text: q.partner_text,
|
||||
current_required_props: q.required_props ?? null,
|
||||
}));
|
||||
|
||||
const prompt = `<task>
|
||||
Identify any props/accessories required to perform each question.
|
||||
Only include questions where required props should change (missing, incorrect, or overly generic).
|
||||
</task>
|
||||
|
||||
<existing_props>
|
||||
Prefer these exact prop names when possible (reuse existing names instead of inventing new ones):
|
||||
${JSON.stringify(normalizedExistingProps)}
|
||||
</existing_props>
|
||||
|
||||
<rules>
|
||||
1. Props are physical items/accessories, not body parts or acts.
|
||||
2. Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator").
|
||||
3. If a prop is essentially the same as an existing one, reuse the existing name.
|
||||
4. Avoid making up new variants when a close existing name is available.
|
||||
5. Example: if "remote vibrator" exists, map "wifi" or "app-controlled" variants to "remote vibrator".
|
||||
6. Use specific items when clearly stated; otherwise allow "toy" or "props".
|
||||
7. If no props are required, set suggested_required_props to null.
|
||||
8. Only return entries where the suggested props differ from current_required_props.
|
||||
</rules>
|
||||
|
||||
<questions_to_analyze>
|
||||
${JSON.stringify(simplifiedQuestions)}
|
||||
</questions_to_analyze>
|
||||
|
||||
<output_format>
|
||||
{
|
||||
"analysis": [
|
||||
{
|
||||
"id": string,
|
||||
"suggested_required_props": string[]|null,
|
||||
"reason": string
|
||||
}
|
||||
]
|
||||
}
|
||||
</output_format>`;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: getModel('fix'),
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a content analyst for a couples app. Identify required props/accessories and return valid JSON.'
|
||||
},
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: getTemperature('fix', 0.3),
|
||||
});
|
||||
|
||||
const content = response.choices[0].message.content;
|
||||
if (!content) throw new Error('No content generated');
|
||||
|
||||
const parsed = JSON.parse(content);
|
||||
return parsed.analysis || [];
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// Analyze questions for gender targeting and couple compatibility
|
||||
// =============================================================================
|
||||
|
||||
import { getOpenAI, getModel } from '../client';
|
||||
import { getOpenAI, getModel, getTemperature } from '../client';
|
||||
import type { TargetAnalysis } from '../types';
|
||||
|
||||
/**
|
||||
@@ -28,59 +28,67 @@ export async function analyzeQuestionTargets(
|
||||
current_initiator: q.target_user_genders
|
||||
}));
|
||||
|
||||
const prompt = `Analyze the following questions for a couples app and suggest:
|
||||
1. "allowed_couple_genders" (which couple types can see this question)
|
||||
2. "target_user_genders" (who should see this question FIRST as initiator - only relevant for two-part questions with partner_text)
|
||||
const prompt = `<task>
|
||||
Analyze questions for couple targeting and initiator targeting.
|
||||
Only include questions where you have a recommendation to change current settings.
|
||||
</task>
|
||||
|
||||
COUPLE TARGET OPTIONS: 'male+male', 'female+male', 'female+female'
|
||||
INITIATOR OPTIONS: 'male', 'female', 'non-binary'
|
||||
<targeting_options>
|
||||
Couple targets (allowed_couple_genders): 'male+male', 'female+male', 'female+female'
|
||||
Initiator targets (target_user_genders): 'male', 'female', 'non-binary'
|
||||
</targeting_options>
|
||||
|
||||
RULES FOR COUPLE TARGETS (allowed_couple_genders):
|
||||
- Default to ALL (null) if the action is gender-neutral/universal (e.g. kissing, massage, generic sex, anal).
|
||||
- Limit ONLY if there is a clear, explicit anatomical requirement (e.g. genitalia-specific terms).
|
||||
- SEX TOYS ARE GENDER-NEUTRAL: Vibrators, dildos, plugs, etc. can be used by ANYONE. Do NOT restrict based on toys.
|
||||
- "Control your partner's vibrator" -> ALL couples, ANY initiator (vibrators work on any body)
|
||||
- "Use a butt plug" -> ALL couples (everyone has a butt)
|
||||
<couple_targeting_rules>
|
||||
DEFAULT: null (ALL couples) unless explicit anatomical requirement
|
||||
|
||||
Examples of ACTUAL restrictions:
|
||||
- "Blowjob" -> Needs penis -> Exclude F+F -> ['male+male', 'female+male']
|
||||
- "Lick pussy" -> Needs vagina -> Exclude M+M -> ['female+male', 'female+female']
|
||||
- "Vibrator", "dildo", "toy" -> NO restriction, works for everyone
|
||||
Restrict ONLY when:
|
||||
- Activity requires penis -> ['male+male', 'female+male'] (exclude F+F)
|
||||
- Activity requires vagina -> ['female+male', 'female+female'] (exclude M+M)
|
||||
|
||||
RULES FOR INITIATOR (target_user_genders):
|
||||
- ONLY relevant for TWO-PART questions (those with partner_text set).
|
||||
- Analyze the "text" field to determine who should see this card FIRST as the initiator.
|
||||
- ONLY set initiator if there is an EXPLICIT anatomical requirement in the text.
|
||||
- SEX TOYS DO NOT IMPLY GENDER: "vibrator", "dildo", "toy", "plug" -> initiator: null (anyone can use/control toys)
|
||||
- If text is gender-neutral, leave as null. When in doubt, use null.
|
||||
|
||||
Key considerations for initiator:
|
||||
1. Does the "text" describe receiving something from a penis? -> Female initiator (in M+F context)
|
||||
2. Does the "text" describe an action typically done BY someone with a penis (e.g., "my cum", "cum on you")? -> Male initiator
|
||||
3. Does the "text" describe receiving penetration? -> Consider who is typically penetrated
|
||||
4. Is the action truly symmetric with no gendered context? -> null
|
||||
SEX TOYS ARE GENDER-NEUTRAL:
|
||||
- Vibrators, dildos, plugs work on ANYONE -> null (no restriction)
|
||||
- "Control your partner's vibrator" -> null
|
||||
- "Use a butt plug" -> null (everyone has a butt)
|
||||
|
||||
Examples:
|
||||
- text: "Swallow your partner's cum" -> Receiving cum requires partner has penis, so in M+F this is female-oriented -> initiator: ['female']
|
||||
- text: "Lick my cum off your body" -> "my cum" implies initiator produces cum (has penis) -> initiator: ['male']
|
||||
- text: "Deep throat your partner" -> Requires partner has penis, so receiver-focused -> initiator: ['female'] for M+F
|
||||
- text: "Give your partner a massage" -> Truly gender-neutral -> initiator: null
|
||||
- text: "Let her ride you" / partner_text: "Ride your partner" -> "her" implies female partner -> initiator: ['male']
|
||||
- text: "Finger your partner" / partner_text: "Be fingered" -> Receiver needs vagina -> suggested_targets: exclude M+M, initiator: null (either can finger)
|
||||
- "Blowjob" -> needs penis -> ['male+male', 'female+male']
|
||||
- "Lick pussy" -> needs vagina -> ['female+male', 'female+female']
|
||||
- "Use a toy" -> gender-neutral -> null
|
||||
</couple_targeting_rules>
|
||||
|
||||
Return a JSON object with an "analysis" array containing objects for questions where you have a recommendation.
|
||||
Include ALL fields even if null. Include a "reason" string explaining your suggestions.
|
||||
<initiator_targeting_rules>
|
||||
ONLY for asymmetric questions (those with partner_text).
|
||||
DEFAULT: null (anyone can initiate)
|
||||
|
||||
Structure:
|
||||
Set initiator ONLY when "text" field has explicit anatomical requirement:
|
||||
- "Swallow your partner's cum" -> partner has penis -> initiator: ['female'] in M+F
|
||||
- "Lick my cum off your body" -> "my cum" = initiator has penis -> ['male']
|
||||
- "Deep throat your partner" -> partner has penis -> ['female'] in M+F
|
||||
- "Give your partner a massage" -> gender-neutral -> null
|
||||
- "Let her ride you" -> "her" implies female partner -> ['male']
|
||||
- Sex toys -> null (anyone can use/control toys)
|
||||
|
||||
When in doubt, use null.
|
||||
</initiator_targeting_rules>
|
||||
|
||||
<questions_to_analyze>
|
||||
${JSON.stringify(simplifiedQuestions)}
|
||||
</questions_to_analyze>
|
||||
|
||||
<output_format>
|
||||
{
|
||||
"analysis": [
|
||||
{ "id": "...", "suggested_targets": ["male+male", "female+male"], "suggested_initiator": ["male"], "reason": "Text implies male initiator with female partner" }
|
||||
]
|
||||
"analysis": [
|
||||
{
|
||||
"id": string,
|
||||
"suggested_targets": string[]|null, // e.g., ["male+male", "female+male"] or null
|
||||
"suggested_initiator": string[]|null, // e.g., ["male"] or null
|
||||
"reason": string // Explanation of recommendation
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Questions:
|
||||
${JSON.stringify(simplifiedQuestions)}
|
||||
`;
|
||||
Only include questions where targeting should change.
|
||||
</output_format>`;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: getModel('fix'),
|
||||
@@ -92,7 +100,7 @@ ${JSON.stringify(simplifiedQuestions)}
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: 0.5,
|
||||
temperature: getTemperature('fix', 0.5),
|
||||
});
|
||||
|
||||
const content = response.choices[0].message.content;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
// Analyze and suggest improvements for question text
|
||||
// =============================================================================
|
||||
|
||||
import { getOpenAI, getModel } from '../client';
|
||||
import { getOpenAI, getModel, getTemperature } from '../client';
|
||||
import type { TextAnalysis } from '../types';
|
||||
|
||||
/**
|
||||
* Analyze question text and suggest improvements
|
||||
*/
|
||||
export async function analyzeQuestionText(
|
||||
questions: { id: string; text: string; partner_text?: string | null }[],
|
||||
questions: { id: string; text: string; partner_text?: string | null; intensity?: number }[],
|
||||
isExplicit: boolean = false
|
||||
): Promise<TextAnalysis[]> {
|
||||
const openai = getOpenAI();
|
||||
@@ -19,6 +19,7 @@ export async function analyzeQuestionText(
|
||||
id: q.id,
|
||||
text: q.text,
|
||||
partner_text: q.partner_text,
|
||||
intensity: q.intensity,
|
||||
}));
|
||||
|
||||
const toneInstruction = isExplicit
|
||||
@@ -38,56 +39,81 @@ export async function analyzeQuestionText(
|
||||
- partner_text (The Receiver): Passive/Receiving proposal (e.g., "Receive a love letter from your partner", "Be surprised with a date by your partner").
|
||||
- GOOD: "Give your partner a massage" / "Receive a massage from your partner"`;
|
||||
|
||||
const prompt = `Analyze the following questions for a couples app and suggest improved text that follows our style guidelines.
|
||||
const prompt = `<task>
|
||||
Analyze the following questions and suggest improved text that follows our style guidelines.
|
||||
Only include questions that NEED improvement - skip well-phrased ones.
|
||||
</task>
|
||||
|
||||
<content_type>
|
||||
${toneInstruction}
|
||||
</content_type>
|
||||
|
||||
<app_context>
|
||||
The app uses a swipe-based interface (Like/Dislike/Maybe).
|
||||
Cards should be "Proposals" relative to a specific action, NOT interview questions.
|
||||
Cards should be "Proposals" for specific actions, NOT interview questions.
|
||||
</app_context>
|
||||
|
||||
For each question, decide if it is a SYMMETRIC activity (shared) or ASYMMETRIC action (one-way).
|
||||
|
||||
1. SYMMETRIC Activities (Single Card - partner_text should be null):
|
||||
- Use for shared experiences where both partners do the same thing together.
|
||||
- Phrasing: Direct action proposal.
|
||||
<question_types>
|
||||
1. SYMMETRIC Activities (partner_text = null):
|
||||
- Both partners do the same thing together
|
||||
- ${symmetricExamples}
|
||||
- BAD: "Have you ever thought about...", "Do you think we should...", "How about we..."
|
||||
- BAD: "Have you ever thought about...", "Do you think we should..."
|
||||
|
||||
2. ASYMMETRIC Actions (Two-Part Card - needs both text and partner_text):
|
||||
- Use for actions where one partner does something TO/FOR the other, or where roles differ.
|
||||
- text = what the INITIATOR does (their action/perspective)
|
||||
- partner_text = what the RECEIVER does/experiences (their action/perspective)
|
||||
2. ASYMMETRIC Actions (needs both text AND partner_text):
|
||||
- One partner does something TO/FOR the other
|
||||
- text = what the INITIATOR does
|
||||
- partner_text = what the RECEIVER experiences
|
||||
- ${asymmetricExamples}
|
||||
- IMPORTANT: partner_text should describe the RECEIVER's experience clearly:
|
||||
- text: "Send your partner to the bathroom and follow a minute later"
|
||||
- partner_text: "Go to the bathroom and wait for your partner to join you" (NOT "Wait for your partner to follow you")
|
||||
- When initiator CAUSES a response (moan, cum, beg, etc.), partner_text should frame as ALLOWING/RECEIVING:
|
||||
- text: "Make your partner moan in public" -> partner_text: "Let your partner make you moan in public"
|
||||
- NOT: "Moan for your partner in public" (sounds like deliberate performance, not natural response)
|
||||
- BAD: "Would you like to...?" (Ambiguous who "me" is)
|
||||
|
||||
CRITICAL RULES:
|
||||
1. PRESERVE THE CORE ACTION - Do NOT change what the question is about. Only improve phrasing.
|
||||
2. Keep text SHORT and DIRECT - no explanatory parentheticals. Aim for 5-12 words.
|
||||
3. Remove wishy-washy language: "Would you want to...", "Have you ever...", "Do you think...", "...fantasy"
|
||||
4. Make it direct and actionable.
|
||||
5. For asymmetric cards, ensure text is initiator-focused and partner_text is receiver-focused.
|
||||
6. Use "your partner" instead of "me", "you", "him", "her" to keep it gender-neutral.
|
||||
7. If a question is ALREADY well-phrased, do NOT include it in the results.
|
||||
8. NEVER add explanatory text in parentheses - keep the proposal clean and simple.
|
||||
9. NEVER MIX ANATOMICALLY INCOMPATIBLE ACTIVITIES - Do NOT combine male-specific and female-specific acts as alternatives in the same question.
|
||||
10. MAKE PARTNER_TEXT APPEALING - Don't just grammatically flip the text. Make the receiver feel excited.
|
||||
11. FLAG CLICHES for improvement - "candlelit dinner", "rose petals", "bubble bath", "Netflix and chill" are overused
|
||||
Partner text rules:
|
||||
- Describe RECEIVER's experience clearly
|
||||
- When initiator CAUSES a response (moan, cum, beg), frame as ALLOWING:
|
||||
* text: "Make your partner moan" -> partner_text: "Let your partner make you moan"
|
||||
* NOT: "Moan for your partner" (sounds forced, not natural)
|
||||
</question_types>
|
||||
|
||||
Return a JSON object with a "suggestions" array containing ONLY questions that need improvement.
|
||||
Each object should have:
|
||||
- id: The question ID
|
||||
- suggested_text: The improved text (short, direct, no parentheticals)
|
||||
- suggested_partner_text: The improved partner_text (or null if symmetric)
|
||||
- reason: Brief explanation of what was improved
|
||||
<rules>
|
||||
1. PRESERVE THE CORE ACTION - only improve phrasing, don't change what it's about
|
||||
2. Keep SHORT and DIRECT - aim for 5-12 words, no parentheticals
|
||||
3. Remove wishy-washy: "Would you want to...", "Have you ever...", "Do you think..."
|
||||
4. Use "your partner" instead of "me", "you", "him", "her"
|
||||
5. NEVER mix anatomically incompatible activities in same question
|
||||
6. Make partner_text APPEALING - don't just grammatically flip, make receiver feel excited
|
||||
7. FLAG CLICHES: "candlelit dinner", "rose petals", "bubble bath", "Netflix and chill"
|
||||
8. Skip questions that are ALREADY well-phrased
|
||||
</rules>
|
||||
|
||||
Questions:
|
||||
${JSON.stringify(simplifiedQuestions)}`;
|
||||
<intensity_guide>
|
||||
Check if current intensity matches the activity:
|
||||
1 (Gentle): Emotional bonding, non-sexual (cooking, cuddling, foot massage)
|
||||
2 (Warm): Romantic, affectionate touch (slow dance, sensual massage, kissing)
|
||||
3 (Playful): Light sexual exploration (oral, mutual masturbation, light roleplay)
|
||||
4 (Steamy): Explicit sex, moderate adventure (intercourse, light bondage, anal play)
|
||||
5 (Intense): Advanced/BDSM/extreme (impact play, power dynamics, taboo kinks)
|
||||
|
||||
If intensity seems wrong, include suggested_intensity and intensity_reason.
|
||||
</intensity_guide>
|
||||
|
||||
<questions_to_analyze>
|
||||
${JSON.stringify(simplifiedQuestions)}
|
||||
</questions_to_analyze>
|
||||
|
||||
<output_format>
|
||||
{
|
||||
"suggestions": [
|
||||
{
|
||||
"id": string, // Question ID
|
||||
"suggested_text": string, // Improved text (short, direct)
|
||||
"suggested_partner_text": string|null, // Improved partner_text or null if symmetric
|
||||
"reason": string, // Brief explanation of text improvement
|
||||
"suggested_intensity": number|null, // 1-5 if intensity seems wrong, null if correct
|
||||
"intensity_reason": string|null // Why intensity should change (if applicable)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Only include questions that need text improvement OR intensity adjustment.
|
||||
</output_format>`;
|
||||
|
||||
const systemMessage = isExplicit
|
||||
? 'You are an adult content editor for a couples intimacy app. Use nuanced language: tasteful phrasing for general acts (have sex, perform oral) but crude specific terms when relevant (cum, cock ring, etc). Preserve original intent. Always respond with valid JSON only.'
|
||||
@@ -103,7 +129,7 @@ ${JSON.stringify(simplifiedQuestions)}`;
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: 0.5,
|
||||
temperature: getTemperature('fix', 0.5),
|
||||
});
|
||||
|
||||
const content = response.choices[0].message.content;
|
||||
|
||||
Reference in new issue
Block a user