mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -143,7 +143,8 @@ export async function generateQuestionsWithCouncil(
|
||||
packDescription,
|
||||
existingQuestions,
|
||||
crudeLang,
|
||||
inspiration
|
||||
inspiration,
|
||||
gen.temperature
|
||||
);
|
||||
const result = {
|
||||
generatorIndex: index,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user