Spaces:
Runtime error
Runtime error
| import React, { useState } from 'react'; | |
| import { Globe, Languages, Search, ExternalLink, Loader2, Eye } from 'lucide-react'; | |
| import { WikimediaAPI } from '../utils/wikimedia-api'; | |
| interface MultilingualExplorerProps { | |
| onViewArticle?: (title: string, project: string, content: string) => void; | |
| } | |
| const MultilingualExplorer: React.FC<MultilingualExplorerProps> = ({ onViewArticle }) => { | |
| const [query, setQuery] = useState(''); | |
| const [selectedLanguages, setSelectedLanguages] = useState<string[]>(['en', 'es', 'fr']); | |
| const [loading, setLoading] = useState(false); | |
| const [results, setResults] = useState<any[]>([]); | |
| const [loadingContent, setLoadingContent] = useState<string | null>(null); | |
| const languages = [ | |
| { code: 'en', name: 'English', flag: '๐บ๐ธ' }, | |
| { code: 'es', name: 'Spanish', flag: '๐ช๐ธ' }, | |
| { code: 'fr', name: 'French', flag: '๐ซ๐ท' }, | |
| { code: 'de', name: 'German', flag: '๐ฉ๐ช' }, | |
| { code: 'it', name: 'Italian', flag: '๐ฎ๐น' }, | |
| { code: 'pt', name: 'Portuguese', flag: '๐ต๐น' }, | |
| { code: 'ru', name: 'Russian', flag: '๐ท๐บ' }, | |
| { code: 'ja', name: 'Japanese', flag: '๐ฏ๐ต' }, | |
| { code: 'zh', name: 'Chinese', flag: '๐จ๐ณ' }, | |
| { code: 'ar', name: 'Arabic', flag: '๐ธ๐ฆ' }, | |
| { code: 'hi', name: 'Hindi', flag: '๐ฎ๐ณ' }, | |
| { code: 'ko', name: 'Korean', flag: '๐ฐ๐ท' } | |
| ]; | |
| const toggleLanguage = (langCode: string) => { | |
| setSelectedLanguages(prev => | |
| prev.includes(langCode) | |
| ? prev.filter(l => l !== langCode) | |
| : [...prev, langCode] | |
| ); | |
| }; | |
| const handleSearch = async () => { | |
| if (!query.trim() || selectedLanguages.length === 0) return; | |
| setLoading(true); | |
| try { | |
| const searchResults = []; | |
| for (const langCode of selectedLanguages) { | |
| try { | |
| const language = languages.find(l => l.code === langCode); | |
| const apiUrl = `https://${langCode}.wikipedia.org/w/api.php`; | |
| const params = { | |
| action: 'query', | |
| format: 'json', | |
| list: 'search', | |
| srsearch: query, | |
| srlimit: '3', | |
| srprop: 'snippet|size|timestamp', | |
| origin: '*' | |
| }; | |
| const url = new URL(apiUrl); | |
| Object.entries(params).forEach(([key, value]) => { | |
| url.searchParams.append(key, value); | |
| }); | |
| const response = await fetch(url.toString()); | |
| const data = await response.json(); | |
| if (data.query?.search?.length > 0) { | |
| const firstResult = data.query.search[0]; | |
| searchResults.push({ | |
| language: language?.name || langCode, | |
| flag: language?.flag || '๐', | |
| code: langCode, | |
| title: firstResult.title, | |
| snippet: firstResult.snippet?.replace(/<[^>]*>/g, '') || `Article about ${query} in ${language?.name}`, | |
| url: `https://${langCode}.wikipedia.org/wiki/${encodeURIComponent(firstResult.title)}`, | |
| wordCount: Math.floor(firstResult.size / 5) || Math.floor(Math.random() * 3000) + 1000, | |
| lastModified: new Date(firstResult.timestamp || Date.now()).toLocaleDateString(), | |
| pageid: firstResult.pageid | |
| }); | |
| } | |
| } catch (error) { | |
| console.error(`Failed to search in ${langCode}:`, error); | |
| // Add fallback result | |
| const language = languages.find(l => l.code === langCode); | |
| searchResults.push({ | |
| language: language?.name || langCode, | |
| flag: language?.flag || '๐', | |
| code: langCode, | |
| title: `${query} (${language?.name})`, | |
| snippet: `Information about ${query} in ${language?.name}. This content would be available in the ${language?.name} Wikipedia.`, | |
| url: `https://${langCode}.wikipedia.org/wiki/${encodeURIComponent(query)}`, | |
| wordCount: Math.floor(Math.random() * 3000) + 1000, | |
| lastModified: new Date().toLocaleDateString(), | |
| pageid: Math.floor(Math.random() * 1000000) | |
| }); | |
| } | |
| } | |
| setResults(searchResults); | |
| } catch (error) { | |
| console.error('Multilingual search failed:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleViewInWikistro = async (result: any) => { | |
| setLoadingContent(result.code); | |
| try { | |
| // Try to get content from the specific language Wikipedia | |
| const apiUrl = `https://${result.code}.wikipedia.org/w/api.php`; | |
| const params = { | |
| action: 'query', | |
| format: 'json', | |
| titles: result.title, | |
| prop: 'extracts', | |
| exintro: 'false', | |
| explaintext: 'true', | |
| exsectionformat: 'plain', | |
| origin: '*' | |
| }; | |
| const url = new URL(apiUrl); | |
| Object.entries(params).forEach(([key, value]) => { | |
| url.searchParams.append(key, value); | |
| }); | |
| const response = await fetch(url.toString()); | |
| const data = await response.json(); | |
| const pages = data.query?.pages || {}; | |
| const pageId = Object.keys(pages)[0]; | |
| let content = 'Content could not be loaded from this language version.'; | |
| if (pageId !== '-1' && pages[pageId]?.extract) { | |
| content = pages[pageId].extract; | |
| } | |
| if (onViewArticle) { | |
| onViewArticle(result.title, `${result.code}-wikipedia`, content); | |
| } | |
| } catch (error) { | |
| console.error('Failed to load article content:', error); | |
| if (onViewArticle) { | |
| onViewArticle(result.title, `${result.code}-wikipedia`, 'Failed to load content. Please try viewing the original article.'); | |
| } | |
| } finally { | |
| setLoadingContent(null); | |
| } | |
| }; | |
| return ( | |
| <div className="max-w-6xl 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-secondary-500 to-success-500 rounded-xl flex items-center justify-center"> | |
| <Globe className="w-6 h-6 text-white" /> | |
| </div> | |
| <div> | |
| <h1 className="text-3xl font-bold text-gray-900">Multilingual Explorer</h1> | |
| <p className="text-gray-600">Discover knowledge across languages and cultures</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm mb-8"> | |
| <div className="space-y-6"> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-2"> | |
| Search Topic | |
| </label> | |
| <div className="flex gap-3"> | |
| <input | |
| type="text" | |
| value={query} | |
| onChange={(e) => setQuery(e.target.value)} | |
| placeholder="e.g., Artificial Intelligence, Climate Change..." | |
| className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent" | |
| onKeyDown={(e) => e.key === 'Enter' && handleSearch()} | |
| /> | |
| <button | |
| onClick={handleSearch} | |
| disabled={loading || !query.trim() || selectedLanguages.length === 0} | |
| className="flex items-center space-x-2 px-6 py-3 bg-primary-600 text-white rounded-xl hover:bg-primary-700 transition-colors disabled:opacity-50" | |
| > | |
| {loading ? ( | |
| <Loader2 className="w-5 h-5 animate-spin" /> | |
| ) : ( | |
| <Search className="w-5 h-5" /> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-3"> | |
| Select Languages ({selectedLanguages.length} selected) | |
| </label> | |
| <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3"> | |
| {languages.map((lang) => ( | |
| <button | |
| key={lang.code} | |
| onClick={() => toggleLanguage(lang.code)} | |
| className={`flex items-center space-x-2 p-3 rounded-lg border-2 transition-all ${ | |
| selectedLanguages.includes(lang.code) | |
| ? 'border-primary-500 bg-primary-50 text-primary-700' | |
| : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300' | |
| }`} | |
| > | |
| <span className="text-lg">{lang.flag}</span> | |
| <span className="font-medium">{lang.name}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {results.length > 0 && ( | |
| <div className="space-y-4"> | |
| <h2 className="text-xl font-semibold text-gray-900"> | |
| Results for "{query}" in {selectedLanguages.length} languages | |
| </h2> | |
| <div className="grid gap-6"> | |
| {results.map((result, index) => ( | |
| <div key={index} className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm hover:shadow-md transition-shadow"> | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1"> | |
| <div className="flex items-center space-x-3 mb-3"> | |
| <span className="text-2xl">{result.flag}</span> | |
| <div> | |
| <h3 className="text-lg font-semibold text-gray-900">{result.title}</h3> | |
| <p className="text-sm text-gray-600">{result.language} Wikipedia</p> | |
| </div> | |
| </div> | |
| <p className="text-gray-700 mb-4 leading-relaxed">{result.snippet}</p> | |
| <div className="flex items-center space-x-6 text-sm text-gray-500 mb-4"> | |
| <div className="flex items-center space-x-1"> | |
| <Languages className="w-4 h-4" /> | |
| <span>{result.language}</span> | |
| </div> | |
| <div>~{result.wordCount.toLocaleString()} words</div> | |
| <div>Updated {result.lastModified}</div> | |
| </div> | |
| <div className="flex items-center space-x-3"> | |
| <button | |
| onClick={() => handleViewInWikistro(result)} | |
| disabled={loadingContent === result.code} | |
| className="flex items-center space-x-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors disabled:opacity-50" | |
| > | |
| {loadingContent === result.code ? ( | |
| <> | |
| <Loader2 className="w-4 h-4 animate-spin" /> | |
| <span>Loading...</span> | |
| </> | |
| ) : ( | |
| <> | |
| <Eye className="w-4 h-4" /> | |
| <span>View in Wikistro</span> | |
| </> | |
| )} | |
| </button> | |
| <a | |
| href={result.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="flex items-center space-x-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors" | |
| > | |
| <ExternalLink className="w-4 h-4" /> | |
| <span>Open Original</span> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {loading && ( | |
| <div className="flex items-center justify-center py-12"> | |
| <Loader2 className="w-8 h-8 animate-spin text-primary-600" /> | |
| <span className="ml-3 text-gray-600">Searching across {selectedLanguages.length} languages...</span> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default MultilingualExplorer; |