semblance_backup/src/utils/personaGenerator.ts
2025-12-19 19:26:16 +00:00

143 lines
No EOL
5.9 KiB
TypeScript
Executable file

import { Persona } from "@/types/persona";
import { aiPersonasApi, personasApi, foldersApi } from "@/lib/api";
/**
* Generate synthetic personas using the AI endpoint
* Using a two-stage approach:
* 1. Generate basic profiles in one call
* 2. Expand each profile into a full persona
*
* @param brief Audience brief to guide persona generation
* @param researchObjective Optional research objective to focus persona goals and scenarios
* @param count Number of personas to generate
* @param file Optional data file to assist in generation (not currently used)
* @param targetFolderId Optional folder ID to assign to generated personas
* @param llmModel Optional LLM model to use for generation
* @returns Array of generated personas
*/
export async function generateSyntheticPersonas(
brief: string,
researchObjective?: string,
count: number,
file?: FileList,
targetFolderId?: string | null,
llmModel?: string,
onTaskIdReceived?: (taskId: string) => void,
temperature?: number
): Promise<{personas: Persona[], task_id?: string, partial_success?: boolean, errors?: any[]}> {
// Debug logging for folder and model
console.log(`generateSyntheticPersonas called with targetFolderId: ${targetFolderId || 'none'}`);
console.log(`🔄 generateSyntheticPersonas using model: ${llmModel || 'gemini-3-pro-preview'}`);
try {
// We'll use the two-stage approach
console.log(`Generating ${count} synthetic personas using two-stage approach...`);
// First, check if the brief is too short to be useful
if (brief.trim().length < 10) {
throw new Error("Audience brief is too short. Please provide more context for better persona generation.");
}
// Upload customer data if files provided
let customerDataSessionId: string | undefined;
if (file && file.length > 0) {
console.log(`Uploading ${file.length} customer data files...`);
try {
const uploadResponse = await aiPersonasApi.uploadCustomerData(file);
customerDataSessionId = uploadResponse.data.session_id;
console.log(`Customer data uploaded with session ID: ${customerDataSessionId}`);
} catch (error) {
console.error('Failed to upload customer data:', error);
throw new Error("Failed to upload customer data files. Please try again.");
}
}
// Use the new unified generation endpoint
console.log('🔥 Calling generatePersonasFull...');
console.log(`🌡️ Using temperature: ${temperature || 1.0}`);
const response = await aiPersonasApi.generatePersonasFull(
brief, // Audience brief
researchObjective, // Research objective
count, // Number of personas to generate
temperature || 1.0, // Temperature (default to 1.0 if not specified)
customerDataSessionId, // Customer data session ID
llmModel, // LLM model
targetFolderId // Target folder ID
);
// Call the task ID callback immediately since unified endpoint returns task_id right away
if (response.data.task_id && onTaskIdReceived) {
console.log('🔥 Unified endpoint - calling onTaskIdReceived with:', response.data.task_id);
onTaskIdReceived(response.data.task_id);
}
if (response.data) {
// Handle partial success (some personas succeeded, some failed)
const hasPartialSuccess = response.data.partial_success === true;
const hasPersonas = response.data.personas && response.data.personas.length > 0;
const hasErrors = response.data.errors && response.data.errors.length > 0;
// Include task_id in response if available
const result = {
personas: response.data.personas,
task_id: response.data.task_id,
partial_success: hasPartialSuccess,
errors: response.data.errors
};
if (hasPersonas) {
console.log(`Generated ${response.data.personas.length} personas with unified endpoint${hasErrors ? ` (${response.data.errors.length} failed)` : ''}`);
// Unified endpoint handles folder assignment and cleanup internally
return result;
} else if (hasErrors) {
// If we have errors but no personas, throw an error
throw new Error(`Failed to generate personas: ${response.data.errors.length} generation attempts failed.`);
} else {
throw new Error("No personas returned from API");
}
} else {
throw new Error("Invalid response format from API");
}
} catch (error) {
// Note: Cleanup is now handled by the unified backend endpoint
// Don't log 499 errors as they are successful cancellations
if (error.response?.status !== 499) {
console.error("Error generating AI personas:", error);
}
throw error;
}
}
/**
* Extract possible personality traits from the research brief
* @param brief The research brief text
* @returns A string of personality traits
*/
function extractPersonalityFromBrief(brief: string): string | undefined {
const traitKeywords = [
{ keyword: 'creativ', trait: 'creative' },
{ keyword: 'innovat', trait: 'innovative' },
{ keyword: 'careful', trait: 'careful' },
{ keyword: 'cautious', trait: 'cautious' },
{ keyword: 'risk', trait: 'risk-taking' },
{ keyword: 'adventur', trait: 'adventurous' },
{ keyword: 'conserv', trait: 'conservative' },
{ keyword: 'tradition', trait: 'traditional' },
{ keyword: 'modern', trait: 'modern' },
{ keyword: 'tech', trait: 'tech-savvy' },
{ keyword: 'social', trait: 'social' },
{ keyword: 'outgoing', trait: 'outgoing' },
{ keyword: 'shy', trait: 'shy' },
{ keyword: 'intro', trait: 'introverted' },
{ keyword: 'extro', trait: 'extroverted' }
];
const briefLower = brief.toLowerCase();
const matchedTraits = traitKeywords
.filter(item => briefLower.includes(item.keyword))
.map(item => item.trait);
return matchedTraits.length > 0 ? matchedTraits.join(', ') : undefined;
}