wiki-project / src /components /ContentTransformer.tsx
Nagi15's picture
Add codebase
fcb5a67
import React, { useState } from 'react';
import { Wand2, FileText, List, BookOpen, MessageSquare, Loader2, Settings, Zap } from 'lucide-react';
import { AIEnhancedWikimedia } from '../utils/ai-enhanced-wikimedia';
import { AIProviderManager } from '../utils/ai-providers';
const ContentTransformer: React.FC = () => {
const [inputUrl, setInputUrl] = useState('');
const [transformationType, setTransformationType] = useState<'summary' | 'quiz' | 'outline' | 'flashcards'>('summary');
const [loading, setLoading] = useState(false);
const [transformedContent, setTransformedContent] = useState<any>(null);
const [selectedAnswers, setSelectedAnswers] = useState<Record<number, number>>({});
const [useAI, setUseAI] = useState(true);
const [aiStatus, setAiStatus] = useState<'checking' | 'available' | 'unavailable'>('checking');
React.useEffect(() => {
checkAIAvailability();
}, []);
const checkAIAvailability = async () => {
try {
const config = AIProviderManager.getConfig();
const isAvailable = await AIProviderManager.testConnection(config.selectedProvider);
setAiStatus(isAvailable ? 'available' : 'unavailable');
} catch (error) {
setAiStatus('unavailable');
}
};
const transformationTypes = [
{
id: 'summary' as const,
name: 'Summary',
description: 'Create concise summaries',
icon: FileText,
color: 'bg-primary-500'
},
{
id: 'quiz' as const,
name: 'Quiz',
description: 'Generate practice questions',
icon: MessageSquare,
color: 'bg-secondary-500'
},
{
id: 'outline' as const,
name: 'Study Outline',
description: 'Structured learning outline',
icon: List,
color: 'bg-accent-500'
},
{
id: 'flashcards' as const,
name: 'Flashcards',
description: 'Key concepts for memorization',
icon: BookOpen,
color: 'bg-success-500'
}
];
const extractTitleFromUrl = (url: string): string => {
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/');
const title = pathParts[pathParts.length - 1];
return decodeURIComponent(title.replace(/_/g, ' '));
} catch {
return '';
}
};
const detectProject = (url: string): string => {
if (url.includes('wikipedia.org')) return 'wikipedia';
if (url.includes('wikibooks.org')) return 'wikibooks';
if (url.includes('wikiversity.org')) return 'wikiversity';
if (url.includes('wikiquote.org')) return 'wikiquote';
if (url.includes('wiktionary.org')) return 'wiktionary';
if (url.includes('wikisource.org')) return 'wikisource';
return 'wikipedia';
};
const handleTransform = async () => {
if (!inputUrl.trim()) return;
setLoading(true);
setSelectedAnswers({});
try {
const title = extractTitleFromUrl(inputUrl);
const project = detectProject(inputUrl);
const content = await AIEnhancedWikimedia.getPageContent(title, project);
let transformed;
if (useAI && aiStatus === 'available') {
// Use AI-enhanced transformation
switch (transformationType) {
case 'summary':
transformed = await AIEnhancedWikimedia.generateSummaryFromContent(content, title);
break;
case 'quiz':
transformed = await AIEnhancedWikimedia.generateQuizFromContent(content, title);
break;
case 'outline':
transformed = await AIEnhancedWikimedia.generateStudyOutline(content, title);
break;
case 'flashcards':
transformed = await AIEnhancedWikimedia.generateFlashcards(content, title);
break;
}
} else {
// Use fallback rule-based transformation
switch (transformationType) {
case 'summary':
transformed = generateSummary(content, title);
break;
case 'quiz':
transformed = generateQuiz(content, title);
break;
case 'outline':
transformed = generateOutline(content, title);
break;
case 'flashcards':
transformed = generateFlashcards(content, title);
break;
}
}
setTransformedContent(transformed);
} catch (error) {
console.error('Content transformation failed:', error);
} finally {
setLoading(false);
}
};
// Fallback generation methods (existing code)
const generateSummary = (content: string, title: string) => {
const sentences = content.split('.').filter(s => s.trim().length > 20);
const keySentences = sentences.slice(0, 5);
return {
type: 'summary',
title: `Summary: ${title}`,
content: keySentences.join('. ') + '.',
keyPoints: sentences.slice(5, 10).map(s => s.trim()).filter(s => s.length > 0)
};
};
const generateQuiz = (content: string, title: string) => {
const questions = [
{
question: `What is the main topic discussed in the article about ${title}?`,
options: [
`The fundamental concepts and principles of ${title}`,
`The historical development of ${title}`,
`The practical applications of ${title}`,
`The criticism and controversies surrounding ${title}`
],
correct: 0
}
];
return {
type: 'quiz',
title: `Quiz: ${title}`,
questions
};
};
const generateOutline = (content: string, title: string) => {
return {
type: 'outline',
title: `Study Outline: ${title}`,
sections: [
{
title: 'Introduction',
points: [
`Overview and definition of ${title}`,
'Historical context and background',
'Key terminology and concepts'
]
},
{
title: 'Main Concepts',
points: [
'Core principles and theories',
'Important characteristics and features',
'Fundamental mechanisms and processes'
]
}
]
};
};
const generateFlashcards = (content: string, title: string) => {
const cards = [
{
front: `What is ${title}?`,
back: `${title} is a comprehensive topic that encompasses various concepts, principles, and applications within its field of study.`
},
{
front: 'Key characteristics',
back: `The main features include fundamental principles, practical applications, and significant impact on related areas.`
}
];
return {
type: 'flashcards',
title: `Flashcards: ${title}`,
cards
};
};
const handleAnswerSelect = (questionIndex: number, answerIndex: number) => {
setSelectedAnswers(prev => ({
...prev,
[questionIndex]: answerIndex
}));
};
const renderTransformedContent = () => {
if (!transformedContent) return null;
switch (transformedContent.type) {
case 'summary':
return (
<div className="space-y-4">
<div className="prose max-w-none">
<p className="text-gray-700 leading-relaxed">{transformedContent.content}</p>
</div>
{transformedContent.keyPoints && (
<div>
<h4 className="font-medium text-gray-900 mb-3">Key Points:</h4>
<ul className="space-y-2">
{transformedContent.keyPoints.map((point: string, index: number) => (
<li key={index} className="flex items-start space-x-2">
<div className="w-2 h-2 bg-primary-500 rounded-full mt-2 flex-shrink-0" />
<span className="text-gray-700">{point}</span>
</li>
))}
</ul>
</div>
)}
</div>
);
case 'quiz':
return (
<div className="space-y-6">
{transformedContent.questions.map((q: any, index: number) => (
<div key={index} className="p-6 bg-gray-50 rounded-xl">
<h4 className="font-medium text-gray-900 mb-4 text-lg">
{index + 1}. {q.question}
</h4>
<div className="space-y-3">
{q.options.map((option: string, optIndex: number) => (
<label
key={optIndex}
className={`flex items-start space-x-3 cursor-pointer p-3 rounded-lg border-2 transition-all ${
selectedAnswers[index] === optIndex
? selectedAnswers[index] === q.correct
? 'border-success-500 bg-success-50'
: 'border-error-500 bg-error-50'
: 'border-gray-200 hover:border-gray-300 bg-white'
}`}
>
<input
type="radio"
name={`q${index}`}
value={optIndex}
checked={selectedAnswers[index] === optIndex}
onChange={() => handleAnswerSelect(index, optIndex)}
className="mt-1 text-primary-600 focus:ring-primary-500"
/>
<span className="text-gray-700 flex-1">{option}</span>
{selectedAnswers[index] !== undefined && optIndex === q.correct && (
<span className="text-success-600 font-medium text-sm">✓ Correct</span>
)}
</label>
))}
</div>
{selectedAnswers[index] !== undefined && q.explanation && (
<div className="mt-4 p-3 bg-blue-50 rounded-lg">
<p className="text-sm text-blue-800">
<strong>Explanation:</strong> {q.explanation}
</p>
</div>
)}
</div>
))}
</div>
);
case 'outline':
return (
<div className="space-y-6">
{transformedContent.sections.map((section: any, index: number) => (
<div key={index} className="border-l-4 border-primary-500 pl-6">
<h4 className="font-semibold text-gray-900 mb-3 text-lg">{section.title}</h4>
<ul className="space-y-2">
{section.points.map((point: string, pointIndex: number) => (
<li key={pointIndex} className="flex items-start space-x-2">
<div className="w-1.5 h-1.5 bg-primary-500 rounded-full mt-2 flex-shrink-0" />
<span className="text-gray-700">{point}</span>
</li>
))}
</ul>
</div>
))}
</div>
);
case 'flashcards':
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{transformedContent.cards.map((card: any, index: number) => (
<div key={index} className="group perspective-1000">
<div className="relative w-full h-48 transition-transform duration-500 transform-style-preserve-3d group-hover:rotate-y-180">
<div className="absolute inset-0 w-full h-full backface-hidden bg-gradient-to-br from-primary-500 to-primary-600 rounded-xl p-6 flex items-center justify-center text-white">
<div className="text-center">
<h4 className="font-semibold text-lg mb-2">Question {index + 1}</h4>
<p className="text-primary-100">{card.front}</p>
</div>
</div>
<div className="absolute inset-0 w-full h-full backface-hidden rotate-y-180 bg-white border-2 border-primary-200 rounded-xl p-6 flex items-center justify-center">
<div className="text-center">
<p className="text-gray-700 leading-relaxed">{card.back}</p>
</div>
</div>
</div>
</div>
))}
</div>
);
default:
return null;
}
};
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-8">
<div className="flex items-center space-x-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-accent-500 to-warning-500 rounded-xl flex items-center justify-center">
<Wand2 className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">Content Transformer</h1>
<p className="text-gray-600">Transform Wikimedia content into interactive learning materials</p>
</div>
</div>
</div>
<div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm mb-8">
{/* AI Status Banner */}
<div className={`mb-6 p-4 rounded-xl border ${
aiStatus === 'available'
? 'bg-green-50 border-green-200'
: aiStatus === 'unavailable'
? 'bg-yellow-50 border-yellow-200'
: 'bg-gray-50 border-gray-200'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
{aiStatus === 'checking' && <Loader2 className="w-5 h-5 animate-spin text-gray-600" />}
{aiStatus === 'available' && <Zap className="w-5 h-5 text-green-600" />}
{aiStatus === 'unavailable' && <Settings className="w-5 h-5 text-yellow-600" />}
<div>
<div className="font-medium text-gray-900">
{aiStatus === 'available' && 'AI Enhancement Available'}
{aiStatus === 'unavailable' && 'AI Enhancement Unavailable'}
{aiStatus === 'checking' && 'Checking AI availability...'}
</div>
<div className="text-sm text-gray-600">
{aiStatus === 'available' && 'Using open-source AI for intelligent content transformation'}
{aiStatus === 'unavailable' && 'Using rule-based transformation (still creates useful content!)'}
</div>
</div>
</div>
{aiStatus === 'available' && (
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={useAI}
onChange={(e) => setUseAI(e.target.checked)}
className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm font-medium text-gray-700">Use AI Enhancement</span>
</label>
)}
</div>
</div>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Wikimedia Article URL
</label>
<input
type="url"
value={inputUrl}
onChange={(e) => setInputUrl(e.target.value)}
placeholder="https://en.wikipedia.org/wiki/Machine_Learning"
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Transformation Type
</label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{transformationTypes.map((type) => {
const Icon = type.icon;
return (
<button
key={type.id}
onClick={() => setTransformationType(type.id)}
className={`p-4 rounded-xl border-2 transition-all ${
transformationType === type.id
? 'border-primary-500 bg-primary-50'
: 'border-gray-200 bg-white hover:border-gray-300'
}`}
>
<div className="text-center">
<div className={`w-10 h-10 ${type.color} rounded-lg flex items-center justify-center mx-auto mb-2`}>
<Icon className="w-5 h-5 text-white" />
</div>
<div className="font-medium text-gray-900">{type.name}</div>
<div className="text-xs text-gray-500 mt-1">{type.description}</div>
</div>
</button>
);
})}
</div>
</div>
<button
onClick={handleTransform}
disabled={loading || !inputUrl.trim()}
className="w-full flex items-center justify-center space-x-2 px-6 py-4 bg-gradient-to-r from-accent-600 to-warning-600 text-white rounded-xl hover:from-accent-700 hover:to-warning-700 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
<span>
{useAI && aiStatus === 'available'
? 'AI is transforming content...'
: 'Transforming Content...'
}
</span>
</>
) : (
<>
<Wand2 className="w-5 h-5" />
<span>
{useAI && aiStatus === 'available'
? 'AI Transform Content'
: 'Transform Content'
}
</span>
</>
)}
</button>
</div>
</div>
{transformedContent && (
<div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-900">{transformedContent.title}</h2>
{useAI && aiStatus === 'available' && (
<span className="px-3 py-1 bg-purple-100 text-purple-700 text-sm rounded-full font-medium">
AI Enhanced
</span>
)}
</div>
{renderTransformedContent()}
</div>
)}
</div>
);
};
export default ContentTransformer;