Advanced Content Hyper-Personalization Implementation

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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