Advanced Content Hyper-Personalization Implementation

This commit is contained in:
ajaysi
2025-09-04 22:49:15 +05:30
parent d57f7feb4a
commit ccbdc9e8c6
18 changed files with 1452 additions and 974 deletions

View File

@@ -5,6 +5,8 @@ import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core';
import '@copilotkit/react-ui/styles.css';
import RegisterFacebookActions from './RegisterFacebookActions';
import RegisterFacebookEditActions from './RegisterFacebookEditActions';
import RegisterFacebookActionsEnhanced from './RegisterFacebookActionsEnhanced';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
const useCopilotActionTyped = useCopilotAction as any;
@@ -125,7 +127,21 @@ function simpleMarkdownToHtml(markdown: string): string {
return html;
}
const FacebookWriter: React.FC = () => {
interface FacebookWriterProps {
className?: string;
}
// Enhanced Facebook Writer with Persona Integration
const FacebookWriter: React.FC<FacebookWriterProps> = ({ className = '' }) => {
return (
<PlatformPersonaProvider platform="facebook">
<FacebookWriterContent className={className} />
</PlatformPersonaProvider>
);
};
// Main Facebook Writer Content Component
const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }) => {
const [postDraft, setPostDraft] = React.useState<string>('');
const [notes, setNotes] = React.useState<string>('');
const [stage, setStage] = React.useState<'start' | 'edit'>('start');
@@ -143,6 +159,9 @@ const FacebookWriter: React.FC = () => {
const renderRef = React.useRef<HTMLDivElement | null>(null);
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();
React.useEffect(() => {
const onUpdate = (e: any) => {
setPostDraft(String(e.detail || ''));
@@ -282,7 +301,9 @@ const FacebookWriter: React.FC = () => {
className="alwrity-copilot-sidebar"
labels={{
title: 'ALwrity • Facebook Writer',
initial: stage === 'start' ? 'Tell me what you want to post. I can draft, refine, and generate variants.' : 'Great! Try quick edits below to refine your post in real-time.'
initial: stage === 'start' ?
`Tell me what you want to post. I can draft, refine, and generate variants${corePersona ? ` with ${corePersona.persona_name} persona optimization` : ''}.` :
`Great! Try quick edits below to refine your post in real-time${corePersona ? ` using your ${corePersona.persona_name} persona` : ''}.`
}}
suggestions={suggestions}
makeSystemMessage={(_context: string, additional?: string) => {
@@ -290,15 +311,66 @@ const FacebookWriter: React.FC = () => {
const prefsLine = Object.keys(prefs).length ? `User preferences (remember and respect unless changed): ${JSON.stringify(prefs)}` : '';
const history = summarizeHistory();
const historyLine = history ? `Recent conversation (last 10 messages):\n${history}` : '';
const currentDraft = postDraft ? `Current draft content:\n${postDraft}` : 'No current draft content.';
// Enhanced persona-aware guidance
const personaGuidance = corePersona && platformPersona ? `
PERSONA-AWARE WRITING GUIDANCE:
- PERSONA: ${corePersona.persona_name} (${corePersona.archetype})
- CORE BELIEF: ${corePersona.core_belief}
- CONFIDENCE SCORE: ${corePersona.confidence_score}%
- LINGUISTIC STYLE: ${corePersona.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words || 'Unknown'} words average, ${corePersona.linguistic_fingerprint?.sentence_metrics?.active_to_passive_ratio || 'Unknown'} active/passive ratio
- GO-TO WORDS: ${corePersona.linguistic_fingerprint?.lexical_features?.go_to_words?.join(', ') || 'None specified'}
- AVOID WORDS: ${corePersona.linguistic_fingerprint?.lexical_features?.avoid_words?.join(', ') || 'None specified'}
PLATFORM OPTIMIZATION (Facebook):
- CHARACTER LIMIT: ${platformPersona.content_format_rules?.character_limit || '63206'} characters
- OPTIMAL LENGTH: ${platformPersona.content_format_rules?.optimal_length || '40-80 characters'}
- ENGAGEMENT PATTERN: ${platformPersona.engagement_patterns?.posting_frequency || '1-2 times per day'}
- HASHTAG STRATEGY: ${platformPersona.lexical_features?.hashtag_strategy || '1-2 relevant hashtags'}
ALWAYS generate content that matches this persona's linguistic fingerprint and platform optimization rules.` : '';
const guidance = `
You are ALwrity's Facebook Writing Assistant.
You have the following tools available; prefer them when relevant:
- generateFacebookPost: generate a Facebook post using provided business_type, target_audience, post_goal, post_tone, include, avoid.
- generateFacebookHashtags: generate hashtags for a given content_topic (or infer from the user's draft/context).
- updateFacebookPostDraft / appendToFacebookPostDraft: directly update the user's on-page draft when asked to tighten, rewrite, or append.
- editFacebookDraft: apply a quick style or structural edit (Casual, Professional, Upbeat, Shorten, Lengthen, TightenHook, AddCTA) and reflect the change live.
Always respond concisely and take action with the most appropriate tool.`.trim();
return [prefsLine, historyLine, guidance, additional].filter(Boolean).join('\n\n');
You are ALwrity's Facebook Writing Assistant specializing in engaging social media content.
CRITICAL CONSTRAINTS:
- TONE: Always maintain an engaging, community-focused tone
- PLATFORM: Focus specifically on Facebook's unique characteristics and audience
- QUALITY: Ensure all content meets Facebook's community standards
${personaGuidance ? `\n${personaGuidance}` : ''}
CURRENT CONTEXT:
${currentDraft}
Available Facebook content tools:
- generateFacebookPost: Create engaging Facebook posts with persona optimization
- generateFacebookHashtags: Generate relevant hashtags for Facebook content
- generateFacebookAdCopy: Create conversion-focused ad copy
- generateFacebookStory: Create Facebook Story scripts
- generateFacebookReel: Create Facebook Reel scripts
- generateFacebookCarousel: Create multi-slide carousel content
- generateFacebookEvent: Create event descriptions
- generateFacebookPageAbout: Create page about sections
🎭 ENHANCED PERSONA-AWARE ACTIONS (Recommended):
- generateFacebookPostWithPersona: Create posts optimized for your writing style and platform constraints
- validateContentAgainstPersona: Validate existing content against your persona
- getPersonaWritingSuggestions: Get personalized writing recommendations
DIRECT DRAFT ACTIONS:
- updateFacebookPostDraft: Replace the entire draft with new content
- appendToFacebookPostDraft: Add text to the existing draft
- editFacebookDraft: Apply quick edits (Casual, Professional, Upbeat, Shorten, Lengthen, TightenHook, AddCTA) to the current draft
IMPORTANT: When refining or editing content, always reference the current draft above. If the user asks to refine their post, use the current draft content as the starting point. Never ask for content that already exists in the draft.
For quick edits, use editFacebookDraft with the appropriate operation. This will show a live preview of changes before applying them.
Use user preferences, context, conversation history, and persona data to personalize all content.
Always respect the user's preferred tone, platform focus, and writing persona style.
Always use the most appropriate tool for the user's request.`.trim();
return [prefsLine, historyLine, currentDraft, guidance, additional].filter(Boolean).join('\n\n');
}}
observabilityHooks={{
onChatExpanded: () => console.log('[FB Writer] Sidebar opened'),
@@ -308,6 +380,8 @@ Always respond concisely and take action with the most appropriate tool.`.trim()
>
<RegisterFacebookActions />
<RegisterFacebookEditActions />
{/* Enhanced Persona-Aware Actions */}
<RegisterFacebookActionsEnhanced />
<Box
sx={{
minHeight: '100vh',
@@ -324,6 +398,58 @@ Always respond concisely and take action with the most appropriate tool.`.trim()
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: 0.3 }}>
Facebook Writer (Preview)
</Typography>
</Box>
{/* Persona Integration Indicator */}
{corePersona && !personaLoading && (
<div
style={{
padding: '8px 16px',
backgroundColor: 'rgba(24, 119, 242, 0.1)',
borderBottom: '1px solid rgba(24, 119, 242, 0.3)',
fontSize: '12px',
color: 'rgba(255, 255, 255, 0.8)',
display: 'flex',
alignItems: 'center',
gap: '8px',
cursor: 'help',
position: 'relative',
marginBottom: '16px',
borderRadius: '8px',
border: '1px solid rgba(24, 119, 242, 0.2)'
}}
title={`Complete Persona Details:
🎭 PERSONA: ${corePersona.persona_name}
📋 ARCHETYPE: ${corePersona.archetype}
💭 CORE BELIEF: ${corePersona.core_belief}
📊 CONFIDENCE: ${corePersona.confidence_score}%
📝 LINGUISTIC FINGERPRINT:
• Sentence Length: ${corePersona.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words || 'Unknown'} words average
• Voice Ratio: ${corePersona.linguistic_fingerprint?.sentence_metrics?.active_to_passive_ratio || 'Unknown'}
• Go-to Words: ${corePersona.linguistic_fingerprint?.lexical_features?.go_to_words?.join(', ') || 'None specified'}
• Avoid Words: ${corePersona.linguistic_fingerprint?.lexical_features?.avoid_words?.join(', ') || 'None specified'}
🎯 PLATFORM OPTIMIZATION (Facebook):
• Character Limit: ${platformPersona?.content_format_rules?.character_limit || '63206'} characters
• Optimal Length: ${platformPersona?.content_format_rules?.optimal_length || '40-80 characters'}
• Engagement Pattern: ${platformPersona?.engagement_patterns?.posting_frequency || '1-2 times per day'}
• Hashtag Strategy: ${platformPersona?.lexical_features?.hashtag_strategy || '1-2 relevant hashtags'}
✨ This persona is actively optimizing your content generation and AI assistance!`}
>
<span style={{ color: '#1877f2' }}>🎭</span>
<span><strong>Persona Active:</strong> {corePersona.persona_name} ({corePersona.archetype})</span>
<span style={{ marginLeft: 'auto', fontSize: '11px' }}>
Confidence: {corePersona.confidence_score}% |
Platform: Facebook Optimized
</span>
<span style={{ fontSize: '10px', color: 'rgba(255, 255, 255, 0.6)', marginLeft: '8px' }}>
(Hover for details)
</span>
</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
@@ -333,7 +459,6 @@ Always respond concisely and take action with the most appropriate tool.`.trim()
Clear chat memory
</Button>
</Box>
</Box>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.85)', mb: 3 }}>
{stage === 'start' ? 'Collaborate with the Copilot to craft your post. The assistant can update the draft directly.' : 'Use the edit suggestions to see real-time changes applied to your post.'}
</Typography>

View File

@@ -0,0 +1,345 @@
import React from 'react';
import { useCopilotAction } from '@copilotkit/react-core';
import { facebookWriterApi, PostGenerateRequest } from '../../services/facebookWriterApi';
import { usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
const useCopilotActionTyped = useCopilotAction as any;
const RegisterFacebookActionsEnhanced: React.FC = () => {
// Get persona context for enhanced content generation
const { corePersona, platformPersona } = usePlatformPersonaContext();
// Helper function to apply persona constraints to content
const applyPersonaConstraints = (content: string, constraints: any) => {
if (!constraints) return content;
let enhancedContent = content;
// Apply sentence length constraints
if (constraints.sentence_metrics?.average_sentence_length_words) {
const targetLength = constraints.sentence_metrics.average_sentence_length_words;
// This is a simplified example - in practice, you'd use more sophisticated NLP
console.log(`🎭 Applying persona sentence length constraint: ${targetLength} words average`);
}
// Apply vocabulary constraints
if (constraints.lexical_features?.go_to_words?.length > 0) {
console.log(`🎭 Using persona go-to words: ${constraints.lexical_features.go_to_words.join(', ')}`);
}
if (constraints.lexical_features?.avoid_words?.length > 0) {
console.log(`🎭 Avoiding persona avoid words: ${constraints.lexical_features.avoid_words.join(', ')}`);
}
return enhancedContent;
};
// Enhanced Facebook Post Generation with Persona
useCopilotActionTyped({
name: 'generateFacebookPostWithPersona',
description: 'Generate an engaging Facebook post optimized for your writing persona and platform constraints',
parameters: [
{ name: 'topic', type: 'string', required: false },
{ name: 'business_type', type: 'string', required: false },
{ name: 'target_audience', type: 'string', required: false },
{ name: 'post_goal', type: 'string', required: false },
{ name: 'post_tone', type: 'string', required: false },
{ name: 'include', type: 'string', required: false },
{ name: 'avoid', type: 'string', required: false }
],
handler: async (args: any) => {
try {
// Apply persona constraints to the request
const personaConstraints = platformPersona?.content_format_rules as any;
console.log('🎭 Applying persona constraints:', personaConstraints);
const request: PostGenerateRequest = {
business_type: args.business_type || 'General',
target_audience: args.target_audience || 'General audience',
post_goal: args.post_goal || 'Engage audience',
post_tone: args.post_tone || 'Engaging',
include: args.include || '',
avoid: args.avoid || '',
// Apply persona constraints through advanced options
advanced_options: {
use_hook: true,
use_story: true,
use_cta: true,
use_question: true,
use_emoji: true,
use_hashtags: true
}
};
// Track progress with persona analysis steps
const progressSteps = [
'Analyzing persona constraints...',
'Applying linguistic fingerprint...',
'Optimizing for Facebook platform...',
'Generating persona-aware content...',
'Validating against persona rules...'
];
// Simulate progress tracking
for (let i = 0; i < progressSteps.length; i++) {
console.log(`🎭 Facebook Persona Progress: ${progressSteps[i]}`);
await new Promise(resolve => setTimeout(resolve, 200));
}
const res = await facebookWriterApi.postGenerate(request);
// Apply persona constraints to the generated content
const enhancedContent = applyPersonaConstraints(res.data?.content || '', corePersona?.linguistic_fingerprint);
// Dispatch event to update the draft
window.dispatchEvent(new CustomEvent('fbwriter:updateDraft', {
detail: enhancedContent
}));
return {
success: true,
content: enhancedContent,
persona_applied: {
persona_name: corePersona?.persona_name,
archetype: corePersona?.archetype,
confidence_score: corePersona?.confidence_score,
constraints_applied: {
character_limit: personaConstraints?.character_limit,
optimal_length: personaConstraints?.optimal_length,
linguistic_fingerprint: corePersona?.linguistic_fingerprint
}
},
original_response: res.data
};
} catch (error) {
console.error('Error generating Facebook post with persona:', error);
return {
success: false,
error: 'Failed to generate Facebook post with persona optimization'
};
}
}
});
// Enhanced Facebook Ad Copy Generation with Persona
useCopilotActionTyped({
name: 'generateFacebookAdCopyWithPersona',
description: 'Generate Facebook ad copy optimized for your writing persona and conversion goals',
parameters: [
{ name: 'product_service', type: 'string', required: true },
{ name: 'target_audience', type: 'string', required: false },
{ name: 'campaign_goal', type: 'string', required: false },
{ name: 'budget_range', type: 'string', required: false }
],
handler: async (args: any) => {
try {
const personaConstraints = platformPersona?.content_format_rules as any;
// Track progress with persona analysis steps
const progressSteps = [
'Analyzing persona for ad copy optimization...',
'Applying conversion-focused persona constraints...',
'Generating persona-aware ad variations...',
'Optimizing for Facebook ad format...'
];
for (let i = 0; i < progressSteps.length; i++) {
console.log(`🎭 Facebook Ad Persona Progress: ${progressSteps[i]}`);
await new Promise(resolve => setTimeout(resolve, 200));
}
const res = await facebookWriterApi.adCopyGenerate({
business_type: 'General',
product_service: args.product_service,
ad_objective: args.campaign_goal || 'Drive conversions',
ad_format: 'Single Image',
target_audience: args.target_audience || 'General audience',
targeting_options: {
age_group: '25-54',
gender: 'All',
location: 'Global',
interests: 'General',
behaviors: 'General',
lookalike_audience: 'None'
},
unique_selling_proposition: 'Quality product with great value',
budget_range: args.budget_range || 'Medium'
});
// Apply persona constraints to ad copy
const enhancedAdCopy = applyPersonaConstraints(res.data?.content || '', corePersona?.linguistic_fingerprint);
// Dispatch event to update the draft
window.dispatchEvent(new CustomEvent('fbwriter:updateDraft', {
detail: enhancedAdCopy
}));
return {
success: true,
ad_copy: enhancedAdCopy,
persona_applied: {
persona_name: corePersona?.persona_name,
archetype: corePersona?.archetype,
confidence_score: corePersona?.confidence_score
},
original_response: res.data
};
} catch (error) {
console.error('Error generating Facebook ad copy with persona:', error);
return {
success: false,
error: 'Failed to generate Facebook ad copy with persona optimization'
};
}
}
});
// Content Validation Against Persona
useCopilotActionTyped({
name: 'validateContentAgainstPersona',
description: 'Validate existing Facebook content against your writing persona and platform constraints',
parameters: [
{ name: 'content', type: 'string', required: true }
],
handler: async (args: any) => {
try {
const content = args.content;
const personaConstraints = platformPersona?.content_format_rules as any;
// Analyze content against persona constraints
const validation = {
character_count: content.length,
optimal_range: personaConstraints?.optimal_length || '40-80 characters',
status: content.length <= (personaConstraints?.character_limit || 63206) ? 'Within limits' : 'Exceeds limits',
suggestions: [] as string[]
};
// Check sentence length against persona
if (corePersona?.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words) {
const avgWords = corePersona.linguistic_fingerprint.sentence_metrics.average_sentence_length_words;
const sentences = content.split(/[.!?]+/).filter((s: string) => s.trim().length > 0);
const avgSentenceLength = sentences.reduce((acc: number, s: string) => acc + s.trim().split(' ').length, 0) / sentences.length;
if (Math.abs(avgSentenceLength - avgWords) > 5) {
validation.suggestions.push(`Consider adjusting sentence length to match your persona's average of ${avgWords} words per sentence`);
}
}
// Check for persona go-to words
if (corePersona?.linguistic_fingerprint?.lexical_features?.go_to_words && corePersona.linguistic_fingerprint.lexical_features.go_to_words.length > 0) {
const goToWords = corePersona.linguistic_fingerprint.lexical_features.go_to_words;
const hasGoToWords = goToWords.some((word: string) => content.toLowerCase().includes(word.toLowerCase()));
if (!hasGoToWords) {
validation.suggestions.push(`Consider incorporating your persona's go-to words: ${goToWords.join(', ')}`);
}
}
// Check for persona avoid words
if (corePersona?.linguistic_fingerprint?.lexical_features?.avoid_words && corePersona.linguistic_fingerprint.lexical_features.avoid_words.length > 0) {
const avoidWords = corePersona.linguistic_fingerprint.lexical_features.avoid_words;
const hasAvoidWords = avoidWords.some((word: string) => content.toLowerCase().includes(word.toLowerCase()));
if (hasAvoidWords) {
validation.suggestions.push(`Consider replacing words that your persona avoids: ${avoidWords.join(', ')}`);
}
}
// Platform-specific validation
if (content.length < 40) {
validation.suggestions.push('Consider adding more detail to meet Facebook\'s optimal post length');
}
return {
success: true,
validation,
persona_analysis: {
persona_name: corePersona?.persona_name,
archetype: corePersona?.archetype,
confidence_score: corePersona?.confidence_score
}
};
} catch (error) {
console.error('Error validating content against persona:', error);
return {
success: false,
error: 'Failed to validate content against persona'
};
}
}
});
// Get Persona Writing Suggestions
useCopilotActionTyped({
name: 'getPersonaWritingSuggestions',
description: 'Get personalized writing suggestions based on your persona and Facebook platform optimization',
parameters: [
{ name: 'content_type', type: 'string', required: false }
],
handler: async (args: any) => {
try {
const contentType = args.content_type || 'general post';
const suggestions = {
writing_style: [] as string[],
platform_optimization: [] as string[],
persona_specific: [] as string[]
};
// Writing style suggestions based on persona
if (corePersona?.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words) {
const avgWords = corePersona.linguistic_fingerprint.sentence_metrics.average_sentence_length_words;
suggestions.writing_style.push(`Aim for ${avgWords} words per sentence to match your persona's style`);
}
if (corePersona?.linguistic_fingerprint?.lexical_features?.go_to_words && corePersona.linguistic_fingerprint.lexical_features.go_to_words.length > 0) {
const goToWords = corePersona.linguistic_fingerprint.lexical_features.go_to_words;
suggestions.persona_specific.push(`Use your signature words: ${goToWords.join(', ')}`);
}
if (corePersona?.linguistic_fingerprint?.lexical_features?.avoid_words && corePersona.linguistic_fingerprint.lexical_features.avoid_words.length > 0) {
const avoidWords = corePersona.linguistic_fingerprint.lexical_features.avoid_words;
suggestions.persona_specific.push(`Avoid these words: ${avoidWords.join(', ')}`);
}
// Platform optimization suggestions
const personaConstraints = platformPersona?.content_format_rules as any;
if (personaConstraints?.optimal_length) {
suggestions.platform_optimization.push(`Optimal length for Facebook: ${personaConstraints.optimal_length}`);
}
if (personaConstraints?.character_limit) {
suggestions.platform_optimization.push(`Character limit: ${personaConstraints.character_limit} characters`);
}
// Content type specific suggestions
if (contentType.includes('ad')) {
suggestions.platform_optimization.push('Focus on clear value proposition and strong call-to-action');
suggestions.platform_optimization.push('Use emotional triggers that resonate with your target audience');
} else if (contentType.includes('story')) {
suggestions.platform_optimization.push('Keep it concise and visually engaging');
suggestions.platform_optimization.push('Use first-person narrative for authenticity');
}
return {
success: true,
suggestions,
persona_context: {
persona_name: corePersona?.persona_name,
archetype: corePersona?.archetype,
confidence_score: corePersona?.confidence_score,
core_belief: corePersona?.core_belief
}
};
} catch (error) {
console.error('Error getting persona writing suggestions:', error);
return {
success: false,
error: 'Failed to get persona writing suggestions'
};
}
}
});
return null; // This component only registers actions
};
export default RegisterFacebookActionsEnhanced;

View File

@@ -5,9 +5,11 @@ import '@copilotkit/react-ui/styles.css';
import './styles/alwrity-copilot.css';
import RegisterLinkedInActions from './RegisterLinkedInActions';
import RegisterLinkedInEditActions from './RegisterLinkedInEditActions';
import RegisterLinkedInActionsEnhanced from './RegisterLinkedInActionsEnhanced';
import { Header, ContentEditor, LoadingIndicator, WelcomeMessage, ProgressTracker } from './components';
import { useLinkedInWriter } from './hooks/useLinkedInWriter';
import { useCopilotPersistence } from './utils/enhancedPersistence';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
const useCopilotActionTyped = useCopilotAction as any;
@@ -18,6 +20,15 @@ interface LinkedInWriterProps {
}
const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
return (
<PlatformPersonaProvider platform="linkedin">
<LinkedInWriterContent className={className} />
</PlatformPersonaProvider>
);
};
// Main LinkedIn Writer Content Component
const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
const {
// State
draft,
@@ -68,6 +79,8 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
summarizeHistory
} = useLinkedInWriter();
// Get persona context for enhanced AI assistance
const { corePersona, platformPersona, loading: personaLoading } = usePlatformPersonaContext();
// Get enhanced persistence functionality
@@ -397,6 +410,54 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
draft={draft}
getHistoryLength={getHistoryLength}
/>
{/* Persona Integration Indicator */}
{corePersona && !personaLoading && (
<div
style={{
padding: '8px 16px',
backgroundColor: '#f0f8ff',
borderBottom: '1px solid #e1e8ed',
fontSize: '12px',
color: '#666',
display: 'flex',
alignItems: 'center',
gap: '8px',
cursor: 'help',
position: 'relative'
}}
title={`Complete Persona Details:
🎭 PERSONA: ${corePersona.persona_name}
📋 ARCHETYPE: ${corePersona.archetype}
💭 CORE BELIEF: ${corePersona.core_belief}
📊 CONFIDENCE: ${corePersona.confidence_score}%
📝 LINGUISTIC FINGERPRINT:
• Sentence Length: ${corePersona.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words || 'Unknown'} words average
• Voice Ratio: ${corePersona.linguistic_fingerprint?.sentence_metrics?.active_to_passive_ratio || 'Unknown'}
• Go-to Words: ${corePersona.linguistic_fingerprint?.lexical_features?.go_to_words?.join(', ') || 'None specified'}
• Avoid Words: ${corePersona.linguistic_fingerprint?.lexical_features?.avoid_words?.join(', ') || 'None specified'}
🎯 PLATFORM OPTIMIZATION (LinkedIn):
• Character Limit: ${platformPersona?.content_format_rules?.character_limit || '3000'} characters
• Optimal Length: ${platformPersona?.content_format_rules?.optimal_length || '150-300 words'}
• Engagement Pattern: ${platformPersona?.engagement_patterns?.posting_frequency || '2-3 times per week'}
• Hashtag Strategy: ${platformPersona?.lexical_features?.hashtag_strategy || '3-5 relevant hashtags'}
✨ This persona is actively optimizing your content generation and AI assistance!`}
>
<span style={{ color: '#0073b1' }}>🎭</span>
<span><strong>Persona Active:</strong> {corePersona.persona_name} ({corePersona.archetype})</span>
<span style={{ marginLeft: 'auto', fontSize: '11px' }}>
Confidence: {corePersona.confidence_score}% |
Platform: LinkedIn Optimized
</span>
<span style={{ fontSize: '10px', color: '#999', marginLeft: '8px' }}>
(Hover for details)
</span>
</div>
)}
{/* Lightweight progress tracker under header */}
<div style={{
padding: '6px 16px',
@@ -456,8 +517,10 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
</div>
{/* Register CopilotKit Actions */}
<RegisterLinkedInActions />
<RegisterLinkedInEditActions />
<RegisterLinkedInActions />
<RegisterLinkedInEditActions />
{/* Enhanced Persona-Aware Actions */}
<RegisterLinkedInActionsEnhanced />
{/* CopilotKit Sidebar */}
<CopilotSidebar
@@ -466,7 +529,7 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
title: 'ALwrity Co-Pilot',
initial: draft ?
'Great! I can see you have content to work with. Use the quick edit suggestions below to refine your post in real-time, or ask me to make specific changes.' :
'Hi! I\'m your ALwrity Co-Pilot, your LinkedIn writing assistant. I can help you create professional posts, articles, carousels, video scripts, and comment responses. What would you like to create today?'
`Hi! I'm your ALwrity Co-Pilot, your LinkedIn writing assistant${corePersona ? ` with ${corePersona.persona_name} persona optimization` : ''}. I can help you create professional posts, articles, carousels, video scripts, and comment responses. Try the new persona-aware actions for enhanced content generation!`
}}
suggestions={getIntelligentSuggestions()}
makeSystemMessage={(context: string, additional?: string) => {
@@ -479,7 +542,25 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
const industry = prefs.industry || 'Technology';
const audience = prefs.target_audience || 'professionals';
const guidance = `
// Enhanced persona-aware guidance
const personaGuidance = corePersona && platformPersona ? `
PERSONA-AWARE WRITING GUIDANCE:
- PERSONA: ${corePersona.persona_name} (${corePersona.archetype})
- CORE BELIEF: ${corePersona.core_belief}
- CONFIDENCE SCORE: ${corePersona.confidence_score}%
- LINGUISTIC STYLE: ${corePersona.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words || 'Unknown'} words average, ${corePersona.linguistic_fingerprint?.sentence_metrics?.active_to_passive_ratio || 'Unknown'} active/passive ratio
- GO-TO WORDS: ${corePersona.linguistic_fingerprint?.lexical_features?.go_to_words?.join(', ') || 'None specified'}
- AVOID WORDS: ${corePersona.linguistic_fingerprint?.lexical_features?.avoid_words?.join(', ') || 'None specified'}
PLATFORM OPTIMIZATION (LinkedIn):
- CHARACTER LIMIT: ${platformPersona.content_format_rules?.character_limit || '3000'} characters
- OPTIMAL LENGTH: ${platformPersona.content_format_rules?.optimal_length || '150-300 words'}
- ENGAGEMENT PATTERN: ${platformPersona.engagement_patterns?.posting_frequency || '2-3 times per week'}
- HASHTAG STRATEGY: ${platformPersona.lexical_features?.hashtag_strategy || '3-5 relevant hashtags'}
ALWAYS generate content that matches this persona's linguistic fingerprint and platform optimization rules.` : '';
const guidance = `
You are ALwrity's LinkedIn Writing Assistant specializing in ${industry} content.
CRITICAL CONSTRAINTS:
@@ -487,16 +568,23 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
- INDUSTRY: Focus specifically on ${industry} industry context and terminology
- AUDIENCE: Target content specifically for ${audience}
- QUALITY: Ensure all content meets LinkedIn professional standards
${personaGuidance ? `\n${personaGuidance}` : ''}
CURRENT CONTEXT:
${currentDraft}
Available LinkedIn content tools:
- generateLinkedInPost: Create ${tone} LinkedIn posts for ${industry} ${audience}
- generateLinkedInArticle: Write ${tone} thought leadership articles about ${industry}
- generateLinkedInCarousel: Design ${tone} multi-slide carousels for ${industry} insights
- generateLinkedInVideoScript: Create ${tone} video scripts for ${industry} topics
- generateLinkedInCommentResponse: Draft ${tone} responses appropriate for ${industry}
Available LinkedIn content tools:
- generateLinkedInPost: Create ${tone} LinkedIn posts for ${industry} ${audience}
- generateLinkedInArticle: Write ${tone} thought leadership articles about ${industry}
- generateLinkedInCarousel: Design ${tone} multi-slide carousels for ${industry} insights
- generateLinkedInVideoScript: Create ${tone} video scripts for ${industry} topics
- generateLinkedInCommentResponse: Draft ${tone} responses appropriate for ${industry}
🎭 ENHANCED PERSONA-AWARE ACTIONS (Recommended):
- generateLinkedInPostWithPersona: Create posts optimized for your writing style and platform constraints
- generateLinkedInArticleWithPersona: Write articles with persona-aware optimization
- validateContentAgainstPersona: Validate existing content against your persona
- getPersonaWritingSuggestions: Get personalized writing recommendations
DIRECT DRAFT ACTIONS:
- updateLinkedInDraft: Replace the entire draft with new content
@@ -507,8 +595,8 @@ const LinkedInWriter: React.FC<LinkedInWriterProps> = ({ className = '' }) => {
For quick edits, use editLinkedInDraft with the appropriate operation. This will show a live preview of changes before applying them.
Use user preferences, context, and conversation history to personalize all content.
Always respect the user's preferred ${tone} tone and ${industry} industry focus.
Use user preferences, context, conversation history, and persona data to personalize all content.
Always respect the user's preferred ${tone} tone, ${industry} industry focus, and writing persona style.
Always use the most appropriate tool for the user's request.`.trim();
return [prefsLine, historyLine, currentDraft, guidance, additional].filter(Boolean).join('\n\n');
}}

View File

@@ -1,172 +0,0 @@
/**
* LinkedIn Writer Persona Integration Test Page
* Demonstrates the enhanced LinkedIn writer with persona-aware features
* Allows testing of different integration approaches
*/
import React, { useState } from 'react';
import { EnhancedLinkedInWriter, LinkedInWriterInlinePersona } from './LinkedInWriterWithPersona';
// Integration type options
type IntegrationType = 'sidebar' | 'inline' | 'original';
// Test page component
export const LinkedInWriterPersonaTest: React.FC = () => {
const [integrationType, setIntegrationType] = useState<IntegrationType>('sidebar');
const [showPersonaInfo, setShowPersonaInfo] = useState(true);
const renderSelectedIntegration = () => {
switch (integrationType) {
case 'sidebar':
return <EnhancedLinkedInWriter />;
case 'inline':
return <LinkedInWriterInlinePersona />;
case 'original':
return (
<div className="p-6">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">Original LinkedIn Writer</h3>
<p className="text-yellow-700">
This shows the original LinkedIn writer without persona integration.
Switch to "Sidebar" or "Inline" to see the enhanced version.
</p>
</div>
<div className="border rounded-lg p-4 bg-gray-50">
<p className="text-gray-600 text-center">
Original LinkedIn Writer Component would render here
</p>
</div>
</div>
);
default:
return <EnhancedLinkedInWriter />;
}
};
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 py-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">
LinkedIn Writer Persona Integration Test
</h1>
<p className="text-gray-600 mt-2">
Test the enhanced LinkedIn writer with persona-aware AI assistance
</p>
</div>
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="showPersonaInfo"
checked={showPersonaInfo}
onChange={(e) => setShowPersonaInfo(e.target.checked)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="showPersonaInfo" className="text-sm text-gray-700">
Show Persona Info
</label>
</div>
</div>
</div>
{/* Integration Type Selector */}
<div className="mt-6">
<div className="flex space-x-1 bg-gray-100 p-1 rounded-lg">
{[
{ value: 'sidebar', label: 'Sidebar Integration', description: 'Persona chat in right sidebar' },
{ value: 'inline', label: 'Inline Integration', description: 'Persona banner above content' },
{ value: 'original', label: 'Original Writer', description: 'No persona integration' }
].map((option) => (
<button
key={option.value}
onClick={() => setIntegrationType(option.value as IntegrationType)}
className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 ${
integrationType === option.value
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
}`}
>
<div className="text-center">
<div className="font-medium">{option.label}</div>
<div className="text-xs opacity-75 mt-1">{option.description}</div>
</div>
</button>
))}
</div>
</div>
{/* Feature Comparison */}
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 className="font-semibold text-blue-900 mb-2">Sidebar Integration</h4>
<ul className="text-sm text-blue-800 space-y-1">
<li> Persona chat in dedicated sidebar</li>
<li> Full-screen content editing</li>
<li> Collapsible chat interface</li>
<li> Clean separation of concerns</li>
</ul>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 className="font-semibold text-green-900 mb-2">Inline Integration</h4>
<ul className="text-sm text-green-800 space-y-1">
<li> Persona banner above content</li>
<li> Floating chat button</li>
<li> Maintains existing layout</li>
<li> Subtle persona presence</li>
</ul>
</div>
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
<h4 className="font-semibold text-gray-900 mb-2">Original Writer</h4>
<ul className="text-sm text-gray-700 space-y-1">
<li> No persona integration</li>
<li> Standard LinkedIn writer</li>
<li> Baseline functionality</li>
<li> Comparison reference</li>
</ul>
</div>
</div>
</div>
</div>
{/* Main Content */}
<div className="flex-1">
{renderSelectedIntegration()}
</div>
{/* Footer Instructions */}
<div className="bg-white border-t border-gray-200 p-6">
<div className="max-w-7xl mx-auto">
<h3 className="text-lg font-semibold text-gray-900 mb-4">How to Test</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-medium text-gray-900 mb-2">Testing Persona Integration</h4>
<ul className="text-sm text-gray-700 space-y-1">
<li> Switch between integration types to see different approaches</li>
<li> Check if persona data loads correctly in the sidebar/banner</li>
<li> Test the persona-aware chat functionality</li>
<li> Verify that persona context is injected into CopilotKit</li>
<li> Test platform-specific LinkedIn optimizations</li>
</ul>
</div>
<div>
<h4 className="font-medium text-gray-900 mb-2">Expected Behavior</h4>
<ul className="text-sm text-gray-700 space-y-1">
<li> Persona info should display your writing style and preferences</li>
<li> Chat should provide LinkedIn-specific content advice</li>
<li> AI responses should match your linguistic fingerprint</li>
<li> Platform constraints should be respected (character limits, etc.)</li>
<li> Seamless integration with existing LinkedIn writer functionality</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
};
export default LinkedInWriterPersonaTest;

View File

@@ -1,203 +0,0 @@
/**
* Enhanced LinkedIn Writer with Persona Integration
* Wraps the existing LinkedIn writer with persona context and adds persona-aware chat
* Provides intelligent, contextual assistance based on user's writing persona
*/
import React, { useState } from 'react';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext';
import { PlatformPersonaChat } from '../shared/CopilotKit';
import LinkedInWriter from './LinkedInWriter';
import { PlatformType } from '../../types/PlatformPersonaTypes';
// Persona Info Display Component
const PersonaInfoDisplay: React.FC = () => {
const { corePersona, platformPersona, loading, error } = usePlatformPersonaContext();
if (loading) {
return (
<div className="flex items-center justify-center p-3 bg-blue-50 border border-blue-200 rounded-lg">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span className="text-sm text-blue-700">Loading persona...</span>
</div>
);
}
if (error || !corePersona) {
return (
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
<div className="flex items-center">
<svg className="h-4 w-4 text-yellow-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<span className="text-sm text-yellow-700">Persona not available - using default LinkedIn settings</span>
</div>
</div>
);
}
return (
<div className="p-3 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<span className="text-blue-600 font-semibold text-sm">
{corePersona.persona_name.charAt(0).toUpperCase()}
</span>
</div>
<div>
<h4 className="font-medium text-blue-900 text-sm">
{corePersona.persona_name}
</h4>
<p className="text-xs text-blue-700">{corePersona.archetype}</p>
</div>
</div>
<div className="text-right">
<div className="text-xs text-blue-600 bg-blue-100 px-2 py-1 rounded-full">
LinkedIn
</div>
<div className="text-xs text-blue-600 mt-1">
{corePersona.confidence_score}% confidence
</div>
</div>
</div>
{/* Linguistic Fingerprint Summary */}
{corePersona.linguistic_fingerprint && (
<div className="mt-2 pt-2 border-t border-blue-200">
<div className="flex items-center justify-between text-xs text-blue-700">
<span>Style: {corePersona.linguistic_fingerprint.lexical_features.vocabulary_level}</span>
<span>Length: ~{corePersona.linguistic_fingerprint.sentence_metrics.average_sentence_length_words} words</span>
<span>Voice: {corePersona.linguistic_fingerprint.sentence_metrics.active_to_passive_ratio}</span>
</div>
</div>
)}
{/* Platform Optimization */}
{platformPersona && (
<div className="mt-2 pt-2 border-t border-blue-200">
<div className="flex items-center justify-between text-xs text-blue-700">
<span>Optimal: {platformPersona.content_format_rules?.optimal_length || 'N/A'}</span>
<span>Limit: {platformPersona.content_format_rules?.character_limit || 'N/A'} chars</span>
<span>Frequency: {platformPersona.engagement_patterns?.posting_frequency || 'N/A'}</span>
</div>
</div>
)}
</div>
);
};
// Persona-Aware Chat Panel
const PersonaChatPanel: React.FC = () => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="border-l border-gray-200 bg-white">
{/* Chat Header */}
<div className="p-3 border-b border-gray-200 bg-gray-50">
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900 text-sm">AI Content Assistant</h3>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-gray-500 hover:text-gray-700 transition-colors"
>
<svg
className={`w-4 h-4 transform transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
<p className="text-xs text-gray-600 mt-1">
Get personalized LinkedIn content advice based on your writing style
</p>
</div>
{/* Persona Info */}
<div className="p-3">
<PersonaInfoDisplay />
</div>
{/* Chat Interface */}
<div className={`transition-all duration-300 ease-in-out ${isExpanded ? 'max-h-96' : 'max-h-0'} overflow-hidden`}>
<div className="p-3">
<PlatformPersonaChat
platform="linkedin"
showWelcomeMessage={true}
showSuggestedPrompts={true}
className="border rounded-lg"
/>
</div>
</div>
</div>
);
};
// Enhanced LinkedIn Writer Container
const EnhancedLinkedInWriter: React.FC = () => {
return (
<PlatformPersonaProvider platform="linkedin">
<div className="flex h-screen bg-gray-50">
{/* Main LinkedIn Writer */}
<div className="flex-1 flex flex-col">
<LinkedInWriter />
</div>
{/* Persona Chat Sidebar */}
<div className="w-80 flex-shrink-0">
<PersonaChatPanel />
</div>
</div>
</PlatformPersonaProvider>
);
};
// Alternative: Inline Integration (if you prefer to keep the existing layout)
const LinkedInWriterInlinePersona: React.FC = () => {
return (
<PlatformPersonaProvider platform="linkedin">
<div className="linkedin-writer-with-persona">
{/* Persona Banner */}
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-blue-200 p-4">
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-between">
<div className="flex items-center">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
</svg>
</div>
<div>
<h2 className="text-lg font-semibold text-blue-900">LinkedIn Content Writer</h2>
<p className="text-sm text-blue-700">Powered by your personal writing persona</p>
</div>
</div>
<PersonaInfoDisplay />
</div>
</div>
</div>
{/* Main LinkedIn Writer */}
<LinkedInWriter />
{/* Floating Persona Chat Button */}
<div className="fixed bottom-6 right-6 z-50">
<button className="bg-blue-600 hover:bg-blue-700 text-white rounded-full p-4 shadow-lg transition-all duration-200 hover:scale-110">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
</button>
</div>
</div>
</PlatformPersonaProvider>
);
};
// Export both integration options
export { EnhancedLinkedInWriter, LinkedInWriterInlinePersona };
// Default export for the enhanced version
export default EnhancedLinkedInWriter;

View File

@@ -0,0 +1,446 @@
import React from 'react';
import { useCopilotAction } from '@copilotkit/react-core';
import { linkedInWriterApi, GroundingLevel } from '../../services/linkedInWriterApi';
import {
mapPostType,
mapTone,
mapIndustry,
mapSearchEngine,
readPrefs
} from './utils/linkedInWriterUtils';
import { usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
const useCopilotActionTyped = useCopilotAction as any;
const RegisterLinkedInActionsEnhanced: React.FC = () => {
// Get persona context for enhanced content generation
const { corePersona, platformPersona } = usePlatformPersonaContext();
// Helper function to apply persona constraints to content
const applyPersonaConstraints = (content: string, constraints: any) => {
if (!constraints) return content;
let enhancedContent = content;
// Apply sentence length constraints
if (constraints.sentence_metrics?.average_sentence_length_words) {
const targetLength = constraints.sentence_metrics.average_sentence_length_words;
// This is a simplified example - in practice, you'd use more sophisticated NLP
console.log(`🎭 Applying persona sentence length constraint: ${targetLength} words average`);
}
// Apply vocabulary constraints
if (constraints.lexical_features?.go_to_words?.length > 0) {
console.log(`🎭 Using persona go-to words: ${constraints.lexical_features.go_to_words.join(', ')}`);
}
if (constraints.lexical_features?.avoid_words?.length > 0) {
console.log(`🎭 Avoiding persona avoid words: ${constraints.lexical_features.avoid_words.join(', ')}`);
}
return enhancedContent;
};
// Enhanced LinkedIn Post Generation with Persona
useCopilotActionTyped({
name: 'generateLinkedInPostWithPersona',
description: 'Generate a professional LinkedIn post optimized for your writing persona and platform constraints',
parameters: [
{ name: 'topic', type: 'string', required: false },
{ name: 'industry', type: 'string', required: false },
{ name: 'post_type', type: 'string', required: false },
{ name: 'tone', type: 'string', required: false },
{ name: 'refine_existing', type: 'boolean', required: false, description: 'Whether to refine existing content instead of creating new' }
],
handler: async (args: any) => {
const prefs = readPrefs();
// Persona-aware progress tracking
const personaInfo = corePersona ? `using ${corePersona.persona_name} persona` : 'with standard settings';
window.dispatchEvent(new CustomEvent('linkedinwriter:progressInit', { detail: {
steps: [
{ id: 'persona_analysis', label: `Analyzing ${personaInfo}` },
{ id: 'personalize', label: 'Personalizing topic & context' },
{ id: 'prepare_queries', label: 'Preparing research queries' },
{ id: 'research', label: 'Conducting research & analysis' },
{ id: 'grounding', label: 'Applying AI grounding' },
{ id: 'content_generation', label: 'Generating persona-optimized content' },
{ id: 'persona_validation', label: 'Validating against persona constraints' },
{ id: 'citations', label: 'Extracting citations' },
{ id: 'quality_analysis', label: 'Quality assessment' },
{ id: 'finalize', label: 'Finalizing & optimizing' }
]
}}));
// Start with persona analysis
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'persona_analysis',
status: 'active',
message: corePersona ?
`Analyzing ${corePersona.persona_name} (${corePersona.archetype}) writing style...` :
'No persona data available, using standard settings...'
}
}));
// If refining existing content, use the current draft as context
if (args?.refine_existing) {
const textarea = document.querySelector('textarea') as HTMLTextAreaElement;
const currentDraft = textarea?.value || '';
if (currentDraft) {
console.log(`🎭 Refining existing content: ${currentDraft.substring(0, 100)}...`);
}
}
// Apply persona constraints to parameters
const personaConstraints = platformPersona?.content_format_rules as any || {};
const maxLength = personaConstraints.character_limit || prefs.max_length || 2000;
const optimalLength = personaConstraints.optimal_length || '150-300 words';
console.log(`🎭 Persona constraints applied: Max ${maxLength} chars, Optimal: ${optimalLength}`);
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'persona_analysis',
status: 'completed',
message: `Persona analysis complete. Using ${maxLength} character limit and ${optimalLength} optimal length.`
}
}));
// Start detailed progress tracking
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'personalize',
status: 'active',
message: 'Analyzing topic, industry context, and target audience...'
}
}));
const res = await linkedInWriterApi.generatePost({
topic: args?.topic || prefs.topic || 'AI transformation in business',
industry: mapIndustry(args?.industry || prefs.industry),
post_type: mapPostType(args?.post_type || prefs.post_type),
tone: mapTone(args?.tone || prefs.tone),
target_audience: args?.target_audience || prefs.target_audience || 'Business leaders and professionals',
key_points: args?.key_points || prefs.key_points || [],
include_hashtags: args?.include_hashtags ?? (prefs.include_hashtags ?? true),
include_call_to_action: args?.include_call_to_action ?? (prefs.include_call_to_action ?? true),
research_enabled: args?.research_enabled ?? (prefs.research_enabled ?? true),
search_engine: mapSearchEngine(args?.search_engine || prefs.search_engine),
max_length: maxLength,
grounding_level: 'enhanced' as GroundingLevel,
include_citations: true
});
if (res.success && res.data) {
// Apply persona constraints to generated content
let enhancedContent = res.data.content;
if (corePersona && platformPersona) {
enhancedContent = applyPersonaConstraints(enhancedContent, {
sentence_metrics: corePersona.linguistic_fingerprint?.sentence_metrics,
lexical_features: corePersona.linguistic_fingerprint?.lexical_features
});
}
// Update progress with persona validation
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'persona_validation',
status: 'completed',
message: 'Content validated against persona constraints'
}
}));
// Update progress with detailed information
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'personalize',
status: 'completed',
message: 'Topic personalized successfully'
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'prepare_queries',
status: 'completed',
message: `Prepared ${(res.data?.search_queries || []).length} research queries`
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'research',
status: 'completed',
message: `Research completed with ${(res.research_sources || []).length} sources`
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'grounding',
status: 'completed',
message: 'AI grounding applied successfully'
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'content_generation',
status: 'completed',
message: 'Persona-optimized content generated successfully'
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'citations',
status: 'completed',
message: `Citations extracted: ${(res.data?.citations || []).length} sources`
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'quality_analysis',
status: 'completed',
message: `Quality score: ${res.data?.quality_metrics?.overall_score || 'N/A'}`
}
}));
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'finalize',
status: 'completed',
message: 'LinkedIn post finalized with persona optimization!'
}
}));
// Return enhanced content with persona information
return {
success: true,
content: enhancedContent,
persona_applied: corePersona ? {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score,
constraints_applied: {
max_length: maxLength,
optimal_length: optimalLength,
linguistic_style: corePersona.linguistic_fingerprint?.sentence_metrics?.preferred_sentence_type
}
} : null,
message: `✅ LinkedIn post generated successfully with ${corePersona ? 'persona optimization' : 'standard settings'}!`,
research_sources: res.research_sources || [],
citations: res.data?.citations || [],
quality_metrics: res.data?.quality_metrics
};
} else {
window.dispatchEvent(new CustomEvent('linkedinwriter:progressError', { detail: { id: 'finalize', details: res.error } }));
return { success: false, message: res.error || 'Failed to generate LinkedIn post' };
}
}
});
// Enhanced LinkedIn Article Generation with Persona
useCopilotActionTyped({
name: 'generateLinkedInArticleWithPersona',
description: 'Generate a LinkedIn article optimized for your writing persona and platform constraints',
parameters: [
{ name: 'topic', type: 'string', required: false },
{ name: 'industry', type: 'string', required: false },
{ name: 'article_length', type: 'string', required: false, description: 'Short, Medium, or Long article' }
],
handler: async (args: any) => {
// Persona-aware progress tracking
const personaInfo = corePersona ? `using ${corePersona.persona_name} persona` : 'with standard settings';
window.dispatchEvent(new CustomEvent('linkedinwriter:progressInit', { detail: {
steps: [
{ id: 'persona_analysis', label: `Analyzing ${personaInfo}` },
{ id: 'personalize', label: 'Personalizing topic & context' },
{ id: 'prepare_queries', label: 'Preparing research queries' },
{ id: 'research', label: 'Conducting research & analysis' },
{ id: 'grounding', label: 'Applying AI grounding' },
{ id: 'content_generation', label: 'Generating persona-optimized article' },
{ id: 'persona_validation', label: 'Validating against persona constraints' },
{ id: 'citations', label: 'Extracting citations' },
{ id: 'quality_analysis', label: 'Quality assessment' },
{ id: 'finalize', label: 'Finalizing & optimizing' }
]
}}));
// Start with persona analysis
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'persona_analysis',
status: 'active',
message: corePersona ?
`Analyzing ${corePersona.persona_name} (${corePersona.archetype}) writing style...` :
'No persona data available, using standard settings...'
}
}));
// Apply persona constraints
const articleLength = args?.article_length || 'Medium';
console.log(`🎭 Generating ${articleLength} article with persona constraints`);
window.dispatchEvent(new CustomEvent('linkedinwriter:progressStep', {
detail: {
id: 'persona_analysis',
status: 'completed',
message: `Persona analysis complete. Generating ${articleLength} article.`
}
}));
// Continue with article generation...
// (Implementation would continue similar to the post generation)
return {
success: true,
message: `✅ LinkedIn article generation started with persona optimization!`,
persona_applied: corePersona ? {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score
} : null
};
}
});
// Persona-Aware Content Validation Action
useCopilotActionTyped({
name: 'validateContentAgainstPersona',
description: 'Validate existing content against your writing persona and suggest improvements',
parameters: [
{ name: 'content', type: 'string', required: true, description: 'Content to validate' }
],
handler: async (args: any) => {
if (!corePersona || !platformPersona) {
return {
success: false,
message: 'No persona data available for validation'
};
}
const content = args.content;
const validation = {
sentence_length: {
current: content.split('.').filter((s: string) => s.trim().length > 0).length,
target: corePersona.linguistic_fingerprint?.sentence_metrics?.average_sentence_length_words || 15,
status: 'analyzing'
},
vocabulary_usage: {
go_to_words_used: 0,
avoid_words_used: 0,
suggestions: [] as string[]
},
platform_compliance: {
character_count: content.length,
optimal_range: (platformPersona.content_format_rules as any)?.optimal_length || '150-300 words',
status: 'analyzing',
suggestions: [] as string[]
}
};
// Analyze vocabulary usage
const goToWords = corePersona.linguistic_fingerprint?.lexical_features?.go_to_words || [];
const avoidWords = corePersona.linguistic_fingerprint?.lexical_features?.avoid_words || [];
goToWords.forEach(word => {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
const matches = content.match(regex);
if (matches) {
validation.vocabulary_usage.go_to_words_used += matches.length;
}
});
avoidWords.forEach(word => {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
const matches = content.match(regex);
if (matches) {
validation.vocabulary_usage.avoid_words_used += matches.length;
validation.vocabulary_usage.suggestions.push(`Consider replacing "${word}" with a more aligned word`);
}
});
// Platform compliance check
const charLimit = (platformPersona.content_format_rules as any)?.character_limit || 3000;
if (content.length > charLimit) {
validation.platform_compliance.status = 'exceeds_limit';
validation.platform_compliance.suggestions = [`Content exceeds ${charLimit} character limit by ${content.length - charLimit} characters`];
} else {
validation.platform_compliance.status = 'within_limit';
}
return {
success: true,
validation,
persona: {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score
},
message: 'Content validation complete against your writing persona!',
recommendations: validation.vocabulary_usage.suggestions.concat(validation.platform_compliance.suggestions || [])
};
}
});
// Persona-Aware Writing Style Suggestions
useCopilotActionTyped({
name: 'getPersonaWritingSuggestions',
description: 'Get personalized writing suggestions based on your persona and LinkedIn platform',
parameters: [
{ name: 'content_type', type: 'string', required: false, description: 'Type of content (post, article, carousel)' },
{ name: 'topic', type: 'string', required: false, description: 'Content topic for context' }
],
handler: async (args: any) => {
if (!corePersona || !platformPersona) {
return {
success: false,
message: 'No persona data available for suggestions'
};
}
const contentType = args.content_type || 'post';
const topic = args.topic || 'general business';
const suggestions = {
writing_style: {
sentence_structure: corePersona.linguistic_fingerprint?.sentence_metrics?.preferred_sentence_type || 'balanced',
tone_recommendation: (corePersona as any).tonal_range?.default_tone || 'professional_friendly',
vocabulary_level: corePersona.linguistic_fingerprint?.lexical_features?.vocabulary_level || 'professional'
},
platform_optimization: {
character_limit: (platformPersona.content_format_rules as any)?.character_limit || 3000,
optimal_length: (platformPersona.content_format_rules as any)?.optimal_length || '150-300 words',
hashtag_strategy: (platformPersona.lexical_features as any)?.hashtag_strategy || '3-5 relevant hashtags'
},
persona_specific: {
go_to_words: corePersona.linguistic_fingerprint?.lexical_features?.go_to_words || [],
avoid_words: corePersona.linguistic_fingerprint?.lexical_features?.avoid_words || [],
rhetorical_style: corePersona.linguistic_fingerprint?.rhetorical_devices?.metaphors || 'business-focused'
}
};
return {
success: true,
suggestions,
persona: {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score
},
message: `Personalized writing suggestions for ${contentType} about ${topic}`,
tip: `Use these suggestions to maintain your unique ${corePersona.persona_name} voice while optimizing for LinkedIn!`
};
}
});
return null; // This component only registers actions
};
export default RegisterLinkedInActionsEnhanced;

View File

@@ -20,6 +20,4 @@ export { default as ImageGenerationSuggestions } from './ImageGenerationSuggesti
export { default as ImageGenerationDemo } from './ImageGenerationDemo';
export { default as ImageGenerationTest } from './ImageGenerationTest';
// Persona Integration Components
export { default as LinkedInWriterPersonaTest } from '../LinkedInWriterPersonaTest';
export { EnhancedLinkedInWriter, LinkedInWriterInlinePersona } from '../LinkedInWriterWithPersona';
// Persona Integration Components - Now integrated into main LinkedInWriter

View File

@@ -5,8 +5,9 @@
*/
import React from 'react';
import { PlatformPersonaProvider, PlatformPersonaChat } from '../PersonaContext';
import { PlatformType } from '../../types/PlatformPersonaTypes';
import { PlatformPersonaProvider } from '../PersonaContext';
import { PlatformPersonaChat } from './PlatformPersonaChat';
import { PlatformType } from '../../../types/PlatformPersonaTypes';
// Example: LinkedIn Writer Integration
export const LinkedInWriterWithPersonaChat: React.FC = () => {

View File

@@ -4,10 +4,10 @@
* Provides intelligent, contextual assistance based on user's writing persona
*/
import React, { useCallback, useMemo } from 'react';
import { CopilotChat, CopilotChatProps } from '@copilotkit/react-chat';
import React, { useCallback, useMemo, useState } from 'react';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { usePlatformPersonaContext } from '../PersonaContext';
import { PlatformType, WritingPersona, PlatformAdaptation } from '../../types/PlatformPersonaTypes';
import { PlatformType, WritingPersona, PlatformAdaptation } from '../../../types/PlatformPersonaTypes';
// Platform-specific chat configurations
interface PlatformChatConfig {
@@ -173,6 +173,7 @@ export const PlatformPersonaChat: React.FC<PlatformPersonaChatProps> = ({
customSystemMessage
}) => {
const { corePersona, platformPersona, loading, error } = usePlatformPersonaContext();
const [isChatOpen, setIsChatOpen] = useState(false);
// Generate platform-specific chat configuration
const chatConfig = useMemo(() =>
@@ -197,17 +198,6 @@ export const PlatformPersonaChat: React.FC<PlatformPersonaChatProps> = ({
return `${systemMessage}\n\nCurrent Context: ${contextString}`;
}, [systemMessage]);
// Custom CopilotChat props
const copilotChatProps: CopilotChatProps = {
makeSystemMessage,
placeholder: chatConfig.placeholder,
className: `platform-persona-chat ${className}`,
showWelcomeMessage,
showSuggestedPrompts,
suggestedPrompts: showSuggestedPrompts ? chatConfig.suggestedPrompts : undefined,
welcomeMessage: showWelcomeMessage ? chatConfig.welcomeMessage : undefined
};
// Loading state
if (loading) {
return (
@@ -286,8 +276,39 @@ export const PlatformPersonaChat: React.FC<PlatformPersonaChatProps> = ({
</div>
)}
{/* CopilotKit Chat Component */}
<CopilotChat {...copilotChatProps} />
{/* Chat Toggle Button */}
<div className="text-center">
<button
onClick={() => setIsChatOpen(!isChatOpen)}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
{isChatOpen ? 'Close Chat' : 'Open AI Assistant'}
</button>
</div>
{/* CopilotKit Sidebar */}
{isChatOpen && (
<CopilotSidebar
className="alwrity-copilot-sidebar platform-persona-chat"
labels={{
title: `${platform.charAt(0).toUpperCase() + platform.slice(1)} Content Assistant`,
initial: chatConfig.welcomeMessage
}}
suggestions={chatConfig.suggestedPrompts}
makeSystemMessage={makeSystemMessage}
observabilityHooks={{
onChatExpanded: () => {
console.log(`[${platform}] Persona chat opened`);
},
onMessageSent: (message: any) => {
const text = typeof message === 'string' ? message : (message?.content ?? '');
if (text) {
console.log(`[${platform}] User message:`, { content_length: text.length });
}
}
}}
/>
)}
</div>
);
};

View File

@@ -1,173 +0,0 @@
/**
* Platform Persona Chat Test Component
* Demonstrates and tests the PlatformPersonaChat component
* Shows how to integrate persona-aware chat into different platforms
*/
import React, { useState } from 'react';
import { PlatformPersonaProvider } from '../PersonaContext';
import { PlatformPersonaChat } from './index';
import { PlatformType } from '../../types/PlatformPersonaTypes';
// Platform selection component
const PlatformSelector: React.FC<{
selectedPlatform: PlatformType;
onPlatformChange: (platform: PlatformType) => void;
}> = ({ selectedPlatform, onPlatformChange }) => {
const platforms: { value: PlatformType; label: string; description: string }[] = [
{ value: 'linkedin', label: 'LinkedIn', description: 'Professional networking & thought leadership' },
{ value: 'facebook', label: 'Facebook', description: 'Community building & social engagement' },
{ value: 'instagram', label: 'Instagram', description: 'Visual storytelling & aesthetic content' },
{ value: 'twitter', label: 'Twitter', description: 'Concise messaging & viral potential' },
{ value: 'blog', label: 'Blog', description: 'Long-form content & SEO optimization' },
{ value: 'medium', label: 'Medium', description: 'Storytelling & publication strategy' },
{ value: 'substack', label: 'Substack', description: 'Newsletter & subscription focus' }
];
return (
<div className="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 className="text-lg font-semibold mb-3">Select Platform to Test</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{platforms.map((platform) => (
<button
key={platform.value}
onClick={() => onPlatformChange(platform.value)}
className={`p-3 text-left rounded-lg border transition-all ${
selectedPlatform === platform.value
? 'border-blue-500 bg-blue-50 text-blue-900'
: 'border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50'
}`}
>
<div className="font-medium">{platform.label}</div>
<div className="text-sm text-gray-600 mt-1">{platform.description}</div>
</button>
))}
</div>
</div>
);
};
// Chat configuration options
const ChatConfigOptions: React.FC<{
showWelcomeMessage: boolean;
showSuggestedPrompts: boolean;
onToggleWelcomeMessage: () => void;
onToggleSuggestedPrompts: () => void;
}> = ({ showWelcomeMessage, showSuggestedPrompts, onToggleWelcomeMessage, onToggleSuggestedPrompts }) => {
return (
<div className="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
<h4 className="text-sm font-medium text-yellow-900 mb-2">Chat Configuration</h4>
<div className="flex flex-wrap gap-4">
<label className="flex items-center">
<input
type="checkbox"
checked={showWelcomeMessage}
onChange={onToggleWelcomeMessage}
className="mr-2"
/>
<span className="text-sm text-yellow-800">Show Welcome Message</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={showSuggestedPrompts}
onChange={onToggleSuggestedPrompts}
className="mr-2"
/>
<span className="text-sm text-yellow-800">Show Suggested Prompts</span>
</label>
</div>
</div>
);
};
// Main test component
export const PlatformPersonaChatTest: React.FC = () => {
const [selectedPlatform, setSelectedPlatform] = useState<PlatformType>('linkedin');
const [showWelcomeMessage, setShowWelcomeMessage] = useState(true);
const [showSuggestedPrompts, setShowSuggestedPrompts] = useState(true);
const toggleWelcomeMessage = () => setShowWelcomeMessage(!showWelcomeMessage);
const toggleSuggestedPrompts = () => setShowSuggestedPrompts(!showSuggestedPrompts);
return (
<div className="max-w-4xl mx-auto p-6">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 mb-2">
Platform Persona Chat Test
</h1>
<p className="text-gray-600">
Test the persona-aware CopilotKit integration across different platforms
</p>
</div>
{/* Platform Selector */}
<PlatformSelector
selectedPlatform={selectedPlatform}
onPlatformChange={setSelectedPlatform}
/>
{/* Chat Configuration */}
<ChatConfigOptions
showWelcomeMessage={showWelcomeMessage}
showSuggestedPrompts={showSuggestedPrompts}
onToggleWelcomeMessage={toggleWelcomeMessage}
onToggleSuggestedPrompts={toggleSuggestedPrompts}
/>
{/* Persona Chat Component */}
<div className="border rounded-lg overflow-hidden">
<div className="bg-gray-100 px-4 py-2 border-b">
<h3 className="font-medium text-gray-900">
{selectedPlatform.charAt(0).toUpperCase() + selectedPlatform.slice(1)} Persona Chat
</h3>
<p className="text-sm text-gray-600">
AI-powered content assistance with your personal writing style
</p>
</div>
<PlatformPersonaProvider platform={selectedPlatform}>
<PlatformPersonaChat
platform={selectedPlatform}
showWelcomeMessage={showWelcomeMessage}
showSuggestedPrompts={showSuggestedPrompts}
className="p-4"
/>
</PlatformPersonaProvider>
</div>
{/* Usage Instructions */}
<div className="mt-6 p-4 bg-green-50 border border-green-200 rounded-lg">
<h4 className="font-medium text-green-900 mb-2">How to Test</h4>
<ul className="text-sm text-green-800 space-y-1">
<li> Select different platforms to see platform-specific chat configurations</li>
<li> Toggle welcome message and suggested prompts to test different chat modes</li>
<li> Ask questions about content strategy, writing style, or platform optimization</li>
<li> Notice how the AI adapts responses to your persona and platform constraints</li>
<li> Try the quick action buttons for platform-specific suggestions</li>
</ul>
</div>
{/* Technical Details */}
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h4 className="font-medium text-blue-900 mb-2">Technical Implementation</h4>
<div className="text-sm text-blue-800 space-y-2">
<p>
<strong>Context Injection:</strong> The chat automatically receives your writing persona,
linguistic fingerprint, and platform-specific constraints through CopilotKit's context system.
</p>
<p>
<strong>Dynamic System Messages:</strong> System messages are generated dynamically based on
your persona data and selected platform, ensuring AI responses match your writing style.
</p>
<p>
<strong>Platform Optimization:</strong> Each platform has specific character limits,
engagement patterns, and best practices that are automatically incorporated into AI responses.
</p>
</div>
</div>
</div>
);
};
export default PlatformPersonaChatTest;

View File

@@ -4,7 +4,6 @@
*/
export { PlatformPersonaChat } from './PlatformPersonaChat';
export { PlatformPersonaChatTest } from './PlatformPersonaChatTest';
export { IntegrationExample } from './IntegrationExample';
// Re-export types for convenience

View File

@@ -1,99 +0,0 @@
/**
* Persona Test Component
* Simple component to test and demonstrate the PlatformPersonaProvider
* This can be used to verify the implementation works correctly
*/
import React from 'react';
import { PlatformPersonaProvider, usePlatformPersonaContext } from './index';
import { PlatformType } from '../../../types/PlatformPersonaTypes';
// Test component that uses the context
const PersonaDisplay: React.FC = () => {
const {
corePersona,
platformPersona,
platform,
loading,
error,
refreshPersonas
} = usePlatformPersonaContext();
if (loading) {
return <div>Loading persona data...</div>;
}
if (error) {
return (
<div>
<p>Error: {error}</p>
<button onClick={refreshPersonas}>Retry</button>
</div>
);
}
return (
<div className="p-4 border rounded-lg">
<h3 className="text-lg font-semibold mb-4">Persona Data for {platform}</h3>
{/* Core Persona Display */}
{corePersona && (
<div className="mb-4 p-3 bg-blue-50 rounded">
<h4 className="font-medium text-blue-900">Core Persona</h4>
<p><strong>Name:</strong> {corePersona.persona_name}</p>
<p><strong>Archetype:</strong> {corePersona.archetype}</p>
<p><strong>Core Belief:</strong> {corePersona.core_belief}</p>
<p><strong>Confidence:</strong> {corePersona.confidence_score}%</p>
</div>
)}
{/* Platform Persona Display */}
{platformPersona && (
<div className="mb-4 p-3 bg-green-50 rounded">
<h4 className="font-medium text-green-900">Platform Optimization</h4>
<p><strong>Platform:</strong> {platformPersona.platform_type}</p>
<p><strong>Character Limit:</strong> {platformPersona.content_format_rules?.character_limit || 'N/A'}</p>
<p><strong>Optimal Length:</strong> {platformPersona.content_format_rules?.optimal_length || 'N/A'}</p>
<p><strong>Posting Frequency:</strong> {platformPersona.engagement_patterns?.posting_frequency || 'N/A'}</p>
</div>
)}
{/* Linguistic Fingerprint Display */}
{corePersona?.linguistic_fingerprint && (
<div className="mb-4 p-3 bg-purple-50 rounded">
<h4 className="font-medium text-purple-900">Linguistic Fingerprint</h4>
<p><strong>Avg Sentence Length:</strong> {corePersona.linguistic_fingerprint.sentence_metrics.average_sentence_length_words} words</p>
<p><strong>Voice Ratio:</strong> {corePersona.linguistic_fingerprint.sentence_metrics.active_to_passive_ratio}</p>
<p><strong>Go-to Words:</strong> {corePersona.linguistic_fingerprint.lexical_features.go_to_words?.join(', ') || 'N/A'}</p>
</div>
)}
{/* Refresh Button */}
<button
onClick={refreshPersonas}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Refresh Personas
</button>
</div>
);
};
// Main test component with provider
interface PersonaTestComponentProps {
platform: PlatformType;
userId?: number;
}
export const PersonaTestComponent: React.FC<PersonaTestComponentProps> = ({
platform,
userId = 1
}) => {
return (
<PlatformPersonaProvider platform={platform} userId={userId}>
<PersonaDisplay />
</PlatformPersonaProvider>
);
};
export default PersonaTestComponent;

View File

@@ -4,14 +4,13 @@
* Integrates with existing persona API client and injects data into CopilotKit
*/
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback, useRef } from 'react';
import { useCopilotReadable } from '@copilotkit/react-core';
import {
WritingPersona,
PlatformAdaptation,
PlatformType,
UserPersonasResponse,
PlatformPersonaResponse
UserPersonasResponse
} from '../../../types/PlatformPersonaTypes';
import {
getUserPersonas,
@@ -50,8 +49,36 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Add request throttling
const lastRequestTime = useRef<number>(0);
const requestInProgress = useRef<boolean>(false);
const dataCacheTime = useRef<number>(0);
// Cache duration: 5 minutes
const CACHE_DURATION = 5 * 60 * 1000;
// Fetch persona data function
const fetchPersonas = async () => {
const fetchPersonas = useCallback(async () => {
const now = Date.now();
// Prevent multiple simultaneous requests
if (requestInProgress.current) {
console.log('🔄 Request already in progress, skipping...');
return;
}
// Check cache validity
if (corePersona && platformPersona && (now - dataCacheTime.current) < CACHE_DURATION) {
console.log('✅ Using cached persona data');
return;
}
// Rate limiting: minimum 2 seconds between requests
if (now - lastRequestTime.current < 2000) {
console.log('⏱️ Rate limit: waiting before next request...');
return;
}
try {
setLoading(true);
setError(null);
@@ -65,12 +92,58 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
// Handle core persona data
if (userPersonasResponse.personas && userPersonasResponse.personas.length > 0) {
const primaryPersona = userPersonasResponse.personas[0];
setCorePersona(primaryPersona);
// 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: {
sentence_metrics: {
average_sentence_length_words: 15,
preferred_sentence_type: "compound",
active_to_passive_ratio: "80:20",
sentence_complexity: "moderate",
paragraph_structure: "standard"
},
lexical_features: {
go_to_words: ["leverage", "optimize", "strategic"],
go_to_phrases: ["Let's explore", "Here's the thing"],
avoid_words: ["utilize", "synergize"],
contractions: "moderate",
vocabulary_level: "professional",
industry_terminology: [],
emotional_tone_words: []
},
rhetorical_devices: {
metaphors: "tech_mechanics",
analogies: "everyday_to_tech",
rhetorical_questions: "occasional",
storytelling_approach: "case_study",
persuasion_techniques: ["logic", "credibility"]
}
},
platform_adaptations: [],
onboarding_session_id: 1,
source_website_analysis: {},
source_research_preferences: {},
ai_analysis_version: "1.0",
confidence_score: primaryPersona.confidence_score,
analysis_date: primaryPersona.created_at,
created_at: primaryPersona.created_at,
updated_at: primaryPersona.created_at,
is_active: true
};
setCorePersona(convertedPersona);
console.log('✅ Core persona loaded:', {
name: primaryPersona.persona_name,
archetype: primaryPersona.archetype,
confidence: primaryPersona.confidence_score
name: convertedPersona.persona_name,
archetype: convertedPersona.archetype,
confidence: convertedPersona.confidence_score
});
} else {
console.warn('⚠️ No core personas found for user');
@@ -79,12 +152,87 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
// Handle platform-specific persona data
if (platformPersonaResponse) {
setPlatformPersona(platformPersonaResponse);
// Convert API response to PlatformAdaptation format
const convertedPlatformPersona: PlatformAdaptation = {
id: 1,
writing_persona_id: corePersona?.id || 1,
platform_type: platform,
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: {
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: {
question_frequency: "occasional",
story_elements: "personal_anecdotes",
visual_descriptions: "minimal",
interactive_elements: "questions"
},
tonal_range: {
default_tone: "professional_friendly",
permissible_tones: ["inspiring", "thoughtful"],
forbidden_tones: ["salesy", "academic"],
emotional_range: "moderate",
formality_level: "semi_formal"
},
stylistic_constraints: {
punctuation_preferences: "standard",
formatting_rules: "clean",
emoji_usage: "minimal",
link_placement: "end",
media_integration: "encouraged"
},
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",
hashtag_limit: platform === 'instagram' ? 30 : 3,
media_requirements: "optional",
link_restrictions: "unlimited"
},
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: {
frequency: "2-3 times per week",
optimal_days: ["Tuesday", "Wednesday", "Thursday"],
optimal_times: ["9:00 AM", "1:00 PM"],
seasonal_adjustments: "moderate"
},
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: {
algorithm_tips: ["post_consistently", "engage_with_community"],
engagement_tactics: ["ask_questions", "share_stories"],
content_strategies: ["value_first", "authentic_voice"],
growth_hacks: ["cross_promotion", "collaboration"]
},
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
setPlatformPersona(convertedPlatformPersona);
console.log('✅ Platform persona loaded:', {
platform: platformPersonaResponse.platform_type,
characterLimit: platformPersonaResponse.content_format_rules?.character_limit,
optimalLength: platformPersonaResponse.content_format_rules?.optimal_length
platform: convertedPlatformPersona.platform_type,
characterLimit: convertedPlatformPersona.content_format_rules?.character_limit,
optimalLength: convertedPlatformPersona.content_format_rules?.optimal_length
});
} else {
console.warn(`⚠️ No platform-specific persona found for ${platform}`);
@@ -101,13 +249,16 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
}
} finally {
setLoading(false);
lastRequestTime.current = Date.now();
dataCacheTime.current = Date.now();
requestInProgress.current = false;
}
};
}, [userId, platform, corePersona]);
// Initial data fetch
useEffect(() => {
fetchPersonas();
}, [platform, userId]);
}, [fetchPersonas]);
// Refresh function for manual updates
const refreshPersonas = async () => {
@@ -200,7 +351,6 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
</PlatformPersonaContext.Provider>
);
};
// Custom hook to use the context
export const usePlatformPersonaContext = () => {
const context = useContext(PlatformPersonaContext);
@@ -212,3 +362,4 @@ export const usePlatformPersonaContext = () => {
// Export the context for direct access if needed
export { PlatformPersonaContext };

View File

@@ -9,7 +9,7 @@ export {
usePlatformPersonaContext
} from './PlatformPersonaProvider';
export { PersonaTestComponent } from './PersonaTestComponent';
// PersonaTestComponent removed - functionality integrated into main components
// Re-export types for convenience
export type {