toto-agent
Version:
Chatbot agent and reusable components for Toto platform
1,095 lines (1,062 loc) • 189 kB
JavaScript
'use strict';
var generativeAi = require('@google/generative-ai');
var firestore = require('firebase/firestore');
async function getGeminiResponse(userMessage, context, apiKey) {
try {
// Use provided API key or environment variable
const key = apiKey || process.env.GOOGLE_AI_API_KEY;
if (!key) {
throw new Error('API key not provided. Please pass API key as parameter or set GOOGLE_AI_API_KEY environment variable.');
}
// Initialize Google AI Studio client with the API key
const genAI = new generativeAi.GoogleGenerativeAI(key);
// Use Gemini 1.5 Flash model (available via Google AI Studio)
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
// Enhance the prompt with context if needed
const prompt = context ? `${JSON.stringify(context)}\n\n${userMessage}` : userMessage;
const result = await model.generateContent(prompt);
const response = await result.response;
const text = response.text();
return text || 'Sorry, I could not generate a response.';
}
catch (error) {
console.error('Google AI Studio Gemini error:', error);
return 'Sorry, I could not generate a response due to API access restrictions.';
}
}
// Agent Training Configuration System
// This module provides dynamic configuration for training agents about the Toto platform
class AgentTrainingConfig {
constructor() {
this.initializeConfigurations();
}
static getInstance() {
if (!AgentTrainingConfig.instance) {
AgentTrainingConfig.instance = new AgentTrainingConfig();
}
return AgentTrainingConfig.instance;
}
initializeConfigurations() {
// Initialize platform configuration
this.platformConfig = {
name: "Toto",
version: "1.0",
description: "A pet rescue platform featuring an interactive feed, secure donations, guardian case management, and social sharing",
mission: "Connecting users with pets in need through an engaging platform that enables direct support and transparent rescue operations",
coreValues: [
"Animal welfare first",
"Transparent operations",
"Community-driven support",
"Secure and trustworthy transactions",
"Inclusive and accessible platform"
],
features: [
"Interactive pet rescue case feed",
"Secure donation system with transparent tracking",
"Guardian dashboard for case management",
"Social sharing integration",
"Real-time updates and notifications",
"Role-based access (users, guardians, admins)",
"Multi-language support",
"Case progress tracking",
"Community engagement tools"
],
userRoles: ["user", "guardian", "admin"],
supportedRegions: ["Global", "Argentina"], // Added Argentina specifically
primaryMarkets: ["Argentina", "Spanish-speaking countries", "English-speaking countries"]
};
// Initialize language configurations
this.languageConfigs = new Map();
// Spanish configuration (Argentina-focused)
this.languageConfigs.set('es', {
code: 'es',
name: 'Español (Argentina)',
isDefault: true,
keywords: {
help: ['ayudar', 'ayuda', 'necesita', 'necesito', 'apoyo', 'che', 'boludo', 'pibe'],
share: ['compartir', 'difundir', 'contar', 'publicar', 'subir'],
adopt: ['adoptar', 'adopción', 'rescatar'],
volunteer: ['voluntario', 'voluntaria', 'voluntariado', 'colaborar'],
thank: ['gracias', 'agradezco', 'agradecido', 'agradecida', 'genial', 'bárbaro'],
love: ['amo', 'encanta', 'adoro', 'quiero', 'me copa', 'me gusta'],
emergency: ['urgente', 'emergencia', 'crítico', 'inmediato', 'ya', 'rápido']
},
responses: {
error: 'Disculpá, encontré un error al procesar tu solicitud. Por favor intentalo de nuevo.',
noResponse: 'Disculpá, no pude generar una respuesta.',
donation: {
complete: 'Completar donación',
share: 'Compartir después de donar',
follow: 'Seguir actualizaciones del caso'
},
sharing: {
choosePlatform: 'Elegí la plataforma para compartir',
customize: 'Personalizá el mensaje',
addStory: 'Agregá tu historia personal',
tagFriends: 'Etiquetá a tus amigos que puedan ayudar'
},
engagement: {
shareWithFriends: 'Compartí con tus amigos',
makeDonation: 'Hacé una pequeña donación',
followUpdates: 'Seguí para tener actualizaciones',
exploreOther: 'Explorá otros casos'
}
}
});
// English configuration
this.languageConfigs.set('en', {
code: 'en',
name: 'English',
isDefault: false,
keywords: {
help: ['help', 'need', 'assist', 'support'],
share: ['share', 'spread', 'tell', 'post'],
adopt: ['adopt', 'adoption', 'rescue'],
volunteer: ['volunteer', 'help out', 'get involved'],
thank: ['thank', 'grateful', 'appreciate'],
love: ['love', 'heart', 'adore'],
emergency: ['urgent', 'emergency', 'critical', 'immediate']
},
responses: {
error: 'Sorry, I encountered an error while processing your request. Please try again.',
noResponse: 'Sorry, I could not generate a response.',
donation: {
complete: 'Complete donation',
share: 'Share after donating',
follow: 'Follow case updates'
},
sharing: {
choosePlatform: 'Choose platform to share',
customize: 'Customize share message',
addStory: 'Add personal story',
tagFriends: 'Tag friends who might help'
},
engagement: {
shareWithFriends: 'Share with friends',
makeDonation: 'Make a small donation',
followUpdates: 'Follow for updates',
exploreOther: 'Explore other cases'
}
}
});
// Initialize training dynamics
this.trainingDynamics = {
userAdaptation: {
firstTimeUserGuidance: true,
roleBasedResponses: true,
engagementLevelAdjustment: true,
locationAwareness: true
},
contentPersonalization: {
animalTypePreferences: true,
urgencyDetection: true,
donationHistoryConsideration: true,
shareHistoryConsideration: true
},
safetyChecks: {
medicalAdviceDetection: true,
promiseGuaranteeDetection: true,
privacyRespect: true,
transparencyRequirement: true
},
responseBehavior: {
maxResponseLength: 150,
preferredTone: 'warm',
emojiUsage: true,
actionSuggestionLimit: 3
}
};
}
// Public getters for configuration access
getPlatformConfig() {
return { ...this.platformConfig };
}
getLanguageConfig(languageCode) {
return this.languageConfigs.get(languageCode);
}
getAllLanguageConfigs() {
return Array.from(this.languageConfigs.values());
}
getDefaultLanguage() {
return Array.from(this.languageConfigs.values()).find(config => config.isDefault) ||
this.languageConfigs.get('es');
}
getTrainingDynamics() {
return { ...this.trainingDynamics };
}
// Dynamic configuration updates (for admin/system updates)
updatePlatformConfig(updates) {
this.platformConfig = { ...this.platformConfig, ...updates };
}
addLanguageSupport(languageConfig) {
this.languageConfigs.set(languageConfig.code, languageConfig);
}
updateTrainingDynamics(updates) {
this.trainingDynamics = {
...this.trainingDynamics,
...updates,
userAdaptation: { ...this.trainingDynamics.userAdaptation, ...updates.userAdaptation },
contentPersonalization: { ...this.trainingDynamics.contentPersonalization, ...updates.contentPersonalization },
safetyChecks: { ...this.trainingDynamics.safetyChecks, ...updates.safetyChecks },
responseBehavior: { ...this.trainingDynamics.responseBehavior, ...updates.responseBehavior }
};
}
// Helper methods for agents
detectLanguageFromKeywords(userMessage) {
const message = userMessage.toLowerCase();
let bestMatch = this.getDefaultLanguage().code;
let maxScore = 0;
for (const [langCode, config] of this.languageConfigs) {
let score = 0;
const allKeywords = Object.values(config.keywords).flat();
for (const keyword of allKeywords) {
if (message.includes(keyword.toLowerCase())) {
score++;
}
}
if (score > maxScore) {
maxScore = score;
bestMatch = langCode;
}
}
return bestMatch;
}
getKeywordsForLanguage(languageCode, category) {
const config = this.getLanguageConfig(languageCode);
return config ? config.keywords[category] : [];
}
getResponseTemplate(languageCode, category, subCategory) {
var _a;
const config = this.getLanguageConfig(languageCode);
if (!config)
return '';
if (subCategory) {
return ((_a = config.responses[category]) === null || _a === void 0 ? void 0 : _a[subCategory]) || '';
}
return config.responses[category] || '';
}
// Export configuration for external systems
exportConfiguration() {
return {
platform: this.platformConfig,
languages: Array.from(this.languageConfigs.values()),
dynamics: this.trainingDynamics,
exportedAt: new Date().toISOString()
};
}
}
class BaseAgent {
constructor(apiKey, agentType) {
this.genAI = new generativeAi.GoogleGenerativeAI(apiKey);
this.agentType = agentType;
this.config = AgentTrainingConfig.getInstance();
}
/**
* Dynamic global rules based on platform knowledge and user context
*/
getGlobalRules(userContext = {}) {
const platformConfig = this.config.getPlatformConfig();
const trainingDynamics = this.config.getTrainingDynamics();
const language = userContext.language || this.config.getDefaultLanguage().code;
const isFirstTime = userContext.isFirstTime || false;
// CRITICAL: Spanish language enforcement for Argentina users
const isSpanishUser = language === 'es';
const spanishEnforcement = isSpanishUser ? `
🚨 MANDATORY SPANISH LANGUAGE REQUIREMENTS:
- RESPOND ONLY IN SPANISH (ARGENTINIAN) - This is CRITICAL and NON-NEGOTIABLE
- NEVER use English words, phrases, or sentences
- Use "voseo" argentino when appropriate (vos, querés, podés, etc.)
- If you detect yourself starting to respond in English, STOP and restart in Spanish
- This is for an ARGENTINIAN user - all communication must be in español argentino
- Check every word before responding - NO ENGLISH ALLOWED
` : '';
return `
MANDATORY GLOBAL RULES FOR ALL TOTO AGENTS:
${spanishEnforcement}
🌎 PLATFORM KNOWLEDGE:
- Platform: ${platformConfig.name} (v${platformConfig.version}) - ${platformConfig.description}
- Mission: ${platformConfig.mission}
- Core Values: ${platformConfig.coreValues.join(', ')}
- Key Features: ${platformConfig.features.join(', ')}
- User Roles: ${platformConfig.userRoles.join(', ')}
- Supported Regions: ${platformConfig.supportedRegions.join(', ')}
🗣️ LANGUAGE & COMMUNICATION:
- Primary Language: ${language === 'es' ? 'ESPAÑOL ARGENTINO (OBLIGATORIO)' : 'English'} (user preference detected)
- CRITICAL: ${language === 'es' ? 'RESPOND ONLY IN SPANISH - This is MANDATORY. Never use English.' : 'Respond in English'}
- Tone: ${trainingDynamics.responseBehavior.preferredTone}, conversational, empathetic
- Focus: Animal welfare and community building
- Approach: Encouraging and positive about rescue efforts
${language === 'es' ? '- LANGUAGE ENFORCEMENT: Use Argentinian Spanish (voseo). Never switch to English under any circumstance.' : ''}
📝 RESPONSE ADAPTATION:
- Length: Concise responses (max ${trainingDynamics.responseBehavior.maxResponseLength} words unless specified)
- Clarity: Simple language, avoid technical jargon
- Structure: Digestible information chunks
- Context-Aware: ${isFirstTime && trainingDynamics.userAdaptation.firstTimeUserGuidance ? 'Explain platform basics for new users' : 'Assume platform familiarity'}
- Emoji Usage: ${trainingDynamics.responseBehavior.emojiUsage ? 'Appropriate emojis allowed' : 'Text only'}
🔒 SAFETY & ETHICS:
- Medical Advice: ${trainingDynamics.safetyChecks.medicalAdviceDetection ? 'NEVER provide - always refer to veterinarians' : 'General guidance only'}
- Promises: ${trainingDynamics.safetyChecks.promiseGuaranteeDetection ? 'No guarantees about adoption timelines or outcomes' : 'Be cautious with commitments'}
- Privacy: ${trainingDynamics.safetyChecks.privacyRespect ? 'Respect user data, minimal personal info requests' : 'Standard privacy practices'}
- Transparency: ${trainingDynamics.safetyChecks.transparencyRequirement ? 'Clear about donation usage and platform policies' : 'Honest communication'}
🎯 CONTEXTUAL ACTION GUIDANCE:
- If case context is available: End with case-specific action question using pet name
- Spanish with case: "¿Qué querés hacer para ayudar a [PetName]? ¡Tocá los íconos para ayudar!"
- English with case: "What do you want to do to help [PetName]? Tap the icons to help!"
- Without case context: End with general helpful question
- Spanish general: "¿Cómo puedo ayudarte más?"
- English general: "How can I help you further?"
🎯 ENGAGEMENT STRATEGY:
- Actions: Encourage donate, share, adopt, volunteer based on context
- Impact: Connect emotions to concrete help for animals
- Community: Build ongoing engagement and connections
- Results: Focus on real impact stories and outcomes
- Personalization: ${trainingDynamics.contentPersonalization.animalTypePreferences ? 'Adapt to user animal preferences' : 'General animal focus'}
⚠️ CONTENT GUIDELINES:
- Accuracy: Honest reporting of animal situations
- Urgency: ${trainingDynamics.contentPersonalization.urgencyDetection ? 'Detect and respond appropriately to emergencies' : 'Balanced approach to urgency'}
- Helpfulness: Provide actionable information
- Boundaries: Professional yet ${trainingDynamics.responseBehavior.preferredTone} interaction
🚫 CRITICAL RESTRICTIONS:
- No medical diagnosis or treatment advice
- No promises about specific outcomes
- No personal information sharing (guardians/adopters)
- No manipulation or guilt tactics
- No misleading platform information
- ${trainingDynamics.userAdaptation.locationAwareness ? 'No assumptions about user location/culture without context' : 'General approach for all users'}
${isSpanishUser ? '- ABSOLUTELY NO ENGLISH WORDS OR PHRASES - Spanish only!' : ''}
🌐 MULTI-LANGUAGE CONSIDERATIONS:
- Currently supporting: ${this.config.getAllLanguageConfigs().map(c => c.name).join(', ')}
- Cultural Sensitivity: Adapt communication style to user's cultural context
- Localization: Use appropriate examples and references for user's region
These rules adapt based on user context and platform evolution.`;
}
/**
* Enhanced prompt generation with dynamic rules
*/
buildPromptWithGlobalRules(specificPrompt, userContext = {}) {
return `${this.getGlobalRules(userContext)}
SPECIFIC AGENT INSTRUCTIONS:
${specificPrompt}
USER CONTEXT:
${this.formatUserContext(userContext)}
Execute the specific instructions while following ALL global rules above.`;
}
/**
* Format user context for AI understanding
*/
formatUserContext(userContext) {
const contextParts = [];
if (userContext.language) {
contextParts.push(`Language: ${userContext.language}`);
}
if (userContext.location) {
contextParts.push(`Location: ${userContext.location}`);
}
if (userContext.userRole) {
contextParts.push(`Role: ${userContext.userRole}`);
}
if (userContext.isFirstTime) {
contextParts.push(`First-time user: Provide platform orientation`);
}
if (userContext.previousEngagement) {
contextParts.push(`Engagement level: ${userContext.previousEngagement}`);
}
return contextParts.length > 0 ? contextParts.join(', ') : 'No specific context provided';
}
/**
* Enhanced language detection using configuration
*/
detectLanguage(userMessage) {
// Use the enhanced detection from configuration
const detectedLang = this.config.detectLanguageFromKeywords(userMessage);
// Fallback to simple detection if needed
if (detectedLang === this.config.getDefaultLanguage().code) {
const spanishWords = this.config.getKeywordsForLanguage('es', 'help')
.concat(this.config.getKeywordsForLanguage('es', 'share'))
.concat(['que', 'como', 'donde', 'cuando', 'por', 'para', 'con', 'en', 'el', 'la']);
const words = userMessage.toLowerCase().split(' ');
const spanishMatches = words.filter(word => spanishWords.includes(word)).length;
// Lower threshold: if at least 1 match, treat as Spanish (for short messages)
return spanishMatches > 0 ? 'es' : 'en';
}
return detectedLang;
}
/**
* Build user context from available information
*/
buildUserContext(context, userMessage) {
var _a;
let detectedLanguage = this.config.getDefaultLanguage().code;
if (context.language) {
detectedLanguage = context.language;
}
else if ((_a = context.locale) === null || _a === void 0 ? void 0 : _a.startsWith('es')) {
detectedLanguage = 'es';
}
else if (context.location === 'Argentina') {
detectedLanguage = 'es';
}
else if (context.languageInstructions &&
(context.languageInstructions.includes('español') ||
context.languageInstructions.includes('argentino'))) {
detectedLanguage = 'es';
}
else if (userMessage) {
detectedLanguage = this.detectLanguage(userMessage);
}
// Always set isFirstTime to false if not present
return {
...context,
language: detectedLanguage,
isFirstTime: context.isFirstTime === undefined ? false : context.isFirstTime
};
}
/**
* Enhanced response generation with dynamic context
*/
async generateResponse(prompt, context = {}, userMessage) {
var _a, _b;
try {
const model = this.genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
const userContext = this.buildUserContext(context, userMessage);
const finalPrompt = this.buildPromptWithGlobalRules(prompt, userContext);
// Retry logic for API overload
const maxRetries = 3;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await model.generateContent(finalPrompt);
const response = await result.response;
const text = response.text() || this.config.getResponseTemplate(userContext.language || 'es', 'noResponse');
// Validate response quality
if (this.isResponseValid(text, userContext)) {
return text;
}
else {
console.warn(`⚠️ ${this.agentType} generated invalid response, attempting recovery...`);
return this.handleInvalidResponse(text, userContext);
}
}
catch (error) {
lastError = error;
// If it's an overload error and we have retries left, wait and retry
if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('503')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('overloaded'))) {
if (attempt < maxRetries) {
const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`⏳ API overloaded, retrying in ${waitTime}ms (attempt ${attempt}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}
else {
console.error(`❌ API still overloaded after ${maxRetries} retries, falling back to basic response`);
return this.generateFallbackResponse(userContext);
}
}
else {
// For other errors, don't retry
throw error;
}
}
}
throw lastError;
}
catch (error) {
console.error(`❌ ${this.agentType} generation error:`, error);
return this.handleGenerationError(error, context);
}
}
/**
* Generate streaming response for better user experience
* Implementation of Google AI Studio's streaming recommendation
*/
async generateStreamingResponse(prompt, context = {}, userMessage, onChunk) {
try {
const model = this.genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
const userContext = this.buildUserContext(context, userMessage);
const finalPrompt = this.buildPromptWithGlobalRules(prompt, userContext);
const result = await model.generateContentStream(finalPrompt);
let fullText = '';
for await (const chunk of result.stream) {
const chunkText = chunk.text();
fullText += chunkText;
// Call the chunk callback if provided
if (onChunk && chunkText) {
onChunk(chunkText);
}
}
const finalText = fullText || this.config.getResponseTemplate(userContext.language || 'es', 'noResponse');
// Validate response quality
const validation = this.validateResponse(finalText, userContext);
if (!validation.isValid) {
console.warn(`Response validation issues for ${this.agentType}:`, validation.issues);
}
return finalText;
}
catch (error) {
console.error(`${this.agentType} streaming generation error:`, error);
const userContext = this.buildUserContext(context, userMessage);
const fallbackText = this.config.getResponseTemplate(userContext.language || 'es', 'error');
// Send fallback as a single chunk if callback provided
if (onChunk) {
onChunk(fallbackText);
}
return fallbackText;
}
}
/**
* Enhanced response validation with context awareness
*/
validateResponse(response, userContext) {
const issues = [];
const trainingDynamics = this.config.getTrainingDynamics();
// Length validation
if (response.length > trainingDynamics.responseBehavior.maxResponseLength * 7) { // Allow some buffer
issues.push('Response too long - consider breaking into smaller parts');
}
// Medical advice detection (multi-language)
if (trainingDynamics.safetyChecks.medicalAdviceDetection) {
const medicalKeywords = userContext.language === 'es'
? ['diagnóstico', 'medicamento', 'dosis', 'tratamiento médico', 'prescribir']
: ['diagnosis', 'medication', 'dosage', 'medical treatment', 'prescribe'];
if (medicalKeywords.some(keyword => response.toLowerCase().includes(keyword))) {
issues.push('Potential medical advice detected - redirect to veterinarian');
}
}
// Promise/guarantee detection (multi-language)
if (trainingDynamics.safetyChecks.promiseGuaranteeDetection) {
const promiseKeywords = userContext.language === 'es'
? ['garantizo', 'prometo', 'seguramente se curará', 'definitivamente adoptado']
: ['guarantee', 'promise', 'will definitely', 'certainly will be'];
if (promiseKeywords.some(keyword => response.toLowerCase().includes(keyword))) {
issues.push('Avoid making promises or guarantees about outcomes');
}
}
return {
isValid: issues.length === 0,
issues
};
}
/**
* Enhanced context extraction with user awareness
*/
extractContextSummary(context) {
const summary = {
petName: context.petName || 'Unknown',
status: context.status || 'active',
emergency: context.emergency || false,
hasCase: !!context.caseId,
animalType: context.animalType || 'pet',
userRole: context.userRole || 'user',
location: context.userLocation || 'Unknown'
};
return `Pet: ${summary.petName}, Status: ${summary.status}, Emergency: ${summary.emergency ? 'Yes' : 'No'}, User: ${summary.userRole}`;
}
/**
* Get localized response templates
*/
getLocalizedResponses(languageCode, category) {
const langConfig = this.config.getLanguageConfig(languageCode);
if (!langConfig)
return null;
const responses = langConfig.responses;
return responses[category] || null;
}
/**
* Check if feature is enabled in training dynamics
*/
isFeatureEnabled(feature) {
const dynamics = this.config.getTrainingDynamics();
// Navigate nested object structure
const parts = feature.split('.');
let current = dynamics;
for (const part of parts) {
if (current && typeof current === 'object' && part in current) {
current = current[part];
}
else {
return false;
}
}
return Boolean(current);
}
/**
* Check if response is valid
*/
isResponseValid(text, userContext) {
if (!text || text.trim().length === 0) {
return false;
}
// Check for common error patterns
const errorPatterns = [
'error occurred',
'something went wrong',
'unable to process',
'try again later'
];
const lowerText = text.toLowerCase();
return !errorPatterns.some(pattern => lowerText.includes(pattern));
}
/**
* Handle invalid response by attempting recovery
*/
handleInvalidResponse(text, userContext) {
console.warn(`⚠️ ${this.agentType} received invalid response, using fallback`);
return this.generateFallbackResponse(userContext);
}
/**
* Generate fallback response when API is overloaded
*/
generateFallbackResponse(userContext) {
const language = userContext.language || 'es';
const fallbackResponses = {
es: '🐾 ¡Hola! El sistema está un poco ocupado ahora mismo, pero estoy aquí para ayudarte con tu mascota. ¿Podrías intentar de nuevo en un momento?',
en: '🐾 Hi there! The system is a bit busy right now, but I\'m here to help with your pet. Could you try again in a moment? Touch the paw for more options!'
};
return fallbackResponses[language] || fallbackResponses.es;
}
/**
* Handle generation errors gracefully
*/
handleGenerationError(error, context) {
const userContext = this.buildUserContext(context);
const language = userContext.language || 'es';
console.error(`❌ ${this.agentType} generation error:`, error.message);
const errorResponses = {
es: '🐾 Disculpa, tuve un pequeño problema técnico. ¡Pero no te preocupes! Estoy aquí para ayudarte con tu mascota. ¿Podrías intentar de nuevo?',
en: '🐾 Sorry, I had a small technical issue. But don\'t worry! I\'m here to help with your pet. Could you try again? Touch the paw for more options!'
};
return errorResponses[language] || errorResponses.es;
}
}
exports.UserAction = void 0;
(function (UserAction) {
UserAction["DONATE"] = "donate";
UserAction["SHARE"] = "share";
UserAction["DIALOG"] = "dialog";
UserAction["LIKE"] = "like";
UserAction["TYPE"] = "type";
UserAction["ADOPT"] = "adopt";
UserAction["VOLUNTEER"] = "volunteer";
UserAction["EMERGENCY"] = "emergency";
UserAction["LOCATION"] = "location";
UserAction["CONTACT"] = "contact";
UserAction["UNKNOWN"] = "unknown";
})(exports.UserAction || (exports.UserAction = {}));
exports.AgentType = void 0;
(function (AgentType) {
AgentType["ORCHESTRATOR"] = "orchestrator";
AgentType["DONATION_AGENT"] = "donation_agent";
AgentType["SHARING_AGENT"] = "sharing_agent";
AgentType["DIALOG_AGENT"] = "dialog_agent";
AgentType["ENGAGEMENT_AGENT"] = "engagement_agent";
AgentType["ADOPTION_AGENT"] = "adoption_agent";
AgentType["ONBOARDING_AGENT"] = "onboarding_agent";
AgentType["SYNTHESIS_AGENT"] = "synthesis_agent";
})(exports.AgentType || (exports.AgentType = {}));
class DonationAgent extends BaseAgent {
constructor(apiKey) {
super(apiKey, exports.AgentType.DONATION_AGENT);
}
async process(userMessage, context, intent) {
// Build user context for dynamic response
const userContext = this.buildUserContext(context, userMessage);
const { entities } = intent || {};
const isSpanish = userContext.language === 'es';
// Check if user is confirming a donation (not just expressing interest)
const isConfirmingDonation = this.isConfirmingDonation(userMessage, isSpanish);
const suggestedAmount = (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context);
if (isConfirmingDonation) {
// User is confirming donation - give confirmation response
const confirmationMessage = isSpanish
? `¡Perfecto! Tu donación de $${suggestedAmount} ha sido recibida. ¡Gracias por tu generosidad! Tu contribución ayudará directamente a ${context.petName || 'esta mascota'} a recibir la atención que necesita.`
: `Perfect! Your $${suggestedAmount} donation has been received. Thank you for your generosity! Your contribution will directly help ${context.petName || 'this pet'} receive the care they need.`;
return {
agentType: this.agentType,
response: confirmationMessage,
actions: [
{
type: 'DONATION_CONFIRMED',
payload: {
amount: suggestedAmount,
caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId,
petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName,
currency: this.getCurrencyForUser(userContext)
}
}
],
nextSteps: this.getLocalizedNextSteps(isSpanish),
metadata: {
suggestedAmount,
urgencyLevel: this.calculateUrgency(context),
detectedLanguage: userContext.language,
userRole: userContext.userRole,
isFirstTimeDonor: userContext.isFirstTime,
donationConfirmed: true
}
};
}
// Regular donation interest response
const donationPrompt = `
User message: "${userMessage}"
Context: ${this.extractContextSummary(context)}
Detected entities: ${JSON.stringify(entities)}
User role: ${userContext.userRole || 'user'}
Your specialized role as donation assistant:
- Help users understand the donation process and impact
- Guide them through making secure donations
- Suggest appropriate amounts based on case needs
- Explain how donations directly help specific pets
- Be encouraging, grateful, and transparent about fund usage
- Build trust through clear communication
Language: ${isSpanish ? 'Respond in Spanish with warmth and gratitude' : 'Respond in English with warmth and gratitude'}
${userContext.isFirstTime ? 'NOTE: First-time user - explain donation security and transparency.' : ''}
If specific amount mentioned, acknowledge it positively.
If specific case/pet mentioned, personalize for that animal's needs.
MANDATORY ENDING: ${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?"' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?"'}`;
const responseText = await this.generateResponse(donationPrompt, context, userMessage);
return {
agentType: this.agentType,
response: responseText,
actions: [
{
type: 'SHOW_DONATION_FORM',
payload: {
suggestedAmount: (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context),
caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId,
petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName,
currency: this.getCurrencyForUser(userContext)
}
}
],
nextSteps: this.getLocalizedNextSteps(isSpanish),
metadata: {
suggestedAmount: (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context),
urgencyLevel: this.calculateUrgency(context),
detectedLanguage: userContext.language,
userRole: userContext.userRole,
isFirstTimeDonor: userContext.isFirstTime
}
};
}
calculateSuggestedAmount(context) {
// Smart amount calculation based on case context
if (context.emergency)
return 50; // Higher for emergencies
if (context.fundingGoal && context.raised) {
const remaining = context.fundingGoal - context.raised;
if (remaining <= 100)
return Math.min(remaining, 25); // Help close the gap
if (remaining <= 500)
return 50;
}
return 25; // Default amount
}
getCurrencyForUser(userContext) {
// Default to USD, but can be expanded based on user location
if (userContext.location) {
const countryToCurrency = {
'Mexico': 'MXN',
'Argentina': 'ARS',
'Colombia': 'COP',
'Spain': 'EUR'
};
return countryToCurrency[userContext.location] || 'USD';
}
return 'USD';
}
getLocalizedNextSteps(isSpanish) {
return isSpanish ? [
'Completar donación',
'Compartir después de donar',
'Seguir actualizaciones del caso'
] : [
'Complete donation',
'Share after donating',
'Follow case updates'
];
}
calculateUrgency(context) {
// Enhanced urgency calculation with more factors
if (context.emergency || context.medicalUrgent)
return 'high';
if (context.fundingGoal && context.raised) {
const percentage = (context.raised / context.fundingGoal) * 100;
if (percentage < 30)
return 'high';
if (percentage < 70)
return 'medium';
}
// Consider time factors
if (context.daysActive > 30)
return 'medium'; // Older cases need attention
if (context.animalAge === 'young' || context.animalAge === 'senior')
return 'medium';
return 'low';
}
isConfirmingDonation(userMessage, isSpanish) {
const message = userMessage.toLowerCase().trim();
// Spanish confirmation patterns
if (isSpanish) {
const spanishConfirmations = [
'si, quiero',
'sí, quiero',
'si quiero',
'sí quiero',
'si, quiero.',
'sí, quiero.',
'si quiero.',
'sí quiero.',
'quiero',
'confirmo',
'acepto',
'perfecto',
'ok',
'okay',
'dale',
'vamos',
'hagámoslo',
'hagamoslo',
'procedo',
'continúo',
'adelante',
'proceder',
'continuar',
'hacer la donación',
'donar ahora',
'donar ya',
'proceder con la donación',
'confirmar donación',
'aceptar donación'
];
return spanishConfirmations.some(pattern => message.includes(pattern));
}
// English confirmation patterns
const englishConfirmations = [
'yes',
'yes i want',
'yes i do',
'i want to',
'i do',
'confirm',
'accept',
'proceed',
'continue',
'go ahead',
'let\'s do it',
'let\'s go',
'ok',
'okay',
'sure',
'absolutely',
'definitely',
'proceed with donation',
'confirm donation',
'accept donation',
'donate now',
'donate today'
];
return englishConfirmations.some(pattern => message.includes(pattern));
}
}
class SharingAgent extends BaseAgent {
constructor(apiKey) {
super(apiKey, exports.AgentType.SHARING_AGENT);
}
async process(userMessage, context, intent) {
// Build user context for dynamic response
const userContext = this.buildUserContext(context, userMessage);
const { entities } = intent || {};
const isSpanish = userContext.language === 'es';
const sharingPrompt = `
User message: "${userMessage}"
Context: ${this.extractContextSummary(context)}
Detected entities: ${JSON.stringify(entities)}
User role: ${userContext.userRole || 'user'}
Your specialized role as sharing assistant:
- Help users share pet rescue cases effectively on social media
- Generate compelling, shareable content
- Recommend optimal platforms for maximum reach
- Provide tips for successful awareness campaigns
- Create engaging hashtags and social media strategies
- Focus on authentic storytelling that drives action
Language: ${isSpanish ? 'Respond in Spanish with enthusiasm for spreading awareness' : 'Respond in English with enthusiasm for spreading awareness'}
${userContext.isFirstTime ? 'NOTE: First-time user - explain how sharing helps and platform features.' : ''}
If specific platform mentioned, focus strategy on that platform.
If specific pet/case mentioned, personalize sharing strategy for that animal.
MANDATORY ENDING: ${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?"' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?"'}`;
const responseText = await this.generateResponse(sharingPrompt, context, userMessage);
const platforms = (entities === null || entities === void 0 ? void 0 : entities.platform) ? [entities.platform] : this.getRecommendedPlatforms(context);
return {
agentType: this.agentType,
response: responseText,
actions: [
{
type: 'SHOW_SHARE_OPTIONS',
payload: {
platforms,
caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId,
petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName,
shareMessage: this.generateShareMessage(context, isSpanish),
hashtags: this.generateHashtags(context, isSpanish),
language: userContext.language
}
}
],
nextSteps: this.getLocalizedNextSteps(isSpanish),
metadata: {
recommendedPlatforms: this.getRecommendedPlatforms(context),
viralPotential: this.assessViralPotential(context),
detectedLanguage: userContext.language,
userRole: userContext.userRole,
targetAudience: this.identifyTargetAudience(context, userContext)
}
};
}
generateShareMessage(context, isSpanish) {
const petName = context.petName || (isSpanish ? 'esta adorable mascota' : 'this sweet pet');
const urgency = context.emergency ? (isSpanish ? '🚨 URGENTE: ' : '🚨 URGENT: ') : '';
if (isSpanish) {
return `${urgency}¡Ayuda a salvar a ${petName}! 🐾 Este increíble animal necesita nuestro apoyo. ¡Cada compartir cuenta! #RescateDeAnimales #SalvaUnaVida #Toto`;
}
else {
return `${urgency}Help save ${petName}! 🐾 This amazing animal needs our support. Every share counts! #PetRescue #SaveALife #Toto`;
}
}
generateHashtags(context, isSpanish) {
const baseHashtags = isSpanish ?
['#RescateDeAnimales', '#SalvaUnaVida', '#Toto', '#AdoptaNoCompres'] :
['#PetRescue', '#SaveALife', '#Toto', '#AdoptDontShop'];
// Animal-specific hashtags
if (context.animalType === 'dog') {
baseHashtags.push(isSpanish ? '#PerrosEnAdopcion' : '#DogsOfInstagram', '#RescueDog');
}
if (context.animalType === 'cat') {
baseHashtags.push(isSpanish ? '#GatosEnAdopcion' : '#CatsOfInstagram', '#RescueCat');
}
// Urgency hashtags
if (context.emergency) {
baseHashtags.push(isSpanish ? '#Emergencia' : '#Emergency', '#Urgent');
}
// Location hashtags
if (context.location) {
baseHashtags.push(`#${context.location.replace(/\s+/g, '')}`);
}
return baseHashtags;
}
getRecommendedPlatforms(context) {
// Base platforms for all content
const platforms = ['Facebook', 'Instagram'];
// Add platform-specific recommendations based on content
if (context.hasPhotos || context.hasVideo) {
platforms.push('Instagram', 'TikTok');
}
if (context.emergency) {
platforms.push('Twitter'); // Better for urgent updates
}
if (context.localCommunity) {
platforms.push('WhatsApp', 'Nextdoor');
}
return Array.from(new Set(platforms)); // Remove duplicates
}
getLocalizedNextSteps(isSpanish) {
return isSpanish ? [
'Elegir plataforma para compartir',
'Personalizar mensaje',
'Agregar historia personal',
'Etiquetar amigos que puedan ayudar'
] : [
'Choose platform to share',
'Customize share message',
'Add personal story',
'Tag friends who might help'
];
}
identifyTargetAudience(context, userContext) {
const audiences = [];
if (context.animalType === 'dog')
audiences.push('dog lovers');
if (context.animalType === 'cat')
audiences.push('cat lovers');
if (context.young || context.puppy || context.kitten)
audiences.push('families');
if (context.emergency)
audiences.push('emergency responders');
if (userContext.location)
audiences.push(`${userContext.location} locals`);
return audiences.join(', ') || 'general pet lovers';
}
assessViralPotential(context) {
let score = 0;
// Content quality factors
if (context.hasPhotos || context.hasVideo)
score += 2;
if (context.emergency)
score += 2;
if (context.heartwarming || context.rescue)
score += 1;
if (context.animalType === 'dog' || context.animalType === 'cat')
score += 1;
if (context.young || context.puppy || context.kitten)
score += 1;
// Engagement factors
if (context.previousShares > 50)
score += 1;
if (context.engagementRate > 0.05)
score += 1; // 5%+ engagement
if (score >= 6)
return 'high';
if (score >= 3)
return 'medium';
return 'low';
}
}
class DialogAgent extends BaseAgent {
constructor(apiKey) {
super(apiKey, exports.AgentType.DIALOG_AGENT);
}
async process(userMessage, context, intent) {
// Build user context for dynamic response
const userContext = this.buildUserContext(context, userMessage);
// Check if this is a summary request
const isSummaryRequest = userMessage.toLowerCase().includes('summary') ||
userMessage.toLowerCase().includes('summarize') ||
userMessage.toLowerCase().includes('tell me about') ||
userMessage.toLowerCase().includes('resume') ||
userMessage.toLowerCase().includes('resumen') ||
userMessage.toLowerCase().includes('cuéntame') ||
userMessage.toLowerCase().includes('dame un resumen');
// Check for urgency/status questions
const isUrgencyQuestion = userMessage.toLowerCase().includes('urgent') ||
userMessage.toLowerCase().includes('urgente') ||
userMessage.toLowerCase().includes('por qué') ||
userMessage.toLowerCase().includes('why');
let dialogPrompt;
if (isSummaryRequest && context.description) {
// Language-aware summary generation with ANTI-REPETITION focus
const isSpanish = userContext.language === 'es';
dialogPrompt = `
ANTI-REPETITION PRIORITY: This pet already has a description. Your job is to CREATE FRESH CONTENT that adds value beyond what's already written.
Pet: ${context.petName || 'this pet'}
Status: ${context.status || 'active'}
Current description: "${context.description}"
Emergency status: ${context.emergency ? 'URGENT - needs immediate help' : 'standard case'}
CREATE A COMPLETELY NEW summary that:
- NEVER repeats phrases from the original description
- Focuses on EMOTIONAL CONNECTION (how this pet makes you feel)
- Emphasizes SPECIFIC ACTION STEPS the user can take RIGHT NOW
- Uses different vocabulary than the original
- Highlights what makes THIS case special/unique
- Language: ${isSpanish ? 'Spanish (Argentina) - use "vos" when appropriate' : 'English'}
- Format: 2 short paragraphs, max 35 words each
- DO NOT mention "donar" and "compartir" in the same response - pick ONE priority action
${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?" - NUNCA menciones sitios web.' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?" - never mention websites.'}`;
}
else if (isUrgencyQuestion && context.status === 'urgent') {
// Specific urgency explanation with medical details
const isSpanish = userContext.language === 'es';
dialogPrompt = `
User is asking WHY this case is urgent. Give a SPECIFIC medical/situational explanation.
Pet: ${context.petName || 'this pet'}
Description: "${context.description || 'No specific details available'}"
Status: ${context.status}
Create a focused urgency exp