[add] analysis
This commit is contained in:
parent
10a8e18005
commit
68e809bbc8
|
|
@ -41,3 +41,4 @@ chainlit
|
||||||
rich
|
rich
|
||||||
questionary
|
questionary
|
||||||
langgraph==0.4.8
|
langgraph==0.4.8
|
||||||
|
daphne
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class WebsocketConfig(AppConfig):
|
class WebsocketConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.websocket'
|
name = 'apps.websocket'
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import json
|
import json
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
from channels.db import database_sync_to_async
|
from channels.db import database_sync_to_async
|
||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from rest_framework_simplejwt.tokens import UntypedToken
|
from rest_framework_simplejwt.tokens import UntypedToken
|
||||||
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
|
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class TradingAnalysisConsumer(AsyncWebsocketConsumer):
|
class TradingAnalysisConsumer(AsyncWebsocketConsumer):
|
||||||
"""거래 분석 실시간 업데이트 WebSocket Consumer"""
|
"""거래 분석 실시간 업데이트 WebSocket Consumer"""
|
||||||
|
|
@ -121,6 +117,10 @@ class TradingAnalysisConsumer(AsyncWebsocketConsumer):
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def get_user_from_token(self):
|
def get_user_from_token(self):
|
||||||
"""JWT 토큰에서 사용자 정보 추출"""
|
"""JWT 토큰에서 사용자 정보 추출"""
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
try:
|
try:
|
||||||
# URL에서 토큰 추출 (query parameter 또는 header)
|
# URL에서 토큰 추출 (query parameter 또는 header)
|
||||||
token = None
|
token = None
|
||||||
|
|
@ -142,13 +142,16 @@ class TradingAnalysisConsumer(AsyncWebsocketConsumer):
|
||||||
|
|
||||||
# JWT 토큰 검증
|
# JWT 토큰 검증
|
||||||
try:
|
try:
|
||||||
UntypedToken(token) # 토큰 유효성 검사
|
# simplejwt 설정에서 올바른 서명 키와 알고리즘 가져오기
|
||||||
|
from rest_framework_simplejwt.settings import api_settings
|
||||||
|
|
||||||
|
UntypedToken(token) # 토큰 기본 구조 검증
|
||||||
|
|
||||||
# 토큰에서 사용자 ID 추출
|
# 토큰에서 사용자 ID 추출
|
||||||
decoded_token = jwt.decode(
|
decoded_token = jwt.decode(
|
||||||
token,
|
token,
|
||||||
settings.SECRET_KEY,
|
api_settings.SIGNING_KEY, # 올바른 서명 키 사용
|
||||||
algorithms=['HS256']
|
algorithms=[api_settings.ALGORITHM] # 올바른 알고리즘 사용
|
||||||
)
|
)
|
||||||
user_id = decoded_token.get('user_id')
|
user_id = decoded_token.get('user_id')
|
||||||
|
|
||||||
|
|
@ -171,6 +174,7 @@ class TradingAnalysisConsumer(AsyncWebsocketConsumer):
|
||||||
def check_session_ownership(self, session_id):
|
def check_session_ownership(self, session_id):
|
||||||
"""분석 세션 소유권 확인"""
|
"""분석 세션 소유권 확인"""
|
||||||
try:
|
try:
|
||||||
|
# 지연 import
|
||||||
from apps.authentication.models import AnalysisSession
|
from apps.authentication.models import AnalysisSession
|
||||||
session = AnalysisSession.objects.get(id=session_id, user=self.user)
|
session = AnalysisSession.objects.get(id=session_id, user=self.user)
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
echo "🚀 Django 서버 시작 - 데이터베이스 초기화"
|
echo "🚀 Django 서버 시작 - 데이터베이스 초기화"
|
||||||
|
|
||||||
|
# Django 설정 모듈 환경 변수 설정
|
||||||
|
export DJANGO_SETTINGS_MODULE=tradingagents_web.settings
|
||||||
|
|
||||||
# 1. 데이터베이스 초기화
|
# 1. 데이터베이스 초기화
|
||||||
echo "🔄 데이터베이스 초기화 중..."
|
echo "🔄 데이터베이스 초기화 중..."
|
||||||
docker exec -i tradingagents_mysql mysql -u root -ppassword -e "
|
docker exec -i tradingagents_mysql mysql -u root -ppassword -e "
|
||||||
|
|
@ -25,6 +28,6 @@ if not User.objects.filter(email='admin@example.com').exists():
|
||||||
print('✅ 관리자: admin@example.com / admin123!');
|
print('✅ 관리자: admin@example.com / admin123!');
|
||||||
"
|
"
|
||||||
|
|
||||||
# 4. 서버 시작
|
# 4. 서버 시작 (환경 변수와 함께)
|
||||||
echo "🎉 서버 시작!"
|
echo "🎉 서버 시작!"
|
||||||
python manage.py runserver
|
daphne -b 0.0.0.0 -p 8000 tradingagents_web.asgi:application
|
||||||
|
|
@ -8,12 +8,15 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import django
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
||||||
from channels.auth import AuthMiddlewareStack
|
|
||||||
import apps.websocket.routing
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tradingagents_web.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tradingagents_web.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from channels.auth import AuthMiddlewareStack
|
||||||
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
import apps.websocket.routing
|
||||||
|
|
||||||
application = ProtocolTypeRouter({
|
application = ProtocolTypeRouter({
|
||||||
"http": get_asgi_application(),
|
"http": get_asgi_application(),
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,129 @@
|
||||||
import React from 'react';
|
// web/frontend/src/pages/Analysis/Analysis.js
|
||||||
import { Card, Typography } from 'antd';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Card, Divider, Spin, Alert, Typography } 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`
|
const AnalysisContainer = styled.div`
|
||||||
max-width: 800px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: ${props => props.theme.spacing.lg};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PlaceholderCard = styled(Card)`
|
const CustomPageHeader = styled(Card)`
|
||||||
text-align: center;
|
border: none;
|
||||||
padding: ${props => props.theme.spacing.xl};
|
background-color: transparent;
|
||||||
background: linear-gradient(135deg, #f0f2f5 0%, #e6f7ff 100%);
|
.ant-card-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Analysis = () => {
|
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-analysis/', 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 (
|
return (
|
||||||
<AnalysisContainer>
|
<AnalysisContainer>
|
||||||
<PlaceholderCard>
|
<CustomPageHeader>
|
||||||
<Title level={2}>분석 시작 페이지</Title>
|
<Title level={2}>AI 기반 주식 분석</Title>
|
||||||
<Text type="secondary">
|
<Paragraph type="secondary">
|
||||||
여기에 거래 분석을 시작할 수 있는 폼이 들어갑니다.
|
관심 있는 종목에 대한 심층 분석을 시작하세요.
|
||||||
<br />
|
</Paragraph>
|
||||||
종목 선택, 분석 옵션 설정, 분석가 선택 등의 기능이 포함됩니다.
|
</CustomPageHeader>
|
||||||
</Text>
|
<Divider />
|
||||||
</PlaceholderCard>
|
|
||||||
|
{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>
|
</AnalysisContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Analysis;
|
export default Analysis;
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import { Card, Progress, Timeline, Button, Result, Typography, Empty, Tag } from 'antd';
|
||||||
|
import { FileDoneOutlined, RedoOutlined } from '@ant-design/icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
|
const { Title, Paragraph, Text } = Typography;
|
||||||
|
|
||||||
|
const DisplayCard = styled(Card)`
|
||||||
|
border-radius: ${props => props.theme.borderRadius.lg};
|
||||||
|
box-shadow: ${props => props.theme.shadows.lg};
|
||||||
|
margin-top: ${props => props.theme.spacing.lg};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimelineContainer = styled.div`
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: ${props => props.theme.spacing.md};
|
||||||
|
border: 1px solid ${props => props.theme.colors.border};
|
||||||
|
border-radius: ${props => props.theme.borderRadius.md};
|
||||||
|
background-color: ${props => props.theme.colors.backgroundSecondary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ReportContainer = styled.div`
|
||||||
|
margin-top: ${props => props.theme.spacing.lg};
|
||||||
|
padding: ${props => props.theme.spacing.lg};
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: ${props => props.theme.borderRadius.md};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const agentTagColors = {
|
||||||
|
market: 'blue',
|
||||||
|
social: 'cyan',
|
||||||
|
news: 'green',
|
||||||
|
fundamentals: 'purple',
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnalysisDisplay = ({ sessionId, status, progress, messages, finalReport, onNewAnalysis }) => {
|
||||||
|
const timelineEndRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
timelineEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const renderRunningState = () => (
|
||||||
|
<>
|
||||||
|
<Title level={4}>분석 진행 중...</Title>
|
||||||
|
<Paragraph type="secondary">AI 분석가들이 정보를 수집하고 분석하고 있습니다. (세션 ID: {sessionId})</Paragraph>
|
||||||
|
<Progress percent={progress?.progress || 0} status="active" />
|
||||||
|
<Paragraph style={{ textAlign: 'center', marginTop: '10px' }}>{progress?.message}</Paragraph>
|
||||||
|
|
||||||
|
<TimelineContainer>
|
||||||
|
<Timeline>
|
||||||
|
{messages.length > 0 ? messages.map(msg => (
|
||||||
|
<Timeline.Item key={msg.id}>
|
||||||
|
<Text strong>
|
||||||
|
<Tag color={agentTagColors[msg.agent] || 'default'}>{msg.agent}</Tag>
|
||||||
|
{new Date(msg.timestamp).toLocaleTimeString()}
|
||||||
|
</Text>
|
||||||
|
<Paragraph style={{ marginLeft: '10px' }}>{msg.content}</Paragraph>
|
||||||
|
</Timeline.Item>
|
||||||
|
)) : <Empty description="실시간 분석 로그가 여기에 표시됩니다." />}
|
||||||
|
</Timeline>
|
||||||
|
<div ref={timelineEndRef} />
|
||||||
|
</TimelineContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderCompletedState = () => (
|
||||||
|
<Result
|
||||||
|
status="success"
|
||||||
|
title="분석이 성공적으로 완료되었습니다!"
|
||||||
|
icon={<FileDoneOutlined />}
|
||||||
|
subTitle="아래에서 생성된 최종 보고서를 확인하세요."
|
||||||
|
extra={[
|
||||||
|
<Button type="primary" key="new" icon={<RedoOutlined />} onClick={onNewAnalysis}>
|
||||||
|
새 분석 시작
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderFailedState = () => (
|
||||||
|
<Result
|
||||||
|
status="error"
|
||||||
|
title="분석에 실패했습니다."
|
||||||
|
subTitle={progress?.error || '알 수 없는 오류가 발생했습니다.'}
|
||||||
|
extra={[
|
||||||
|
<Button type="primary" key="retry" icon={<RedoOutlined />} onClick={onNewAnalysis}>
|
||||||
|
다시 시도
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DisplayCard>
|
||||||
|
{status === 'running' && renderRunningState()}
|
||||||
|
{status === 'completed' && renderCompletedState()}
|
||||||
|
{status === 'failed' && renderFailedState()}
|
||||||
|
|
||||||
|
{status === 'completed' && finalReport && (
|
||||||
|
<ReportContainer>
|
||||||
|
<Title level={3}>최종 분석 보고서</Title>
|
||||||
|
<ReactMarkdown>{finalReport}</ReactMarkdown>
|
||||||
|
</ReportContainer>
|
||||||
|
)}
|
||||||
|
</DisplayCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalysisDisplay;
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Form, Input, Button, Card, Select, Slider, Checkbox, Row, Col, Typography } from 'antd';
|
||||||
|
import { FundOutlined, SendOutlined } from '@ant-design/icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const FormCard = styled(Card)`
|
||||||
|
border-radius: ${props => props.theme.borderRadius.lg};
|
||||||
|
box-shadow: ${props => props.theme.shadows.lg};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const analystsOptions = [
|
||||||
|
{ label: '시장 분석가 (Market)', value: 'market' },
|
||||||
|
{ label: '소셜 분석가 (Social)', value: 'social' },
|
||||||
|
{ label: '뉴스 분석가 (News)', value: 'news' },
|
||||||
|
{ label: '재무 분석가 (Fundamentals)', value: 'fundamentals' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const AnalysisForm = ({ onStartAnalysis, loading }) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const handleSubmit = (values) => {
|
||||||
|
onStartAnalysis(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormCard>
|
||||||
|
<Title level={4} style={{ textAlign: 'center', marginBottom: '24px' }}>새 분석 시작</Title>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleSubmit}
|
||||||
|
initialValues={{
|
||||||
|
research_depth: 3,
|
||||||
|
analysts_selected: ['market', 'news'],
|
||||||
|
shallow_thinker: 'gpt-4o-mini',
|
||||||
|
deep_thinker: 'gpt-4o'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="ticker"
|
||||||
|
label="분석할 주식 Ticker"
|
||||||
|
rules={[{ required: true, message: 'Ticker를 입력해주세요 (예: AAPL, TSLA)' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<FundOutlined />}
|
||||||
|
placeholder="예: AAPL, GOOGL, MSFT"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="analysts_selected"
|
||||||
|
label="분석가 팀 선택"
|
||||||
|
rules={[{ required: true, message: '최소 한 명의 분석가를 선택해주세요.' }]}
|
||||||
|
>
|
||||||
|
<Checkbox.Group style={{ width: '100%' }}>
|
||||||
|
<Row>
|
||||||
|
{analystsOptions.map(option => (
|
||||||
|
<Col span={12} key={option.value}>
|
||||||
|
<Checkbox value={option.value}>{option.label}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Checkbox.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="research_depth"
|
||||||
|
label="분석 깊이"
|
||||||
|
>
|
||||||
|
<Slider
|
||||||
|
min={1}
|
||||||
|
max={5}
|
||||||
|
step={2}
|
||||||
|
marks={{ 1: '가볍게', 3: '보통', 5: '심층' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item name="shallow_thinker" label="Shallow Thinker 모델">
|
||||||
|
<Select>
|
||||||
|
<Option value="gpt-4o-mini">GPT-4o Mini</Option>
|
||||||
|
<Option value="gpt-4o">GPT-4o</Option>
|
||||||
|
<Option value="gpt-4-turbo">GPT-4 Turbo</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item name="deep_thinker" label="Deep Thinker 모델">
|
||||||
|
<Select>
|
||||||
|
<Option value="gpt-4o">GPT-4o</Option>
|
||||||
|
<Option value="gpt-4-turbo">GPT-4 Turbo</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item style={{ marginTop: '24px' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
icon={<SendOutlined />}
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{loading ? '분석 시작 중...' : '분석 시작'}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</FormCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalysisForm;
|
||||||
|
|
@ -97,7 +97,7 @@ const Dashboard = () => {
|
||||||
});
|
});
|
||||||
const [recentAnalyses, setRecentAnalyses] = useState([]);
|
const [recentAnalyses, setRecentAnalyses] = useState([]);
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { connected, analysisProgress } = useWebSocket();
|
const { connected, messages } = useWebSocket();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue