diff --git a/nepa-frontend/src/components/ClassificationResult.tsx b/nepa-frontend/src/components/ClassificationResult.tsx new file mode 100644 index 0000000..1033668 --- /dev/null +++ b/nepa-frontend/src/components/ClassificationResult.tsx @@ -0,0 +1,133 @@ +import React, { useState, useEffect } from 'react'; +import { announceToScreenReader } from '../utils/accessibility'; + +interface ClassificationResult { + id: string; + category: string; + confidence: number; + description: string; + timestamp: Date; +} + +interface ClassificationResultProps { + results: ClassificationResult[]; + loading: boolean; + error: string | null; +} + +const ClassificationResult: React.FC = ({ + results, + loading, + error +}) => { + const [previousResultsCount, setPreviousResultsCount] = useState(0); + + useEffect(() => { + if (results.length !== previousResultsCount) { + const newResultsCount = results.length - previousResultsCount; + if (newResultsCount > 0) { + announceToScreenReader( + `New classification results loaded. ${newResultsCount} new result${newResultsCount > 1 ? 's' : ''} available.`, + 'polite' + ); + } + setPreviousResultsCount(results.length); + } + }, [results.length, previousResultsCount]); + + useEffect(() => { + if (error) { + announceToScreenReader(`Error loading classification results: ${error}`, 'assertive'); + } + }, [error]); + + useEffect(() => { + if (loading) { + announceToScreenReader('Loading classification results...', 'polite'); + } else { + announceToScreenReader('Classification results loaded.', 'polite'); + } + }, [loading]); + + if (loading) { + return ( +
+
+
+

Loading classification results...

+
+
+ ); + } + + if (error) { + return ( +
+
+

Error: {error}

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

Classification Results

+ + {/* Live region for dynamic updates */} +
+ {results.length > 0 + ? `${results.length} classification result${results.length > 1 ? 's' : ''} displayed.` + : 'No classification results available.' + } +
+ + {results.length === 0 ? ( +

No classification results available.

+ ) : ( +
+ {results.map((result) => ( +
+
+

+ {result.category} +

+ + {Math.round(result.confidence * 100)}% confidence + +
+ +

+ {result.description} +

+ + +
+ ))} +
+ )} +
+ ); +}; + +export default ClassificationResult; \ No newline at end of file diff --git a/nepa-frontend/src/pages/index.tsx b/nepa-frontend/src/pages/index.tsx new file mode 100644 index 0000000..b6c1d27 --- /dev/null +++ b/nepa-frontend/src/pages/index.tsx @@ -0,0 +1,156 @@ +import React, { useState, useEffect } from 'react'; +import ClassificationResult from '../components/ClassificationResult'; +import { announceToScreenReader } from '../utils/accessibility'; + +interface ClassificationResultData { + id: string; + category: string; + confidence: number; + description: string; + timestamp: Date; +} + +const IndexPage: React.FC = () => { + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [inputText, setInputText] = useState(''); + + const handleClassify = async () => { + if (!inputText.trim()) { + setError('Please enter text to classify'); + announceToScreenReader('Please enter text to classify', 'assertive'); + return; + } + + setLoading(true); + setError(null); + + try { + // Simulate API call to classification service + const response = await fetch('/api/classify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ text: inputText }), + }); + + if (!response.ok) { + throw new Error('Failed to classify text'); + } + + const data = await response.json(); + + const newResult: ClassificationResultData = { + id: Date.now().toString(), + category: data.category || 'Unknown', + confidence: data.confidence || 0, + description: data.description || 'Classification completed', + timestamp: new Date(), + }; + + setResults(prev => [newResult, ...prev]); + setInputText(''); + + // Announce the new result + announceToScreenReader( + `New classification result: ${newResult.category} with ${Math.round(newResult.confidence * 100)}% confidence.`, + 'polite' + ); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'An error occurred'; + setError(errorMessage); + announceToScreenReader(`Classification failed: ${errorMessage}`, 'assertive'); + } finally { + setLoading(false); + } + }; + + const handleClearResults = () => { + setResults([]); + announceToScreenReader('All classification results cleared', 'polite'); + }; + + return ( +
+
+

NEPA Classification Tool

+

+ Enter text to get AI-powered classification results +

+
+ +
+
+

Text Input Section

+ +
+ + +