diff --git a/frontend/src/components/shared/PersonaContext/PersonaTestComponent.tsx b/frontend/src/components/shared/PersonaContext/PersonaTestComponent.tsx new file mode 100644 index 00000000..8fbc0283 --- /dev/null +++ b/frontend/src/components/shared/PersonaContext/PersonaTestComponent.tsx @@ -0,0 +1,99 @@ +/** + * 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
Loading persona data...
; + } + + if (error) { + return ( +
+

Error: {error}

+ +
+ ); + } + + return ( +
+

Persona Data for {platform}

+ + {/* Core Persona Display */} + {corePersona && ( +
+

Core Persona

+

Name: {corePersona.persona_name}

+

Archetype: {corePersona.archetype}

+

Core Belief: {corePersona.core_belief}

+

Confidence: {corePersona.confidence_score}%

+
+ )} + + {/* Platform Persona Display */} + {platformPersona && ( +
+

Platform Optimization

+

Platform: {platformPersona.platform_type}

+

Character Limit: {platformPersona.content_format_rules?.character_limit || 'N/A'}

+

Optimal Length: {platformPersona.content_format_rules?.optimal_length || 'N/A'}

+

Posting Frequency: {platformPersona.engagement_patterns?.posting_frequency || 'N/A'}

+
+ )} + + {/* Linguistic Fingerprint Display */} + {corePersona?.linguistic_fingerprint && ( +
+

Linguistic Fingerprint

+

Avg Sentence Length: {corePersona.linguistic_fingerprint.sentence_metrics.average_sentence_length_words} words

+

Voice Ratio: {corePersona.linguistic_fingerprint.sentence_metrics.active_to_passive_ratio}

+

Go-to Words: {corePersona.linguistic_fingerprint.lexical_features.go_to_words?.join(', ') || 'N/A'}

+
+ )} + + {/* Refresh Button */} + +
+ ); +}; + +// Main test component with provider +interface PersonaTestComponentProps { + platform: PlatformType; + userId?: number; +} + +export const PersonaTestComponent: React.FC = ({ + platform, + userId = 1 +}) => { + return ( + + + + ); +}; + +export default PersonaTestComponent; diff --git a/frontend/src/components/shared/PersonaContext/PlatformPersonaProvider.tsx b/frontend/src/components/shared/PersonaContext/PlatformPersonaProvider.tsx new file mode 100644 index 00000000..a0e380f9 --- /dev/null +++ b/frontend/src/components/shared/PersonaContext/PlatformPersonaProvider.tsx @@ -0,0 +1,214 @@ +/** + * Platform Persona Provider + * React Context provider for platform-specific persona data + * Integrates with existing persona API client and injects data into CopilotKit + */ + +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { useCopilotReadable } from '@copilotkit/react-core'; +import { + WritingPersona, + PlatformAdaptation, + PlatformType, + UserPersonasResponse, + PlatformPersonaResponse +} from '../../../types/PlatformPersonaTypes'; +import { + getUserPersonas, + getPlatformPersona +} from '../../../api/persona'; + +// Context interface +interface PlatformPersonaContextType { + corePersona: WritingPersona | null; + platformPersona: PlatformAdaptation | null; + platform: PlatformType; + loading: boolean; + error: string | null; + refreshPersonas: () => Promise; +} + +// Create the context +const PlatformPersonaContext = createContext(null); + +// Provider props interface +interface PlatformPersonaProviderProps { + children: ReactNode; + platform: PlatformType; + userId?: number; // Default to 1 for now, can be enhanced with auth context later +} + +// Provider component +export const PlatformPersonaProvider: React.FC = ({ + children, + platform, + userId = 1 +}) => { + // State management + const [corePersona, setCorePersona] = useState(null); + const [platformPersona, setPlatformPersona] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch persona data function + const fetchPersonas = async () => { + try { + setLoading(true); + setError(null); + + // Fetch both core persona and platform-specific data + const [userPersonasResponse, platformPersonaResponse] = await Promise.all([ + getUserPersonas(userId), + getPlatformPersona(userId, platform) + ]); + + // Handle core persona data + if (userPersonasResponse.personas && userPersonasResponse.personas.length > 0) { + const primaryPersona = userPersonasResponse.personas[0]; + setCorePersona(primaryPersona); + + console.log('✅ Core persona loaded:', { + name: primaryPersona.persona_name, + archetype: primaryPersona.archetype, + confidence: primaryPersona.confidence_score + }); + } else { + console.warn('⚠️ No core personas found for user'); + setCorePersona(null); + } + + // Handle platform-specific persona data + if (platformPersonaResponse) { + setPlatformPersona(platformPersonaResponse); + + console.log('✅ Platform persona loaded:', { + platform: platformPersonaResponse.platform_type, + characterLimit: platformPersonaResponse.content_format_rules?.character_limit, + optimalLength: platformPersonaResponse.content_format_rules?.optimal_length + }); + } else { + console.warn(`⚠️ No platform-specific persona found for ${platform}`); + setPlatformPersona(null); + } + + } catch (error) { + console.error('❌ Error fetching personas:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch persona data'); + + // Set fallback data if available + if (corePersona) { + console.log('🔄 Using existing core persona data'); + } + } finally { + setLoading(false); + } + }; + + // Initial data fetch + useEffect(() => { + fetchPersonas(); + }, [platform, userId]); + + // Refresh function for manual updates + const refreshPersonas = async () => { + await fetchPersonas(); + }; + + // Inject core persona into CopilotKit context + useCopilotReadable({ + description: `Core writing persona: ${corePersona?.persona_name || 'Loading...'}`, + value: corePersona, + categories: ["core-persona", "writing-style", "user-preferences"], + parentId: corePersona?.id?.toString() + }); + + // Inject platform-specific persona into CopilotKit context + useCopilotReadable({ + description: `${platform} platform optimization rules and constraints`, + value: platformPersona, + categories: ["platform-persona", platform, "content-optimization"], + parentId: corePersona?.id?.toString() + }); + + // Inject combined persona context for comprehensive understanding + useCopilotReadable({ + description: `Complete ${platform} writing persona with linguistic fingerprint and platform optimization`, + value: { + core: corePersona, + platform: platformPersona, + combined: { + persona_name: corePersona?.persona_name, + archetype: corePersona?.archetype, + platform: platform, + linguistic_fingerprint: corePersona?.linguistic_fingerprint, + platform_constraints: platformPersona?.content_format_rules, + engagement_patterns: platformPersona?.engagement_patterns + } + }, + categories: ["complete-persona", platform, "writing-guidance"], + parentId: corePersona?.id?.toString() + }); + + // Loading state + if (loading) { + return ( +
+
+
+

Loading {platform} persona...

+
+
+ ); + } + + // Error state + if (error && !corePersona) { + return ( +
+
+
+ + + +
+
+

Failed to load persona data

+

{error}

+ +
+
+
+ ); + } + + // Success state - provide context + return ( + + {children} + + ); +}; + +// Custom hook to use the context +export const usePlatformPersonaContext = () => { + const context = useContext(PlatformPersonaContext); + if (!context) { + throw new Error('usePlatformPersonaContext must be used within PlatformPersonaProvider'); + } + return context; +}; + +// Export the context for direct access if needed +export { PlatformPersonaContext }; diff --git a/frontend/src/components/shared/PersonaContext/index.ts b/frontend/src/components/shared/PersonaContext/index.ts new file mode 100644 index 00000000..d1de15bf --- /dev/null +++ b/frontend/src/components/shared/PersonaContext/index.ts @@ -0,0 +1,19 @@ +/** + * Persona Context Index + * Central export point for all persona context components and hooks + */ + +export { + PlatformPersonaProvider, + PlatformPersonaContext, + usePlatformPersonaContext +} from './PlatformPersonaProvider'; + +export { PersonaTestComponent } from './PersonaTestComponent'; + +// Re-export types for convenience +export type { + PlatformType, + WritingPersona, + PlatformAdaptation +} from '../../../types/PlatformPersonaTypes'; diff --git a/frontend/src/hooks/usePlatformPersonaContext.ts b/frontend/src/hooks/usePlatformPersonaContext.ts new file mode 100644 index 00000000..de65580e --- /dev/null +++ b/frontend/src/hooks/usePlatformPersonaContext.ts @@ -0,0 +1,13 @@ +/** + * usePlatformPersonaContext Hook + * Custom hook for accessing platform-specific persona context + * Re-exports the hook from PlatformPersonaProvider for better organization + */ + +export { usePlatformPersonaContext } from '../components/shared/PersonaContext/PlatformPersonaProvider'; + +// Additional utility hooks can be added here in the future +// For example: +// - usePersonaValidation +// - usePersonaOptimization +// - usePersonaAnalytics diff --git a/frontend/src/types/PlatformPersonaTypes.ts b/frontend/src/types/PlatformPersonaTypes.ts new file mode 100644 index 00000000..e34f4a73 --- /dev/null +++ b/frontend/src/types/PlatformPersonaTypes.ts @@ -0,0 +1,310 @@ +/** + * Platform Persona Types + * TypeScript interfaces mapping to backend persona models from PR #226 + */ + +// Core Writing Persona Interface +export interface WritingPersona { + id: number; + user_id: number; + persona_name: string; + archetype: string; + core_belief: string; + brand_voice_description: string; + linguistic_fingerprint: LinguisticFingerprint; + platform_adaptations: PlatformAdaptation[]; + onboarding_session_id: number; + source_website_analysis: any; + source_research_preferences: any; + ai_analysis_version: string; + confidence_score: number; + analysis_date: string; + created_at: string; + updated_at: string; + is_active: boolean; +} + +// Linguistic Fingerprint Interface +export interface LinguisticFingerprint { + sentence_metrics: SentenceMetrics; + lexical_features: LexicalFeatures; + rhetorical_devices: RhetoricalDevices; +} + +// Sentence Metrics Interface +export interface SentenceMetrics { + average_sentence_length_words: number; + preferred_sentence_type: string; + active_to_passive_ratio: string; + sentence_complexity: string; + paragraph_structure: string; +} + +// Lexical Features Interface +export interface LexicalFeatures { + go_to_words: string[]; + go_to_phrases: string[]; + avoid_words: string[]; + contractions: string; + vocabulary_level: string; + industry_terminology: string[]; + emotional_tone_words: string[]; +} + +// Rhetorical Devices Interface +export interface RhetoricalDevices { + metaphors: string; + analogies: string; + rhetorical_questions: string; + storytelling_approach: string; + persuasion_techniques: string[]; +} + +// Platform Types +export type PlatformType = + | "twitter" + | "linkedin" + | "instagram" + | "facebook" + | "blog" + | "medium" + | "substack"; + +// Platform Adaptation Interface +export interface PlatformAdaptation { + id: number; + writing_persona_id: number; + platform_type: PlatformType; + sentence_metrics: PlatformSentenceMetrics; + lexical_features: PlatformLexicalFeatures; + rhetorical_devices: PlatformRhetoricalDevices; + tonal_range: TonalRange; + stylistic_constraints: StylisticConstraints; + content_format_rules: ContentFormatRules; + engagement_patterns: EngagementPatterns; + posting_frequency: PostingFrequency; + content_types: ContentTypes; + platform_best_practices: PlatformBestPractices; + created_at: string; + updated_at: string; +} + +// Platform-Specific Sentence Metrics +export interface PlatformSentenceMetrics { + optimal_length: string; + character_limit: number; + sentence_structure: string; + paragraph_breaks: string; + readability_score: number; +} + +// Platform-Specific Lexical Features +export interface PlatformLexicalFeatures { + hashtag_strategy: string; + platform_specific_terms: string[]; + engagement_phrases: string[]; + call_to_action_style: string; +} + +// Platform-Specific Rhetorical Devices +export interface PlatformRhetoricalDevices { + question_frequency: string; + story_elements: string; + visual_descriptions: string; + interactive_elements: string; +} + +// Tonal Range Interface +export interface TonalRange { + default_tone: string; + permissible_tones: string[]; + forbidden_tones: string[]; + emotional_range: string; + formality_level: string; +} + +// Stylistic Constraints Interface +export interface StylisticConstraints { + punctuation_preferences: string; + formatting_rules: string; + emoji_usage: string; + link_placement: string; + media_integration: string; +} + +// Content Format Rules Interface +export interface ContentFormatRules { + character_limit: number; + optimal_length: string; + word_count: string; + hashtag_limit: number; + media_requirements: string; + link_restrictions: string; +} + +// Engagement Patterns Interface +export interface EngagementPatterns { + posting_frequency: string; + best_timing: string; + interaction_style: string; + response_strategy: string; + community_approach: string; +} + +// Posting Frequency Interface +export interface PostingFrequency { + frequency: string; + optimal_days: string[]; + optimal_times: string[]; + seasonal_adjustments: string; +} + +// Content Types Interface +export interface ContentTypes { + primary_content: string[]; + secondary_content: string[]; + content_mix: string; + seasonal_content: string[]; +} + +// Platform Best Practices Interface +export interface PlatformBestPractices { + algorithm_tips: string[]; + engagement_tactics: string[]; + content_strategies: string[]; + growth_hacks: string[]; +} + +// Persona Analysis Result Interface +export interface PersonaAnalysisResult { + id: number; + writing_persona_id: number; + analysis_prompt: string; + linguistic_analysis: any; + platform_recommendations: any; + confidence_score: number; + analysis_date: string; + ai_model_version: string; +} + +// Persona Validation Result Interface +export interface PersonaValidationResult { + id: number; + writing_persona_id: number; + stylometric_accuracy: number; + consistency_score: number; + platform_compliance: number; + user_satisfaction: number; + validation_date: string; + improvement_suggestions: string[]; +} + +// API Response Interfaces +export interface PersonaGenerationResponse { + success: boolean; + persona_id?: number; + message: string; + confidence_score?: number; + data_sufficiency?: number; + platforms_generated?: string[]; +} + +export interface PersonaReadinessResponse { + ready: boolean; + message: string; + missing_steps: string[]; + data_sufficiency: number; + recommendations?: string[]; +} + +export interface PersonaPreviewResponse { + preview: { + identity: { + persona_name: string; + archetype: string; + core_belief: string; + brand_voice_description: string; + }; + linguistic_fingerprint: any; + tonal_range: any; + sample_platform: { + platform: string; + adaptation: any; + }; + }; + confidence_score: number; + data_sufficiency: number; +} + +// Platform Information Interface +export interface PlatformInfo { + id: string; + name: string; + description: string; + character_limit?: number; + optimal_length?: string; + word_count?: string; + seo_optimized?: boolean; + storytelling_focus?: boolean; + subscription_focus?: boolean; +} + +// Supported Platforms Response Interface +export interface SupportedPlatformsResponse { + platforms: PlatformInfo[]; +} + +// User Personas Response Interface +export interface UserPersonasResponse { + personas: WritingPersona[]; + total_count: number; + active_count: number; +} + +// Platform Persona Response Interface +export interface PlatformPersonaResponse { + platform_type: PlatformType; + sentence_metrics: PlatformSentenceMetrics; + lexical_features: PlatformLexicalFeatures; + content_format_rules: ContentFormatRules; + engagement_patterns: EngagementPatterns; + platform_best_practices: PlatformBestPractices; +} + +// Content Generation Request Interface +export interface ContentGenerationRequest { + platform: PlatformType; + topic: string; + content_type: string; + additional_context?: string; +} + +// Content Generation Response Interface +export interface ContentGenerationResponse { + success: boolean; + content: string; + metadata: { + character_count: number; + word_count: number; + persona_compliance_score: number; + platform_optimization_score: number; + generated_at: string; + }; + suggestions?: string[]; +} + +// Export Persona Request Interface +export interface ExportPersonaRequest { + platform: PlatformType; + format: "prompt" | "json" | "markdown"; + include_metadata?: boolean; +} + +// Export Persona Response Interface +export interface ExportPersonaResponse { + success: boolean; + content: string; + format: string; + export_date: string; + version: string; +}