Advanced Content Hyper-Personalization Implementation
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
export { PlatformPersonaChat } from './PlatformPersonaChat';
|
||||
export { PlatformPersonaChatTest } from './PlatformPersonaChatTest';
|
||||
export { IntegrationExample } from './IntegrationExample';
|
||||
|
||||
// Re-export types for convenience
|
||||
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user