129 lines
4.1 KiB
JavaScript
129 lines
4.1 KiB
JavaScript
// web/frontend/src/pages/Analysis/Analysis.js
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Card, Divider, Spin, Alert, Typography, Row, Col } from 'antd';
|
|
import styled from 'styled-components';
|
|
import api from '../../services/api';
|
|
import { useWebSocket } from '../../contexts/WebSocketContext';
|
|
import AnalysisForm from './components/AnalysisForm';
|
|
import AnalysisDisplay from './components/AnalysisDisplay';
|
|
|
|
const { Title, Paragraph } = Typography;
|
|
|
|
const AnalysisContainer = styled.div`
|
|
max-width: 100%;
|
|
margin: 0 auto;
|
|
padding: ${props => props.theme.spacing.lg};
|
|
`;
|
|
|
|
const CustomPageHeader = styled(Card)`
|
|
border: none;
|
|
background-color: transparent;
|
|
.ant-card-body {
|
|
padding: 0;
|
|
}
|
|
`;
|
|
|
|
const Analysis = () => {
|
|
const [currentSessionId, setCurrentSessionId] = useState(null);
|
|
const [analysisStatus, setAnalysisStatus] = useState('idle'); // idle, running, completed, failed
|
|
const [error, setError] = useState(null);
|
|
const [finalReport, setFinalReport] = useState(null);
|
|
|
|
const { subscribeToAnalysis, analysisProgress, messages, clearMessages, clearAnalysisProgress } = useWebSocket();
|
|
|
|
// WebSocket 메시지로부터 상태 업데이트
|
|
useEffect(() => {
|
|
if (currentSessionId && analysisProgress[currentSessionId]) {
|
|
const progress = analysisProgress[currentSessionId];
|
|
setAnalysisStatus(progress.status);
|
|
if (progress.status === 'completed') {
|
|
setFinalReport(progress.result);
|
|
} else if (progress.status === 'failed') {
|
|
setError(progress.error || '분석 중 알 수 없는 오류가 발생했습니다.');
|
|
}
|
|
}
|
|
}, [analysisProgress, currentSessionId]);
|
|
|
|
// 분석 시작 핸들러
|
|
const handleStartAnalysis = async (values) => {
|
|
setAnalysisStatus('starting');
|
|
setError(null);
|
|
setFinalReport(null);
|
|
clearMessages();
|
|
if(currentSessionId) clearAnalysisProgress(currentSessionId);
|
|
|
|
try {
|
|
const response = await api.post('/api/trading/start/', values);
|
|
const { session_id } = response.data;
|
|
|
|
setCurrentSessionId(session_id);
|
|
subscribeToAnalysis(session_id);
|
|
setAnalysisStatus('running');
|
|
|
|
} catch (err) {
|
|
const errorMessage = err.response?.data?.error || '분석 시작에 실패했습니다.';
|
|
setError(errorMessage);
|
|
setAnalysisStatus('failed');
|
|
}
|
|
};
|
|
|
|
// 새 분석 시작 핸들러 (Display 컴포넌트에서 호출)
|
|
const handleNewAnalysis = () => {
|
|
if(currentSessionId) clearAnalysisProgress(currentSessionId);
|
|
setCurrentSessionId(null);
|
|
setAnalysisStatus('idle');
|
|
setError(null);
|
|
setFinalReport(null);
|
|
clearMessages();
|
|
};
|
|
|
|
const renderContent = () => {
|
|
if (analysisStatus === 'starting') {
|
|
return <div style={{ textAlign: 'center', padding: '50px' }}><Spin size="large" tip="분석을 시작하고 있습니다..." /></div>;
|
|
}
|
|
|
|
if (currentSessionId && (analysisStatus === 'running' || analysisStatus === 'completed' || analysisStatus === 'failed')) {
|
|
return (
|
|
<AnalysisDisplay
|
|
sessionId={currentSessionId}
|
|
status={analysisStatus}
|
|
progress={analysisProgress[currentSessionId]}
|
|
messages={messages.filter(m => m.sessionId === currentSessionId)}
|
|
finalReport={finalReport}
|
|
onNewAnalysis={handleNewAnalysis}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return <AnalysisForm onStartAnalysis={handleStartAnalysis} loading={analysisStatus === 'starting'} />;
|
|
};
|
|
|
|
return (
|
|
<AnalysisContainer>
|
|
<CustomPageHeader>
|
|
<Title level={2}>AI 기반 주식 분석</Title>
|
|
<Paragraph type="secondary">
|
|
관심 있는 종목에 대한 심층 분석을 시작하세요.
|
|
</Paragraph>
|
|
</CustomPageHeader>
|
|
<Divider style={{ margin: '16px 0' }} />
|
|
|
|
{error && !currentSessionId && ( // Show top-level error only when no session is active
|
|
<Alert
|
|
message="오류"
|
|
description={error}
|
|
type="error"
|
|
showIcon
|
|
closable
|
|
onClose={() => setError(null)}
|
|
style={{ marginBottom: '24px' }}
|
|
/>
|
|
)}
|
|
|
|
{renderContent()}
|
|
</AnalysisContainer>
|
|
);
|
|
};
|
|
|
|
export default Analysis; |