Phase 1 Complete: React Context Layer - TypeScript interfaces and PlatformPersonaProvider implemented

This commit is contained in:
ajaysi
2025-09-04 14:27:36 +05:30
parent 4c2e1daef9
commit bf41db00e5
5 changed files with 655 additions and 0 deletions

View File

@@ -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 <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

@@ -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<void>;
}
// Create the context
const PlatformPersonaContext = createContext<PlatformPersonaContextType | null>(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<PlatformPersonaProviderProps> = ({
children,
platform,
userId = 1
}) => {
// State management
const [corePersona, setCorePersona] = useState<WritingPersona | null>(null);
const [platformPersona, setPlatformPersona] = useState<PlatformAdaptation | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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 (
<div className="flex items-center justify-center p-4">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
<p className="text-sm text-gray-600">Loading {platform} persona...</p>
</div>
</div>
);
}
// Error state
if (error && !corePersona) {
return (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<div className="flex items-center">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">Failed to load persona data</h3>
<p className="text-sm text-red-700 mt-1">{error}</p>
<button
onClick={refreshPersonas}
className="mt-2 text-sm text-red-600 hover:text-red-500 underline"
>
Try again
</button>
</div>
</div>
</div>
);
}
// Success state - provide context
return (
<PlatformPersonaContext.Provider value={{
corePersona,
platformPersona,
platform,
loading,
error,
refreshPersonas
}}>
{children}
</PlatformPersonaContext.Provider>
);
};
// 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 };

View File

@@ -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';

View File

@@ -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

View File

@@ -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;
}