Phase 1 Complete: React Context Layer - TypeScript interfaces and PlatformPersonaProvider implemented
This commit is contained in:
@@ -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;
|
||||
@@ -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 };
|
||||
19
frontend/src/components/shared/PersonaContext/index.ts
Normal file
19
frontend/src/components/shared/PersonaContext/index.ts
Normal 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';
|
||||
Reference in New Issue
Block a user