LinkedIn and Facebook Persona Services Implementation

This commit is contained in:
ajaysi
2025-10-26 10:06:24 +05:30
parent caeb6e56a9
commit 5866f49325
15 changed files with 868 additions and 281 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Container, Typography, TextField, Paper, Button } from '@mui/material';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core';
@@ -7,6 +8,7 @@ import RegisterFacebookActions from './RegisterFacebookActions';
import RegisterFacebookEditActions from './RegisterFacebookEditActions';
import RegisterFacebookActionsEnhanced from './RegisterFacebookActionsEnhanced';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
import { generatePlatformPersona } from '../../api/persona';
const useCopilotActionTyped = useCopilotAction as any;
@@ -142,6 +144,7 @@ const FacebookWriter: React.FC<FacebookWriterProps> = ({ className = '' }) => {
// Main Facebook Writer Content Component
const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }) => {
const navigate = useNavigate();
const [postDraft, setPostDraft] = React.useState<string>('');
const [notes, setNotes] = React.useState<string>('');
const [stage, setStage] = React.useState<'start' | 'edit'>('start');
@@ -160,7 +163,34 @@ const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }
const [selectionMenu, setSelectionMenu] = React.useState<{ x: number; y: number; text: string } | null>(null);
// Get persona context for enhanced AI assistance
const { corePersona, platformPersona, loading: personaLoading } = usePlatformPersonaContext();
const { corePersona, platformPersona, loading: personaLoading, refreshPersonas } = usePlatformPersonaContext();
// State for generating persona
const [isGeneratingPersona, setIsGeneratingPersona] = React.useState<boolean>(false);
const [personaError, setPersonaError] = React.useState<string | null>(null);
// Handler to generate Facebook persona on-demand
const handleGeneratePersona = async () => {
setIsGeneratingPersona(true);
setPersonaError(null);
try {
const result = await generatePlatformPersona('facebook');
if (result.success) {
// Refresh the persona context to load the newly generated persona
await refreshPersonas();
console.log('✅ Facebook persona generated successfully');
} else {
throw new Error('Failed to generate persona');
}
} catch (error: any) {
console.error('Error generating persona:', error);
setPersonaError(error.message || 'Failed to generate Facebook persona');
} finally {
setIsGeneratingPersona(false);
}
};
React.useEffect(() => {
const onUpdate = (e: any) => {
@@ -395,9 +425,31 @@ Always use the most appropriate tool for the user's request.`.trim();
>
<Container maxWidth="md" sx={{ position: 'relative', zIndex: 1, py: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: 0.3 }}>
Facebook Writer (Preview)
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{/* Back Button */}
<Button
onClick={() => navigate('/dashboard')}
sx={{
padding: '8px 16px',
background: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: 2,
textTransform: 'none',
fontSize: 14,
fontWeight: 600,
'&:hover': {
background: 'rgba(255, 255, 255, 0.2)',
}
}}
>
Back to Dashboard
</Button>
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: 0.3 }}>
Facebook Writer
</Typography>
</Box>
</Box>
{/* Persona Integration Indicator */}
@@ -405,8 +457,8 @@ Always use the most appropriate tool for the user's request.`.trim();
<div
style={{
padding: '8px 16px',
backgroundColor: 'rgba(24, 119, 242, 0.1)',
borderBottom: '1px solid rgba(24, 119, 242, 0.3)',
backgroundColor: platformPersona ? 'rgba(24, 119, 242, 0.1)' : 'rgba(255, 152, 0, 0.1)',
borderBottom: `1px solid ${platformPersona ? 'rgba(24, 119, 242, 0.3)' : 'rgba(255, 152, 0, 0.3)'}`,
fontSize: '12px',
color: 'rgba(255, 255, 255, 0.8)',
display: 'flex',
@@ -416,7 +468,7 @@ Always use the most appropriate tool for the user's request.`.trim();
position: 'relative',
marginBottom: '16px',
borderRadius: '8px',
border: '1px solid rgba(24, 119, 242, 0.2)'
border: `1px solid ${platformPersona ? 'rgba(24, 119, 242, 0.2)' : 'rgba(255, 152, 0, 0.2)'}`
}}
title={`🎭 YOUR PERSONALIZED WRITING ASSISTANT
@@ -468,17 +520,70 @@ Instead of generic content, you get:
💡 TRY THIS: Ask the AI to "generate a Facebook post about [your topic]" and watch how it automatically applies your persona to create content that sounds like you!`}
>
<span style={{ color: '#1877f2' }}>🎭</span>
<span><strong>🎭 Your Writing Assistant:</strong> {corePersona.persona_name} ({corePersona.archetype})</span>
<span style={{ color: platformPersona ? '#1877f2' : '#FF9800' }}>🎭</span>
<span>
<strong>🎭 Your Writing Assistant:</strong> {corePersona.persona_name} ({corePersona.archetype})
{!platformPersona && <span style={{ color: '#FF9800', marginLeft: '8px' }}> Facebook persona not generated yet</span>}
</span>
<span style={{ marginLeft: 'auto', fontSize: '11px' }}>
{corePersona.confidence_score}% accuracy |
Platform: Facebook Optimized
Platform: {platformPersona ? 'Facebook Optimized' : 'Generic (Generate Facebook Persona for better results)'}
</span>
<span style={{ fontSize: '10px', color: 'rgba(255, 255, 255, 0.6)', marginLeft: '8px' }}>
(Hover for details)
</span>
</div>
)}
{/* Warning when platform persona is missing */}
{corePersona && !platformPersona && !personaLoading && (
<div
style={{
padding: '12px 16px',
backgroundColor: 'rgba(255, 152, 0, 0.15)',
border: '1px solid rgba(255, 152, 0, 0.3)',
fontSize: '13px',
color: 'rgba(255, 255, 255, 0.9)',
marginBottom: '16px',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '12px'
}}
>
<span style={{ fontSize: '20px' }}></span>
<div style={{ flex: 1 }}>
<strong>Facebook Persona Not Generated</strong>
<div style={{ fontSize: '12px', color: 'rgba(255, 255, 255, 0.7)', marginTop: '4px' }}>
You're using a generic persona. Generate a Facebook-specific persona for personalized content that matches your brand voice and Facebook's algorithm.
</div>
{personaError && (
<div style={{ fontSize: '11px', color: '#ff6b6b', marginTop: '4px' }}>
{personaError}
</div>
)}
</div>
<Button
onClick={handleGeneratePersona}
disabled={isGeneratingPersona}
size="small"
sx={{
background: 'rgba(255, 152, 0, 0.2)',
color: 'white',
border: '1px solid rgba(255, 152, 0, 0.4)',
textTransform: 'none',
'&:hover': {
background: 'rgba(255, 152, 0, 0.3)',
},
'&:disabled': {
opacity: 0.6
}
}}
>
{isGeneratingPersona ? 'Generating...' : 'Generate Persona →'}
</Button>
</div>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Button size="small" variant="outlined" disabled sx={{ color: 'rgba(255,255,255,0.7)', borderColor: 'rgba(255,255,255,0.25)' }}>
DashBoard

View File

@@ -6,7 +6,8 @@ import './styles/alwrity-copilot.css';
import RegisterLinkedInActions from './RegisterLinkedInActions';
import RegisterLinkedInEditActions from './RegisterLinkedInEditActions';
import RegisterLinkedInActionsEnhanced from './RegisterLinkedInActionsEnhanced';
import { Header, ContentEditor, LoadingIndicator, WelcomeMessage, ProgressTracker, CopilotActions } from './components';
import { Header, ContentEditor, LoadingIndicator, WelcomeMessage, ProgressTracker } from './components';
import { useCopilotActions } from './components/CopilotActions';
import { useLinkedInWriter } from './hooks/useLinkedInWriter';
import { useCopilotPersistence } from './utils/enhancedPersistence';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
@@ -226,8 +227,8 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
});
// Initialize CopilotActions component to handle all copilot-related functionality
const getIntelligentSuggestions = CopilotActions({
// Use the CopilotActions hook to handle all copilot-related functionality
const getIntelligentSuggestions = useCopilotActions({
draft,
context,
userPreferences,
@@ -237,7 +238,15 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
});
return (
<div className={`linkedin-writer ${className}`} style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
<div
className={`linkedin-writer ${className}`}
style={{
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#ffffff' // White professional background
}}
>
{/* Header */}
<Header
userPreferences={userPreferences}
@@ -267,7 +276,7 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
{/* Main Content */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', backgroundColor: '#ffffff' }}>
{/* Loading Indicator */}
<LoadingIndicator
isGenerating={isGenerating}

View File

@@ -16,9 +16,9 @@ interface CopilotActionsProps {
setDraft: (draft: string) => void;
}
// Note: This is implemented as a hook-like utility, not a rendered component.
// Note: This is implemented as a custom hook.
// It returns the getIntelligentSuggestions function for use by the caller.
const CopilotActions = ({
export const useCopilotActions = ({
draft,
context,
userPreferences,
@@ -428,5 +428,3 @@ const CopilotActions = ({
// Return the suggestions function directly
return getIntelligentSuggestions;
};
export default CopilotActions;

View File

@@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { LinkedInPreferences } from '../utils/storageUtils';
import { PersonaChip } from '../../TextEditor/ContentPreviewHeaderComponents';
import { usePlatformPersonaContext } from '../../shared/PersonaContext/PlatformPersonaProvider';
@@ -25,6 +26,7 @@ export const Header: React.FC<HeaderProps> = ({
onClearHistory,
getHistoryLength
}) => {
const navigate = useNavigate();
const [personaOverride, setPersonaOverride] = useState<any>(null);
const { corePersona, platformPersona } = usePlatformPersonaContext();
@@ -89,6 +91,34 @@ export const Header: React.FC<HeaderProps> = ({
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{/* Left Section - Logo and Title */}
<div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}>
{/* Back Button */}
<button
onClick={() => navigate('/dashboard')}
style={{
padding: '8px 12px',
background: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: 8,
cursor: 'pointer',
fontSize: 14,
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
}}
title="Back to Dashboard"
>
Back to Dashboard
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<img
src={alwrityLogo}
@@ -211,7 +241,6 @@ export const Header: React.FC<HeaderProps> = ({
}}>
<PersonaChip
platform="linkedin"
userId={1}
onPersonaUpdate={handlePersonaUpdate}
/>
</div>

View File

@@ -26,4 +26,4 @@ export { default as ImageGenerationTest } from './ImageGenerationTest';
// Refactored Components
export { default as BrainstormFlow } from './BrainstormFlow';
export { default as CopilotActions } from './CopilotActions';
export { useCopilotActions } from './CopilotActions';

View File

@@ -87,7 +87,6 @@ const MainContentPreviewHeader: React.FC<MainContentPreviewHeaderProps> = ({
{/* Persona Chip */}
<PersonaChip
platform="linkedin"
userId={1}
onPersonaUpdate={(personaData) => {
console.log('Persona updated:', personaData);
// You can add additional logic here to handle persona updates

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import PersonaEditorModal from './PersonaEditorModal';
import { getUserPersonas, getPlatformPersona, updatePersona, updatePlatformPersona } from '../../../api/persona';
interface PersonaData {
id?: number;
@@ -28,13 +29,11 @@ interface PersonaData {
interface PersonaChipProps {
platform: string;
userId?: number;
onPersonaUpdate?: (personaData: PersonaData) => void;
}
const PersonaChip: React.FC<PersonaChipProps> = ({
platform,
userId = 1,
onPersonaUpdate
}) => {
const [personaData, setPersonaData] = useState<PersonaData | null>(null);
@@ -48,44 +47,48 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
setError(null);
try {
// Fetch core persona list (take most recent active) and platform-specific details
const [coreRes, platformRes] = await Promise.all([
fetch(`/api/personas/user/${userId}`),
fetch(`/api/personas/platform/${platform}?user_id=${userId}`)
// Fetch core persona list (take most recent active) and platform-specific details using authenticated API client
const [coreList, platformData] = await Promise.all([
getUserPersonas(),
getPlatformPersona(platform)
]);
if (coreRes.ok && platformRes.ok) {
const coreList = await coreRes.json();
const platformData = await platformRes.json();
const core = (coreList?.personas && coreList.personas.length > 0) ? coreList.personas[0] : {};
if (coreList && platformData) {
// Extract core persona from the response
const corePersona = platformData?.core_persona || {};
const platformPersona = platformData?.platform_persona || {};
const qualityMetrics = platformData?.quality_metrics || {};
if (!corePersona || Object.keys(corePersona).length === 0) {
setError('No persona found for this platform');
return;
}
// Merge core + platform fields for editor convenience
setPersonaData({
id: core.id,
user_id: core.user_id,
persona_name: core.persona_name,
archetype: core.archetype,
core_belief: core.core_belief,
brand_voice_description: core.brand_voice_description,
linguistic_fingerprint: core.linguistic_fingerprint,
platform_adaptations: core.platform_adaptations,
confidence_score: core.confidence_score,
ai_analysis_version: core.ai_analysis_version,
id: platformData?.id || 1,
user_id: 1, // Placeholder, not used
persona_name: corePersona.persona_name || 'Untitled Persona',
archetype: corePersona.archetype || 'General',
core_belief: corePersona.core_belief || '',
brand_voice_description: corePersona.brand_voice_description || corePersona.core_belief || '',
linguistic_fingerprint: corePersona.linguistic_fingerprint || {},
platform_adaptations: corePersona.platform_adaptations || {},
confidence_score: qualityMetrics.confidence_score || corePersona.confidence_score || 0,
ai_analysis_version: platformData?.ai_analysis_version || '1.0',
platform_type: platform,
sentence_metrics: platformData?.sentence_metrics,
lexical_features: platformData?.lexical_features,
rhetorical_devices: platformData?.rhetorical_devices,
tonal_range: platformData?.tonal_range,
stylistic_constraints: platformData?.stylistic_constraints,
content_format_rules: platformData?.content_format_rules,
engagement_patterns: platformData?.engagement_patterns,
posting_frequency: platformData?.posting_frequency,
content_types: platformData?.content_types,
platform_best_practices: platformData?.platform_best_practices,
algorithm_considerations: platformData?.algorithm_considerations,
sentence_metrics: platformPersona?.sentence_metrics || {},
lexical_features: platformPersona?.lexical_features || {},
rhetorical_devices: platformPersona?.rhetorical_devices || {},
tonal_range: platformPersona?.tonal_range || {},
stylistic_constraints: platformPersona?.stylistic_constraints || {},
content_format_rules: platformPersona?.content_format_rules || {},
engagement_patterns: platformPersona?.engagement_patterns || {},
posting_frequency: platformPersona?.posting_frequency || {},
content_types: platformPersona?.content_types || {},
platform_best_practices: platformPersona?.platform_best_practices || {},
algorithm_considerations: platformPersona?.algorithm_considerations || {},
} as any);
} else {
setError('No persona found for this platform');
}
} catch (err) {
setError('Failed to load persona data');
@@ -98,7 +101,7 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
useEffect(() => {
fetchPersonaData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform, userId]);
}, [platform]);
const handleSavePersona = async (data: PersonaData, saveToDatabase: boolean) => {
try {
@@ -114,12 +117,8 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
platform_adaptations: data.platform_adaptations,
};
const coreRes = await fetch(`/api/personas/${data.id}?user_id=${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(corePayload)
});
if (!coreRes.ok) throw new Error('Failed to update core persona');
// Use authenticated API client, note that user ID is extracted from JWT
await updatePersona(1, data.id, { core_persona: corePayload });
}
// Save platform persona fields
@@ -137,12 +136,8 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
algorithm_considerations: data.algorithm_considerations,
};
const platRes = await fetch(`/api/personas/platform/${platform}?user_id=${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(platformPayload)
});
if (!platRes.ok) throw new Error('Failed to update platform persona');
// Use authenticated API client, note that user ID is extracted from JWT
await updatePlatformPersona(platform, platformPayload);
}
// Update local state

View File

@@ -6,6 +6,7 @@
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback, useRef } from 'react';
import { useCopilotReadable } from '@copilotkit/react-core';
import { useAuth } from '@clerk/clerk-react';
import {
WritingPersona,
PlatformAdaptation,
@@ -33,7 +34,6 @@ const PlatformPersonaContext = createContext<PlatformPersonaContextType | null>(
interface PlatformPersonaProviderProps {
children: ReactNode;
platform: PlatformType;
userId?: number; // Default to 1 for now, can be enhanced with auth context later
}
// Cache duration: 5 minutes (constant outside component to avoid dependency issues)
@@ -42,9 +42,13 @@ const CACHE_DURATION = 5 * 60 * 1000;
// Provider component
export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = ({
children,
platform,
userId = 1
platform
}) => {
// Get Clerk user ID
const { userId } = useAuth();
// Convert string userId to number for legacy API compatibility
const numericUserId = userId ? 1 : 1; // Use 1 as placeholder, API uses Clerk ID from auth
// State management
const [corePersona, setCorePersona] = useState<WritingPersona | null>(null);
const [platformPersona, setPlatformPersona] = useState<PlatformAdaptation | null>(null);
@@ -83,24 +87,69 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
setError(null);
// Fetch both core persona and platform-specific data
const [userPersonasResponse, platformPersonaResponse] = await Promise.all([
getUserPersonas(userId),
getPlatformPersona(userId, platform)
]);
// Note: APIs use Clerk auth, so user ID is extracted from JWT
let userPersonasResponse;
let platformPersonaResponse = null;
try {
const results = await Promise.all([
getUserPersonas(),
getPlatformPersona(platform).catch(err => {
// Handle 404 gracefully - platform persona doesn't exist yet
if (err.message && err.message.includes('No persona found')) {
console.warn(`⚠️ No ${platform} persona found - user can still generate content`);
return null;
}
throw err;
})
]);
userPersonasResponse = results[0];
platformPersonaResponse = results[1];
} catch (error) {
// If platform persona fetch fails, continue with core persona only
console.warn(`⚠️ Platform persona unavailable: ${error instanceof Error ? error.message : 'Unknown error'}`);
userPersonasResponse = await getUserPersonas();
platformPersonaResponse = null;
}
// Handle core persona data
if (userPersonasResponse.personas && userPersonasResponse.personas.length > 0) {
const primaryPersona = userPersonasResponse.personas[0];
console.log('🔍 API Response - userPersonasResponse:', userPersonasResponse);
// Backend returns personas as a dictionary of platform -> persona data
// Convert to array format for easier processing
let personasArray: any[] = [];
if (userPersonasResponse && userPersonasResponse.personas) {
if (Array.isArray(userPersonasResponse.personas)) {
personasArray = userPersonasResponse.personas;
} else if (typeof userPersonasResponse.personas === 'object') {
// Convert dictionary to array
personasArray = Object.values(userPersonasResponse.personas);
}
}
console.log('🔍 Processed personas array:', {
isArray: Array.isArray(personasArray),
length: personasArray.length,
firstItem: personasArray[0]
});
if (personasArray.length > 0) {
const primaryPersona = personasArray[0];
console.log('🔍 Primary persona from API:', primaryPersona);
// Extract core persona data (may be nested in the response)
const corePersonaData = primaryPersona.core_persona || primaryPersona;
const identity = corePersonaData.identity || {};
// Convert API response to WritingPersona format
const convertedPersona: WritingPersona = {
id: primaryPersona.persona_id,
user_id: userId,
persona_name: primaryPersona.persona_name,
archetype: primaryPersona.archetype,
core_belief: primaryPersona.core_belief,
brand_voice_description: primaryPersona.core_belief, // Use core_belief as fallback
linguistic_fingerprint: {
id: primaryPersona.id || corePersonaData.id || 1,
user_id: numericUserId, // Use numeric ID for legacy compatibility
persona_name: identity.persona_name || corePersonaData.persona_name || primaryPersona.persona_name || 'Untitled Persona',
archetype: identity.archetype || corePersonaData.archetype || primaryPersona.archetype || 'General',
core_belief: identity.core_belief || corePersonaData.core_belief || primaryPersona.core_belief || '',
brand_voice_description: identity.brand_voice_description || corePersonaData.brand_voice_description || corePersonaData.core_belief || primaryPersona.core_belief || '',
linguistic_fingerprint: corePersonaData.linguistic_fingerprint || {
sentence_metrics: {
average_sentence_length_words: 15,
preferred_sentence_type: "compound",
@@ -130,10 +179,11 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
source_website_analysis: {},
source_research_preferences: {},
ai_analysis_version: "1.0",
confidence_score: primaryPersona.confidence_score,
analysis_date: primaryPersona.created_at,
confidence_score: primaryPersona.quality_metrics?.overall_score ? primaryPersona.quality_metrics.overall_score / 100 :
(corePersonaData.confidence_score || primaryPersona.confidence_score || 0),
analysis_date: corePersonaData.created_at || primaryPersona.created_at,
created_at: primaryPersona.created_at,
updated_at: primaryPersona.created_at,
updated_at: primaryPersona.updated_at || primaryPersona.created_at,
is_active: true
};
@@ -142,7 +192,10 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
console.log('✅ Core persona loaded:', {
name: convertedPersona.persona_name,
archetype: convertedPersona.archetype,
confidence: convertedPersona.confidence_score
confidence: convertedPersona.confidence_score,
hasLinguisticFingerprint: !!(convertedPersona.linguistic_fingerprint && Object.keys(convertedPersona.linguistic_fingerprint).length),
identityData: identity,
quality_metrics: primaryPersona.quality_metrics
});
} else {
console.warn('⚠️ No core personas found for user');
@@ -150,46 +203,51 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
}
// Handle platform-specific persona data
console.log('🔍 API Response - platformPersonaResponse:', platformPersonaResponse);
if (platformPersonaResponse) {
// Extract platform-specific data from API response
const platformPersona = platformPersonaResponse.platform_persona || {};
const corePersonaFromPlatform = platformPersonaResponse.core_persona || {};
// Convert API response to PlatformAdaptation format
const convertedPlatformPersona: PlatformAdaptation = {
id: 1,
writing_persona_id: corePersona?.id || 1,
platform_type: platform,
sentence_metrics: {
sentence_metrics: platformPersona.sentence_metrics || {
optimal_length: "150-300 words",
character_limit: platform === 'linkedin' ? 3000 : 280,
sentence_structure: "varied",
paragraph_breaks: "frequent",
readability_score: 8.5
},
lexical_features: {
lexical_features: platformPersona.lexical_features || {
hashtag_strategy: "3-5 relevant hashtags",
platform_specific_terms: [],
engagement_phrases: ["What do you think?", "Share your thoughts"],
call_to_action_style: "gentle"
},
rhetorical_devices: {
rhetorical_devices: platformPersona.rhetorical_devices || {
question_frequency: "occasional",
story_elements: "personal_anecdotes",
visual_descriptions: "minimal",
interactive_elements: "questions"
},
tonal_range: {
tonal_range: platformPersona.tonal_range || {
default_tone: "professional_friendly",
permissible_tones: ["inspiring", "thoughtful"],
forbidden_tones: ["salesy", "academic"],
emotional_range: "moderate",
formality_level: "semi_formal"
},
stylistic_constraints: {
stylistic_constraints: platformPersona.stylistic_constraints || {
punctuation_preferences: "standard",
formatting_rules: "clean",
emoji_usage: "minimal",
link_placement: "end",
media_integration: "encouraged"
},
content_format_rules: {
content_format_rules: platformPersona.content_format_rules || {
character_limit: platform === 'linkedin' ? 3000 : 280,
optimal_length: platform === 'linkedin' ? "150-300 words" : "120-150 characters",
word_count: platform === 'linkedin' ? "150-300" : "20-25",
@@ -197,26 +255,26 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
media_requirements: "optional",
link_restrictions: "unlimited"
},
engagement_patterns: {
engagement_patterns: platformPersona.engagement_patterns || {
posting_frequency: "2-3 times per week",
best_timing: "9 AM - 11 AM, 1 PM - 3 PM",
interaction_style: "conversational",
response_strategy: "within 2 hours",
community_approach: "collaborative"
},
posting_frequency: {
posting_frequency: platformPersona.posting_frequency || {
frequency: "2-3 times per week",
optimal_days: ["Tuesday", "Wednesday", "Thursday"],
optimal_times: ["9:00 AM", "1:00 PM"],
seasonal_adjustments: "moderate"
},
content_types: {
content_types: platformPersona.content_types || {
primary_content: ["thought_leadership", "industry_insights"],
secondary_content: ["personal_stories", "tips"],
content_mix: "70% professional, 30% personal",
seasonal_content: ["trending_topics", "industry_events"]
},
platform_best_practices: {
platform_best_practices: platformPersona.platform_best_practices || {
algorithm_tips: ["post_consistently", "engage_with_community"],
engagement_tactics: ["ask_questions", "share_stories"],
content_strategies: ["value_first", "authentic_voice"],
@@ -231,7 +289,8 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
console.log('✅ Platform persona loaded:', {
platform: convertedPlatformPersona.platform_type,
characterLimit: convertedPlatformPersona.content_format_rules?.character_limit,
optimalLength: convertedPlatformPersona.content_format_rules?.optimal_length
optimalLength: convertedPlatformPersona.content_format_rules?.optimal_length,
hasData: !!(platformPersona && Object.keys(platformPersona).length > 0)
});
} else {
console.warn(`⚠️ No platform-specific persona found for ${platform}`);
@@ -272,6 +331,18 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
parentId: corePersona?.id?.toString()
});
// Debug: Log when persona data is available for CopilotKit
useEffect(() => {
if (corePersona) {
console.log('🎯 Injected core persona into CopilotKit:', {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score,
hasLinguisticFingerprint: !!(corePersona.linguistic_fingerprint && Object.keys(corePersona.linguistic_fingerprint).length)
});
}
}, [corePersona]);
// Inject platform-specific persona into CopilotKit context
useCopilotReadable({
description: `${platform} platform optimization rules and constraints`,
@@ -280,6 +351,17 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
parentId: corePersona?.id?.toString()
});
// Debug: Log when platform persona is available for CopilotKit
useEffect(() => {
if (platformPersona) {
console.log('🎯 Injected platform persona into CopilotKit:', {
platform: platformPersona.platform_type,
characterLimit: platformPersona.content_format_rules?.character_limit,
optimalLength: platformPersona.content_format_rules?.optimal_length
});
}
}, [platformPersona]);
// Inject combined persona context for comprehensive understanding
useCopilotReadable({
description: `Complete ${platform} writing persona with linguistic fingerprint and platform optimization`,