File size: 4,339 Bytes
c3bf538 8427d6e c3bf538 8427d6e c3bf538 683938d c3bf538 683938d c3bf538 683938d c3bf538 8427d6e 683938d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
import React, { useState, useEffect } from 'react';
import Header from './components/Header';
import JobForm from './components/JobForm';
import JobStatusCard from './components/JobStatusCard';
import ResultsDisplay from './components/ResultsDisplay';
import LoadingSkeleton from './components/LoadingSkeleton';
import HistoryPanel from './components/HistoryPanel';
import { createJob, getJob } from './services/api';
import { XCircle } from 'lucide-react';
function App() {
const [job, setJob] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isPolling, setIsPolling] = useState(false);
const [error, setError] = useState(null);
const handleAnalysisRequest = async (ticker) => {
setIsLoading(true);
setIsPolling(true);
setError(null);
setJob(null);
try {
const response = await createJob(ticker);
setJob(response.data);
} catch (err) {
setError('Failed to create job. Please check the API server and try again.');
setIsLoading(false);
setIsPolling(false);
}
};
const handleSelectHistoryJob = (historyJob) => {
setIsLoading(false);
setIsPolling(false);
setError(null);
setJob(historyJob);
}
useEffect(() => {
if (!job?.id || !isPolling) return;
if (job.status !== 'PENDING') {
setIsLoading(false);
}
const intervalId = setInterval(async () => {
try {
const response = await getJob(job.id);
const updatedJob = response.data;
setJob(updatedJob);
if (updatedJob.status === 'SUCCESS' || updatedJob.status === 'FAILED') {
clearInterval(intervalId);
setIsPolling(false);
}
} catch (err) {
setError('Failed to poll job status.');
clearInterval(intervalId);
setIsPolling(false);
}
}, 3000);
return () => clearInterval(intervalId);
}, [job, isPolling]);
return (
<div className="min-h-screen bg-gray-900 text-white font-sans">
<Header />
<HistoryPanel onSelectJob={handleSelectHistoryJob} />
<main className="container mx-auto p-4 md:p-8">
<div className="max-w-4xl mx-auto">
<p className="text-lg text-gray-400 mb-4 text-center">
Enter an Indian stock ticker to receive a comprehensive, AI-powered analysis.
</p>
{/* --- NEW, SIMPLE TEXT EXAMPLE LINE --- */}
<p className="text-sm text-gray-500 text-center mb-8">
e.g., RELIANCE.NS, TCS.NS, INFY.NS, HDFCBANK.NS
</p>
{/* --- END NEW LINE --- */}
<JobForm onAnalyze={handleAnalysisRequest} isLoading={isLoading || isPolling} />
{error && <div className="my-6 p-4 bg-red-900/50 rounded-lg text-red-300 text-center">{error}</div>}
{isLoading && !job && <LoadingSkeleton />}
{job && !isLoading && <JobStatusCard job={job} />}
{job?.status === 'SUCCESS' && job.result && (
<ResultsDisplay result={job.result} />
)}
{job?.status === 'FAILED' && job.result?.error && (
<div className="mt-8 p-6 bg-gray-800/30 border border-red-500/30 rounded-lg text-center animate-fade-in">
<XCircle className="w-16 h-16 text-red-400 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-red-300 mb-2">Analysis Failed</h2>
<p className="text-gray-400 max-w-lg mx-auto">
We couldn't complete the analysis for <strong className="text-white">{job.ticker}</strong>.
This usually means the stock symbol is incorrect or not listed.
</p>
<p className="text-xs text-gray-500 mt-4">Please double-check the ticker and try again.</p>
<details className="mt-6 text-left w-full max-w-lg mx-auto">
<summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-400 focus:outline-none">Show technical details</summary>
<pre className="mt-2 bg-gray-900 p-4 rounded-md text-gray-400 text-xs whitespace-pre-wrap font-mono">
{job.result.error}
</pre>
</details>
</div>
)}
</div>
</main>
</div>
);
}
export default App; |