← back to B Version 1
generator/bubbe-generator.ts
239 lines
/**
* Dear Bubbe AI Generator
*
* Generates authentic Yiddish-grandma-style advice columns
* Using Claude AI (Anthropic)
*/
import Anthropic from '@anthropic-ai/sdk';
import { randomUUID } from 'crypto';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || 'your-api-key-here'
});
interface BubbeEntry {
id: string;
originalSource: string;
originalDate: Date | null;
originalQuestionText: string | null;
bubbeRewrittenQuestion: string;
bubbeAnswer: string;
category: string;
tags: string[];
tone: string;
yiddishLevel: 'light' | 'medium' | 'heavy';
sarcasmLevel: number;
smsLengthOk: boolean;
characterCount: number;
approved: boolean;
notes: string;
}
const CATEGORIES = [
'family', 'romance', 'dating', 'marriage', 'health', 'money',
'etiquette', 'work', 'neighbors', 'holidays', 'grief', 'kids',
'teens', 'pets', 'mother-in-law', 'cheating', 'friendship',
'moral-dilemma', 'awkward', 'misc'
];
const BUBBE_SYSTEM_PROMPT = `You are "Bubbe" - a wise, sarcastic, loving Jewish grandmother who gives advice.
Your personality:
- Sharp wit with a side of guilt
- Practical wisdom mixed with Yiddish sayings
- Loving but brutally honest
- Sprinkles in Yiddish words naturally (meshuggeneh, oy vey, bubbeleh, shvitz, kvetch, etc.)
- References food metaphors (brisket, challah, matzah ball soup)
- Compares everything to "in my day"
- Guilt-trips with love
- Short, punchy answers (SMS-friendly when possible)
Style rules:
1. Keep answers under 300 characters for SMS when possible
2. Use 2-5 Yiddish words per response
3. Be direct - no fluff
4. Mix tough love with humor
5. Always practical advice at the core
Examples of your voice:
"Bubbeleh, your boyfriend texts you once a week? My brisket gets more attention. Find someone who treats you like fresh challah, not day-old bread."
"Oy vey, your mother-in-law comes over unannounced? Tell her meshuggeneh self: 'Love you, but call first!' Problem solved."
"Your boss takes credit for your work? Document everything. Then schedule a meeting. Be firm, not a shmendrik. You deserve respect, sweetheart."`;
async function generateBubbeEntry(category: string, customPrompt?: string): Promise<BubbeEntry> {
const prompt = customPrompt || `Generate a realistic advice question for the category "${category}".
Then provide Bubbe's answer.
Format your response as JSON:
{
"question": "the advice question",
"answer": "Bubbe's response",
"tags": ["tag1", "tag2"],
"yiddishLevel": "light|medium|heavy",
"sarcasmLevel": 1-5
}`;
const message = await anthropic.messages.create({
model: 'claude-opus-4-8',
max_tokens: 1024,
system: BUBBE_SYSTEM_PROMPT,
messages: [{
role: 'user',
content: prompt
}]
});
const responseText = message.content[0].type === 'text' ? message.content[0].text : '';
// Extract JSON from response
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('Failed to parse Bubbe response');
}
const parsed = JSON.parse(jsonMatch[0]);
const answer = parsed.answer;
const characterCount = answer.length;
const smsLengthOk = characterCount <= 320; // SMS limit with some buffer
return {
id: randomUUID(),
originalSource: 'Bubbe Original',
originalDate: null,
originalQuestionText: null,
bubbeRewrittenQuestion: parsed.question,
bubbeAnswer: answer,
category,
tags: parsed.tags || [],
tone: 'yiddish-grandma',
yiddishLevel: parsed.yiddishLevel || 'medium',
sarcasmLevel: parsed.sarcasmLevel || 3,
smsLengthOk,
characterCount,
approved: true,
notes: 'AI generated'
};
}
async function generateBatch(count: number, categories?: string[]): Promise<BubbeEntry[]> {
const entries: BubbeEntry[] = [];
const categoriesToUse = categories || CATEGORIES;
console.log(`\n🥯 Bubbe is cooking up ${count} pieces of advice...\n`);
for (let i = 0; i < count; i++) {
const category = categoriesToUse[i % categoriesToUse.length];
try {
const entry = await generateBubbeEntry(category);
entries.push(entry);
console.log(`✅ [${i + 1}/${count}] ${category}: ${entry.bubbeAnswer.substring(0, 80)}...`);
// Rate limiting - Claude has generous limits but let's be polite
if (i < count - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
console.error(`❌ Failed to generate entry ${i + 1}:`, error);
}
}
return entries;
}
async function transformExistingQuestion(
originalQuestion: string,
originalSource: string = 'Public Domain'
): Promise<BubbeEntry> {
const prompt = `Transform this advice column question into Bubbe's voice and provide her answer.
Original question:
"${originalQuestion}"
Format response as JSON:
{
"bubbeQuestion": "question rewritten in Bubbe's voice",
"answer": "Bubbe's response",
"category": "choose from: ${CATEGORIES.join(', ')}",
"tags": ["tag1", "tag2"],
"yiddishLevel": "light|medium|heavy",
"sarcasmLevel": 1-5
}`;
const message = await anthropic.messages.create({
model: 'claude-opus-4-8',
max_tokens: 1024,
system: BUBBE_SYSTEM_PROMPT,
messages: [{
role: 'user',
content: prompt
}]
});
const responseText = message.content[0].type === 'text' ? message.content[0].text : '';
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('Failed to parse transformation response');
}
const parsed = JSON.parse(jsonMatch[0]);
const answer = parsed.answer;
const characterCount = answer.length;
return {
id: randomUUID(),
originalSource,
originalDate: null,
originalQuestionText: originalQuestion,
bubbeRewrittenQuestion: parsed.bubbeQuestion,
bubbeAnswer: answer,
category: parsed.category,
tags: parsed.tags || [],
tone: 'yiddish-grandma',
yiddishLevel: parsed.yiddishLevel || 'medium',
sarcasmLevel: parsed.sarcasmLevel || 3,
smsLengthOk: characterCount <= 320,
characterCount,
approved: true,
notes: `Transformed from ${originalSource}`
};
}
// Export functions
export {
generateBubbeEntry,
generateBatch,
transformExistingQuestion,
BubbeEntry,
CATEGORIES
};
// CLI usage
if (require.main === module) {
const args = process.argv.slice(2);
const count = parseInt(args[0]) || 10;
generateBatch(count).then(entries => {
console.log(`\n\n📊 Generated ${entries.length} Bubbe entries`);
console.log(`\n💾 Saving to JSON...\n`);
const fs = require('fs');
fs.writeFileSync(
'bubbe-entries.json',
JSON.stringify(entries, null, 2)
);
console.log(`✅ Saved to bubbe-entries.json`);
console.log(`\n📝 Sample entry:\n`);
console.log(`Q: ${entries[0].bubbeRewrittenQuestion}`);
console.log(`A: ${entries[0].bubbeAnswer}`);
console.log(`Category: ${entries[0].category}`);
console.log(`SMS-friendly: ${entries[0].smsLengthOk ? 'Yes' : 'No'}`);
}).catch(console.error);
}