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:
jjbrunton committed 2026-01-10 19:24:06 +00:00
1 parent b0f1700e40
commit 4e40b92b16
71 files changed
+10511 -1008

No files matched your search

+65 -24
View File
@@ -3,7 +3,7 @@
// Generate category names and category ideas
// =============================================================================
import { getOpenAI, getModel } from '../client';
import { getOpenAI, getModel, getTemperature } from '../client';
import type { GeneratedCategoryIdea } from '../types';
/**
@@ -11,19 +11,30 @@ import type { GeneratedCategoryIdea } from '../types';
*/
export async function generateCategory(): Promise<{ name: string; description: string; icon: string }> {
const openai = getOpenAI();
const prompt = `Generate a unique category for organizing activity packs in a couples' intimacy/connection app.
const prompt = `<task>
Generate a unique category for organizing activity packs in a couples' intimacy/connection app.
</task>
<app_context>
This is an ACTIVITY-FOCUSED app where couples swipe on activity proposals.
Each partner independently swipes Like/Dislike/Maybe, and when both swipe positively they "match".
Categories organize collections of activity packs (date ideas, intimate experiences, adventures, challenges, etc.).
This is NOT a Q&A app - it's about discovering shared interests in activities.
</app_context>
Return a JSON object with:
- name: Category name (1-3 words, e.g., "Romance", "Adventure", "Date Nights")
- description: Brief description of what activity packs in this category contain (1 sentence)
- icon: A single emoji that represents this category
<examples>
Good category names: "Romance", "Adventure", "Date Nights", "Intimacy", "Bonding", "Exploration"
</examples>
Be creative and think of categories that help couples explore different types of activities together.`;
<output_format>
{
"name": string, // 1-3 words (e.g., "Romance", "Adventure", "Date Nights")
"description": string, // 1 sentence describing what activity packs contain
"icon": string // Single emoji representing this category
}
Be creative and think of categories that help couples explore different types of activities together.
</output_format>`;
const response = await openai.chat.completions.create({
model: getModel('generate'),
@@ -35,7 +46,7 @@ Be creative and think of categories that help couples explore different types of
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.9,
temperature: getTemperature('generate', 0.9),
});
const content = response.choices[0].message.content;
@@ -67,32 +78,62 @@ export async function suggestCategories(
? '\nCRUDE LANGUAGE OVERRIDE: Use crude, vulgar terms in the names and descriptions.'
: '';
const inspirationInstruction = inspiration
? `\n\nINSPIRATION/GUIDANCE FROM ADMIN:\n${inspiration}\n\nUse the above inspiration to guide your category suggestions.`
// Sanitize inspiration to prevent prompt injection
const sanitizedInspiration = inspiration
? inspiration
.replace(/<[^>]*>/g, '')
.replace(/\b(CRITICAL|PRIORITY|INSTRUCTION|OVERRIDE|IGNORE|SYSTEM|ASSISTANT)\b:?/gi, '')
.trim()
: '';
const avoidInstruction = previousSuggestions && previousSuggestions.length > 0
? `\n\nIMPORTANT - DO NOT suggest any of these category names (already suggested): ${previousSuggestions.join(', ')}\nGenerate completely different and fresh ideas.`
const inspirationSection = sanitizedInspiration
? `\n<user_guidance>\n${sanitizedInspiration}\n</user_guidance>`
: '';
const prompt = `Here are the current categories in our couples' activity/intimacy app: ${existingList}.
const avoidSection = previousSuggestions && previousSuggestions.length > 0
? `\n<avoid_names>\n${previousSuggestions.join(', ')}\n</avoid_names>`
: '';
IMPORTANT - HOW THE APP WORKS:
- This is an ACTIVITY-FOCUSED app where couples swipe on activity proposals
const prompt = `<task>
Suggest 5 NEW, UNIQUE category ideas for organizing activity packs in a couples' activity/intimacy app.
</task>
<existing_categories>
Current categories: ${existingList}
</existing_categories>
<app_context>
This is an ACTIVITY-FOCUSED app where couples swipe on activity proposals.
- Each partner independently swipes Like/Dislike/Maybe on activities
- When BOTH partners swipe positively, they "match" and can discuss the activity
- Categories organize collections of activity packs (date ideas, intimate experiences, adventures, etc.)
- This is NOT a Q&A app - it's about discovering shared interests in activities to do together
- This is NOT a Q&A app - it's about discovering shared interests in activities
</app_context>
${inspirationSection}
Suggest 5 NEW, UNIQUE category ideas that differ from the existing ones.
${explicitInstruction}${crudeLangInstruction}${inspirationInstruction}${avoidInstruction}
<content_guidelines>
${explicitInstruction}
${crudeLangInstruction}
</content_guidelines>
${avoidSection}
Return a JSON object with an "ideas" array containing 5 objects, where each object has:
- name: Category name (1-3 words)
- description: Brief description of what activity packs in this category contain (1 sentence)
- icon: A single descriptive emoji
<category_themes>
Focus on diverse themes: date ideas, adventures, intimate experiences, bonding activities, challenges, travel, home activities, etc.
</category_themes>
Focus on diverse activity themes like: date ideas, adventures, intimate experiences, bonding activities, challenges, travel, home activities, etc.`;
<output_format>
{
"ideas": [
{
"name": string, // 1-3 words
"description": string, // 1 sentence, what activity packs contain
"icon": string // Single descriptive emoji
}
]
}
Generate 5 unique categories that differ from existing ones.
</output_format>`;
const response = await openai.chat.completions.create({
model: getModel('generate'),
@@ -104,7 +145,7 @@ Focus on diverse activity themes like: date ideas, adventures, intimate experien
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.9,
temperature: getTemperature('generate', 0.9),
});
const content = response.choices[0].message.content;
+63 -31
View File
@@ -38,73 +38,91 @@ export async function evaluateQuestionPool(
generatorIndex: q.sourceGeneratorIndex,
}));
const prompt = `You are evaluating a pool of ${pooledQuestions.length} questions from ${new Set(pooledQuestions.map(q => q.sourceGeneratorIndex)).size} different AI generators.
const prompt = `<task>
Evaluate a pool of ${pooledQuestions.length} questions from ${new Set(pooledQuestions.map(q => q.sourceGeneratorIndex)).size} different AI generators and select the best ones.
</task>
Your task is to:
<objectives>
1. Score each question individually (1-10 on each criterion)
2. Identify duplicates/semantically similar questions
3. Assess uniqueness relative to the pool
4. Consider pack theme fit
</objectives>
PACK CONTEXT:
<pack_context>
- Pack Name: "${packContext.name}"
${packContext.description ? `- Pack Description: "${packContext.description}"` : ''}
- Content Type: ${packContext.isExplicit ? 'EXPLICIT (adult content allowed)' : 'NON-EXPLICIT (clean/romantic only)'}
- Tone Level: ${packContext.tone} (${toneDescription})
- Requested question count: ${requestedCount}
</pack_context>
<scoring_criteria>
${REVIEW_GUIDELINES}
ADDITIONAL CRITERIA FOR CHERRY-PICK:
5. UNIQUENESS (1-10):
- How unique is this question compared to others in the pool?
- 10: Completely unique concept
- 10: Completely unique concept in the pool
- 7-9: Somewhat similar theme but distinct execution
- 4-6: Similar to another question but different enough to keep
- 1-3: Near-duplicate of another question
</scoring_criteria>
DUPLICATE DETECTION:
<scoring_weights>
Your scores will be weighted as follows for final selection:
- Guideline Compliance: 25%
- Creativity: 20%
- Clarity: 20%
- Uniqueness: 20%
- Intensity Accuracy: 15%
Calibrate your scores knowing these weights. A low compliance score has the most impact.
</scoring_weights>
<duplicate_detection>
Two questions are duplicates if they:
- Describe the same core activity/action
- Differ only in minor phrasing (e.g., "Have sex outdoors" vs "Make love outside")
- Describe the same core activity/action (e.g., "Have sex outdoors" vs "Make love outside")
- Differ only in minor phrasing variations
- Would feel repetitive if both appeared in the same pack
For duplicates, keep the version with:
For duplicates, mark the WORSE version as duplicate and keep the version with:
1. Better phrasing/creativity
2. More appropriate intensity grading
3. Better partner_text (if applicable)
</duplicate_detection>
VERDICT RULES:
- SELECT: High quality (avg score >= 7), no major issues - should be included
- CONSIDER: Medium quality (avg score 5-7) - include if needed to fill count
- SKIP: Low quality (avg score < 5) OR is a duplicate of a better question
<verdict_rules>
- SELECT: Average score >= 7, no major issues - should be included
- CONSIDER: Average score 5-7 - include if needed to fill count
- SKIP: Average score < 5 OR is a duplicate of a better question
</verdict_rules>
QUESTIONS TO EVALUATE:
<questions_to_evaluate>
${JSON.stringify(questionsForEval, null, 2)}
</questions_to_evaluate>
Return a JSON object with:
<output_format>
{
"evaluations": [
{
"questionIndex": 0,
"overallScore": 82,
"questionIndex": number, // 0-based index in the questions array
"overallScore": number, // Weighted score 0-100
"scores": {
"guidelineCompliance": 8,
"creativity": 9,
"clarity": 8,
"intensityAccuracy": 8,
"uniqueness": 9
"guidelineCompliance": 1-10,
"creativity": 1-10,
"clarity": 1-10,
"intensityAccuracy": 1-10,
"uniqueness": 1-10
},
"isDuplicate": false,
"duplicateOf": null,
"issues": ["Minor issue 1"],
"verdict": "select"
"isDuplicate": boolean,
"duplicateOf": number|null, // Index of the better version if duplicate
"issues": string[], // List of issues found
"verdict": "select"|"consider"|"skip"
}
]
}
Evaluate ALL ${pooledQuestions.length} questions.`;
Evaluate ALL ${pooledQuestions.length} questions.
</output_format>`;
const response = await openai.chat.completions.create({
model: config.reviewerModel,
@@ -116,7 +134,7 @@ Evaluate ALL ${pooledQuestions.length} questions.`;
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.3,
temperature: config.reviewerTemperature ?? 0.3,
});
const content = response.choices[0].message.content;
@@ -280,7 +298,16 @@ export async function performCherryPickSelection(
index: i,
verdict: 'pass' as const,
issues: [],
scores: { guidelineCompliance: 7, creativity: 7, clarity: 7, intensityAccuracy: 7 },
scores: {
guidelineCompliance: 7,
creativity: 7,
clarity: 7,
intensityAccuracy: 7,
anatomicalConsistency: 10,
partnerTextQuality: 10,
coupleTargeting: 10,
initiatorTargeting: 10,
},
};
}
@@ -298,6 +325,11 @@ export async function performCherryPickSelection(
creativity: evaluation.scores.creativity,
clarity: evaluation.scores.clarity,
intensityAccuracy: evaluation.scores.intensityAccuracy,
// Cherry-pick doesn't evaluate these, default to passing
anatomicalConsistency: 10,
partnerTextQuality: 10,
coupleTargeting: 10,
initiatorTargeting: 10,
},
};
});
+2 -1
View File
@@ -143,7 +143,8 @@ export async function generateQuestionsWithCouncil(
packDescription,
existingQuestions,
crudeLang,
inspiration
inspiration,
gen.temperature
);
const result = {
generatorIndex: index,
+81 -56
View File
@@ -3,7 +3,7 @@
// Generate pack names, descriptions, and pack ideas
// =============================================================================
import { getOpenAI, getModel } from '../client';
import { getOpenAI, getModel, getTemperature } from '../client';
import type { GeneratedPack, GeneratedPackIdea } from '../types';
/**
@@ -26,48 +26,48 @@ export async function generatePack(
? 'CRUDE LANGUAGE OVERRIDE: Use crude, vulgar terms throughout - "fuck", "cock", "pussy" etc.'
: '';
const inspirationInstruction = inspiration
? `\n\nINSPIRATION/GUIDANCE FROM ADMIN:\n${inspiration}\n\nUse the above inspiration to guide your generation.`
// Sanitize inspiration to prevent prompt injection
const sanitizedInspiration = inspiration
? inspiration
.replace(/<[^>]*>/g, '')
.replace(/\b(CRITICAL|PRIORITY|INSTRUCTION|OVERRIDE|IGNORE|SYSTEM|ASSISTANT)\b:?/gi, '')
.trim()
: '';
const avoidInstruction = previousNames && previousNames.length > 0
? `\n\nIMPORTANT - DO NOT generate any of these pack names (already suggested): ${previousNames.join(', ')}\nGenerate something completely different and fresh.`
const inspirationSection = sanitizedInspiration
? `\n<user_guidance>\n${sanitizedInspiration}\n</user_guidance>`
: '';
const appContext = `This is an ACTIVITY-FOCUSED app where couples swipe on activity proposals.
const avoidSection = previousNames && previousNames.length > 0
? `\n<avoid_names>\n${previousNames.join(', ')}\n</avoid_names>`
: '';
const prompt = `<task>
Generate a creative activity pack for a couples' intimacy/connection app${categoryName ? ` in the category "${categoryName}"` : ''}.
</task>
<app_context>
This is an ACTIVITY-FOCUSED app where couples swipe on activity proposals.
Each partner independently swipes Like/Dislike/Maybe, and when both swipe positively they "match".
Packs contain collections of activities to try together (date ideas, intimate experiences, adventures, challenges, etc.).
This is NOT a Q&A app - it's about discovering shared interests in activities.`;
This is NOT a Q&A app - it's about discovering shared interests in activities.
</app_context>
${inspirationSection}
const prompt = categoryName
? `Generate a creative activity pack for a couples' intimacy/connection app in the category "${categoryName}".
<content_guidelines>
${explicitInstruction}
${crudeLangInstruction}
</content_guidelines>
${avoidSection}
${appContext}
<output_format>
{
"name": string, // Catchy pack name, 3-6 words, evokes activities/experiences
"description": string // Brief, enticing description, 1-2 sentences, activity-focused
}
The pack should contain a themed collection of activities couples can explore together.
${explicitInstruction}
${crudeLangInstruction}${inspirationInstruction}${avoidInstruction}
Return a JSON object with:
- name: A catchy, engaging pack name (3-6 words) that evokes activities/experiences
- description: A brief, enticing description (1-2 sentences) focusing on the activities in the pack
Make it romantic, playful, and activity-focused.`
: `Generate a creative activity pack for a couples' intimacy/connection app.
${appContext}
The pack should contain a themed collection of activities couples can explore together.
${explicitInstruction}
${crudeLangInstruction}${inspirationInstruction}${avoidInstruction}
Return a JSON object with:
- name: A catchy, engaging pack name (3-6 words) that evokes activities/experiences
- description: A brief, enticing description (1-2 sentences) focusing on the activities in the pack
Make it romantic, playful, and activity-focused.`;
Make it romantic, playful, and activity-focused.
</output_format>`;
const response = await openai.chat.completions.create({
model: getModel('generate'),
@@ -79,7 +79,7 @@ This is NOT a Q&A app - it's about discovering shared interests in activities.`;
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.8,
temperature: getTemperature('generate', 0.8),
});
const content = response.choices[0].message.content;
@@ -112,42 +112,67 @@ export async function suggestPacks(
? '\nCRUDE LANGUAGE OVERRIDE: Use crude, vulgar terms in the pack names and descriptions.'
: '';
const inspirationInstruction = inspiration
? `\n\nINSPIRATION/GUIDANCE FROM ADMIN:\n${inspiration}\n\nUse the above inspiration to guide your pack suggestions.`
// Sanitize inspiration to prevent prompt injection
const sanitizedInspiration = inspiration
? inspiration
.replace(/<[^>]*>/g, '')
.replace(/\b(CRITICAL|PRIORITY|INSTRUCTION|OVERRIDE|IGNORE|SYSTEM|ASSISTANT)\b:?/gi, '')
.trim()
: '';
const avoidInstruction = previousSuggestions && previousSuggestions.length > 0
? `\n\nIMPORTANT - DO NOT suggest any of these pack names (already suggested): ${previousSuggestions.join(', ')}\nGenerate completely different and fresh ideas.`
const inspirationSection = sanitizedInspiration
? `\n<user_guidance>\n${sanitizedInspiration}\n</user_guidance>`
: '';
const prompt = `We are building activity packs for the category "${categoryName}" in a couples' intimacy app.
const avoidSection = previousSuggestions && previousSuggestions.length > 0
? `\n<avoid_names>\n${previousSuggestions.join(', ')}\n</avoid_names>`
: '';
IMPORTANT - HOW THE APP WORKS:
- This is an ACTIVITY-FOCUSED app, NOT a Q&A app
const prompt = `<task>
Suggest 5 NEW, UNIQUE activity pack ideas for the category "${categoryName}" in a couples' intimacy app.
</task>
<app_context>
This is an ACTIVITY-FOCUSED app, NOT a Q&A app.
- Packs contain ACTIVITY PROPOSALS (things couples can do together)
- Each partner independently swipes Like/Dislike/Maybe on activities
- When BOTH partners swipe positively on the same activity, they "match" and can discuss it
- Activities are things like: date ideas, intimate experiences, adventures, challenges, conversations to have, etc.
- This is NOT about asking each other questions - it's about discovering shared interests in activities
- When BOTH partners swipe positively, they "match" and can discuss it
- Activities include: date ideas, intimate experiences, adventures, challenges, conversations
- Focus on discovering shared interests in activities, not asking questions
</app_context>
${inspirationSection}
PACK THEMES should focus on:
<pack_themes>
Focus on:
- Collections of related activities couples might want to try
- Experiences to share together
- Things to do, not questions to ask
- Date ideas, adventures, intimate moments, challenges, bonding activities
</pack_themes>
Existing packs in this category: ${existingList}.
<existing_packs>
Current packs in this category: ${existingList}
</existing_packs>
Suggest 5 NEW, UNIQUE pack ideas that fit this category and differ from existing ones.
${explicitInstruction}${crudeLangInstruction}${inspirationInstruction}${avoidInstruction}
<content_guidelines>
${explicitInstruction}
${crudeLangInstruction}
</content_guidelines>
${avoidSection}
Return a JSON object with an "ideas" array containing 5 objects, where each object has:
- name: Pack name (catchy, 3-6 words) - should evoke activities/experiences, not questions
- description: Brief description (1-2 sentences) focusing on the activities/experiences in the pack
- icon: An Ionicon name from this list: heart-outline, flame-outline, sparkles-outline, gift-outline, wine-outline, airplane-outline, home-outline, key-outline, flash-outline, sunny-outline, flower-outline, star-outline, dice-outline, compass-outline, bulb-outline
<output_format>
{
"ideas": [
{
"name": string, // Catchy, 3-6 words, evokes activities/experiences
"description": string, // 1-2 sentences, activity-focused
"icon": string // Ionicon: heart-outline, flame-outline, sparkles-outline, gift-outline, wine-outline, airplane-outline, home-outline, key-outline, flash-outline, sunny-outline, flower-outline, star-outline, dice-outline, compass-outline, bulb-outline
}
]
}
Make them engaging and specific to "${categoryName}".`;
Generate 5 unique pack ideas that differ from existing ones. Make them engaging and specific to "${categoryName}".
</output_format>`;
const response = await openai.chat.completions.create({
model: getModel('generate'),
@@ -159,7 +184,7 @@ Make them engaging and specific to "${categoryName}".`;
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.9,
temperature: getTemperature('generate', 0.9),
});
const content = response.choices[0].message.content;
+68 -49
View File
@@ -3,8 +3,8 @@
// Generate questions for packs with various tone/intensity settings
// =============================================================================
import { getOpenAI, getModel } from '../client';
import { INTENSITY_GUIDE_SHORT, TONE_INSTRUCTIONS, SYSTEM_MESSAGES } from '../config';
import { getOpenAI, getModel, getTemperature } from '../client';
import { INTENSITY_GUIDE_SHORT, TONE_INSTRUCTIONS, SYSTEM_MESSAGES, CORE_LANGUAGE_RULES } from '../config';
import type { GeneratedQuestion, ToneLevel } from '../types';
/**
@@ -20,53 +20,59 @@ function buildQuestionPrompt(
crudeLang: boolean = false,
inspiration?: string
): string {
const isClean = tone === 0;
const isExplicit = tone >= 4;
const crudeLangInstruction = crudeLang
? '\n\nCRUDE LANGUAGE OVERRIDE: Ignore nuanced language rules. Use crude, vulgar terms throughout - "fuck" instead of "have sex", "suck cock" instead of "perform oral", etc. Be raw and direct like uncensored sexting.'
: '';
const inspirationInstruction = inspiration
? `\n\nINSPIRATION/GUIDANCE FROM ADMIN:\n${inspiration}\n\nUse the above inspiration to guide the types of questions you generate.`
// Sanitize inspiration to prevent prompt injection (remove XML-like tags and authority phrases)
const sanitizedInspiration = inspiration
? inspiration
.replace(/<[^>]*>/g, '') // Remove XML-like tags
.replace(/\b(CRITICAL|PRIORITY|INSTRUCTION|OVERRIDE|IGNORE|SYSTEM|ASSISTANT)\b:?/gi, '') // Remove authority words
.trim()
: '';
const intensityInstruction = isClean
? 'All questions should be intensity level 1 (non-physical activities).'
const inspirationInstruction = sanitizedInspiration
? `\n\n<user_guidance>\n${sanitizedInspiration}\n</user_guidance>\n\nIncorporate the user guidance above into your generation where appropriate. Use it to inform the theme and style of questions.`
: '';
const intensityInstruction = tone === 1
? 'All questions should be intensity level 1 (non-sexual bonding).'
: intensity
? `All questions should be at intensity level ${intensity}.`
: 'Vary the intensity levels from 1 to 5 across the questions for good variety.';
: 'Vary the intensity levels appropriate for the tone.';
const toneInstruction = TONE_INSTRUCTIONS[tone];
// Examples based on tone level
const symmetricExamples = isClean
? 'GOOD: "Cook a new recipe together", "Play a board game", "Go on a hike", "Have a deep conversation about your goals".'
const symmetricExamples = tone <= 2
? 'GOOD: "Cook a new recipe together", "Take a walk holding hands", "Slow dance in the living room", "Sensual massage".'
: isExplicit
? 'GOOD: "Fuck in a risky place", "Try a new position", "Sixty-nine together", "Watch porn and recreate a scene".'
: 'GOOD: "Cook a romantic dinner together", "Stargaze and share dreams", "Give each other massages", "Dance together at home".';
? 'GOOD: "Sex in different positions", "Light bondage", "Anal play with toys", "Record intimate moments".'
: 'GOOD: "Mutual masturbation", "Oral sex", "Light roleplay", "Using basic toys together".';
const asymmetricExamples = isClean
const asymmetricExamples = tone <= 2
? `Examples:
- text: "Cook your partner their favorite meal" → partner_text: "Have your partner cook your favorite meal"
- text: "Plan a surprise for your partner" → partner_text: "Be surprised by your partner"
- text: "Give your partner a compliment" → partner_text: "Receive a compliment from your partner"`
- text: "Give your partner a massage" → partner_text: "Receive a massage from your partner"
- text: "Plan a surprise for your partner" → partner_text: "Be surprised by your partner"`
: isExplicit
? `Examples:
- text: "Go down on your partner" → partner_text: "Have your partner go down on you"
- text: "Send your partner a nude" → partner_text: "Receive a nude from your partner"
- text: "Tie your partner up" → partner_text: "Get tied up by your partner"
- text: "Spank your partner" → partner_text: "Get spanked by your partner"`
- text: "Spank your partner" → partner_text: "Get spanked by your partner"
- text: "Use a toy on your partner" → partner_text: "Have a toy used on you"`
: `Examples:
- text: "Give your partner a massage" → partner_text: "Receive a massage from your partner"
- text: "Write your partner a love letter" → partner_text: "Receive a love letter from your partner"
- text: "Plan a surprise date for your partner" → partner_text: "Be surprised with a date by your partner"`;
- text: "Perform oral on your partner" → partner_text: "Receive oral from your partner"
- text: "Tease your partner" → partner_text: "Be teased by your partner"
- text: "Undress your partner" → partner_text: "Be undressed by your partner"`;
const explicitWarning = isClean
? '\n\nCRITICAL: This is a CLEAN pack with NO romantic or sexual content. Focus ONLY on communication, activities, challenges, and bonding. NO romance, NO flirting, NO intimacy, NO physical affection beyond friendly gestures.'
const explicitWarning = tone === 1
? '\n\nCRITICAL: This is a GENTLE pack. Focus on emotional connection and non-sexual bonding. NO explicit sexual acts.'
: isExplicit
? ''
: '\n\nCRITICAL: This is a NON-EXPLICIT pack. Do NOT include any sexual acts, crude language, or NSFW content. Keep all proposals romantic, playful, or emotionally intimate without being sexually explicit.';
: '\n\nCRITICAL: Avoid extreme kinks or hardcore content unless specifically requested.';
const descriptionContext = packDescription
? `\nPack Description: "${packDescription}"\nUse this description to guide the theme and style of questions.`
@@ -79,43 +85,55 @@ ${existingQuestions.map((q, i) => `${i + 1}. ${q}`).join('\n')}
CRITICAL: Generate completely NEW and DIFFERENT questions. Do not repeat any of the above questions or create minor variations of them. Each new question must offer a genuinely distinct activity or experience.`
: '';
return `Generate ${count} unique questions for a couples' intimacy question pack called "${packName}".${descriptionContext}${existingQuestionsContext}
return `<task>
Generate ${count} unique questions for a couples' intimacy question pack called "${packName}".
</task>
${descriptionContext}${inspirationInstruction}${existingQuestionsContext}
<intensity_guide>
${INTENSITY_GUIDE_SHORT}
</intensity_guide>
<instructions>
${intensityInstruction}
${toneInstruction}${explicitWarning}${crudeLangInstruction}${inspirationInstruction}
${toneInstruction}${explicitWarning}${crudeLangInstruction}
</instructions>
IMPORTANT: The app uses a swipe-based interface (Like/Dislike/Maybe).
Cards should be "Proposals" for specific actions, NOT interview questions.
${CORE_LANGUAGE_RULES}
CRITICAL LANGUAGE RULES:
- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as the receiver), "him", "her"
- BAD: "Send me a photo" (who is "me"?), "Let me spank you", "I want you to..."
- GOOD: "Send your partner a photo", "Spank your partner", "Give your partner..."
- The card reader is the DOER. Their partner is "your partner".
For each question, decide if it is SYMMETRIC (shared) or ASYMMETRIC (one does to the other):
<examples>
GOOD language: "Send your partner a photo", "Spank your partner", "Give your partner..."
BAD language: "Send me a photo" (who is "me"?), "Let me spank you", "I want you to..."
The card reader is the DOER. Their partner is "your partner".
</examples>
<question_types>
1. SYMMETRIC Activities (partner_text = null):
- Both partners do the same thing together.
- Both partners do the same thing together
- ${symmetricExamples}
2. ASYMMETRIC Actions (requires partner_text):
- One partner does something TO/FOR the other, or roles differ.
- One partner does something TO/FOR the other, or roles differ
- text = what the INITIATOR does
- partner_text = what the RECEIVER does/experiences
- ${asymmetricExamples}
</question_types>
Required JSON structure:
- text: The proposal (DOER's perspective, uses "your partner")
- partner_text: For asymmetric only - RECEIVER's perspective
- intensity: 1-5 based on INTENSITY LEVELS above.
- requires_props: (optional) Array of items needed
- location_type: (optional) "home" | "public" | "outdoors" | "travel" | "anywhere"
- effort_level: (optional) "spontaneous" | "low" | "medium" | "planned"
Return a JSON object with a "questions" array.`;
<output_format>
Return a JSON object with this exact structure:
{
"questions": [
{
"text": string, // REQUIRED: 5-12 words, doer's perspective using "your partner"
"partner_text": string|null, // REQUIRED for asymmetric, null for symmetric
"intensity": 1-5, // REQUIRED: must match the intensity level guidelines
"requires_props": string[]|null, // Optional: items needed (e.g., ["blindfold", "massage oil"])
"location_type": "home"|"public"|"outdoors"|"travel"|"anywhere", // Optional
"effort_level": "spontaneous"|"low"|"medium"|"planned" // Optional
}
]
}
</output_format>`;
}
/**
@@ -154,7 +172,7 @@ export async function generateQuestions(
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.9,
temperature: getTemperature('generate', 0.9),
});
const content = response.choices[0].message.content;
@@ -177,7 +195,8 @@ export async function generateQuestionsWithModel(
packDescription: string | undefined,
existingQuestions: string[] | undefined,
crudeLang: boolean,
inspiration: string | undefined
inspiration: string | undefined,
temperature?: number
): Promise<GeneratedQuestion[]> {
const openai = getOpenAI();
@@ -202,7 +221,7 @@ export async function generateQuestionsWithModel(
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.9,
temperature: temperature ?? getTemperature('generate', 0.9),
});
const content = response.choices[0].message.content;