|
import { useState, useEffect, useRef } from 'react' |
|
import { useAuth0 } from '@auth0/auth0-react' |
|
import { useNavigate } from 'react-router-dom' |
|
import ChatInput from './components/chatinput' |
|
import MessageBubble from './components/MessageBubble' |
|
import './styles/App.css' |
|
|
|
type Message = { role: 'user' | 'bot'; text: string } |
|
type Session = { id: number; title: string; messages: Message[] } |
|
|
|
function App() { |
|
const { loginWithRedirect, logout, isAuthenticated } = useAuth0() |
|
const navigate = useNavigate() |
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(true) |
|
const [showPrompt, setShowPrompt] = useState(true) |
|
const [sessions, setSessions] = useState<Session[]>([ |
|
{ id: 1, title: 'Session 1', messages: [] } |
|
]) |
|
const [activeSessionId, setActiveSessionId] = useState(1) |
|
const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0] |
|
const videoRef = useRef<HTMLVideoElement>(null) |
|
|
|
const handleSend = async (text: string) => { |
|
const userMessage: Message = { role: 'user', text } |
|
setSessions(prev => |
|
prev.map(session => |
|
session.id === activeSessionId |
|
? { ...session, messages: [...session.messages, userMessage] } |
|
: session |
|
) |
|
) |
|
setShowPrompt(false) |
|
|
|
try { |
|
const res = await fetch('https://fintrack-backend-8dji.onrender.com/generate-strategy', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ user_input: text }) |
|
}); |
|
|
|
const data = await res.json() |
|
const botText = data.strategy_code || data.error || 'Error: Empty response.' |
|
|
|
const botMessage: Message = { role: 'bot', text: botText } |
|
setSessions(prev => |
|
prev.map(session => |
|
session.id === activeSessionId |
|
? { ...session, messages: [...session.messages, botMessage] } |
|
: session |
|
) |
|
) |
|
} catch (err) { |
|
const errorMessage: Message = { role: 'bot', text: '鈿狅笍 Error: Failed to connect to server.' } |
|
setSessions(prev => |
|
prev.map(session => |
|
session.id === activeSessionId |
|
? { ...session, messages: [...session.messages, errorMessage] } |
|
: session |
|
) |
|
) |
|
} |
|
} |
|
|
|
const handleNewChat = () => { |
|
const newId = sessions.length + 1 |
|
const newSession: Session = { id: newId, title: `Session ${newId}`, messages: [] } |
|
setSessions(prev => [newSession, ...prev]) |
|
setActiveSessionId(newId) |
|
setShowPrompt(true) |
|
} |
|
|
|
const suggestions = [ |
|
'Enter a stock trading strategy...', |
|
'Backtest a moving average crossover...', |
|
'Analyze S&P 500 signals...', |
|
'Try a momentum-based portfolio...', |
|
'Run a mean reversion strategy...', |
|
'Backtest Bollinger Band breakouts...' |
|
] |
|
const [currentSuggestion, setCurrentSuggestion] = useState(suggestions[0]) |
|
|
|
useEffect(() => { |
|
const interval = setInterval(() => { |
|
setCurrentSuggestion(prev => { |
|
let next = prev |
|
while (next === prev) { |
|
next = suggestions[Math.floor(Math.random() * suggestions.length)] |
|
} |
|
return next |
|
}) |
|
}, 3000) |
|
return () => clearInterval(interval) |
|
}, []) |
|
|
|
useEffect(() => { |
|
const handleVisibility = () => { |
|
if (videoRef.current) { |
|
if (document.visibilityState === 'visible') { |
|
videoRef.current.play().catch(() => {}) |
|
} else { |
|
videoRef.current.pause() |
|
} |
|
} |
|
} |
|
|
|
document.addEventListener('visibilitychange', handleVisibility) |
|
return () => { |
|
document.removeEventListener('visibilitychange', handleVisibility) |
|
} |
|
}, []) |
|
|
|
return ( |
|
<> |
|
<video |
|
ref={videoRef} |
|
className="background-video" |
|
autoPlay |
|
loop |
|
muted |
|
playsInline |
|
src="https://i.imgur.com/RCoLmZ9.mp4" |
|
/> |
|
|
|
<div className="app-container"> |
|
{sidebarOpen && ( |
|
<aside className="sidebar"> |
|
<div className="sidebar-header"> |
|
<img |
|
src="/image.png" |
|
alt="Collapse Sidebar" |
|
className="sidebar-icon" |
|
onClick={() => setSidebarOpen(false)} |
|
/> |
|
<h1 className="site-title">FinTrack</h1> |
|
<img |
|
src="/newchat.png" |
|
alt="New Chat" |
|
className="sidebar-icon" |
|
onClick={handleNewChat} |
|
/> |
|
</div> |
|
|
|
<div className="history-list"> |
|
{sessions.map(session => ( |
|
<div |
|
key={session.id} |
|
className={`history-item ${session.id === activeSessionId ? 'active' : ''}`} |
|
onClick={() => setActiveSessionId(session.id)} |
|
> |
|
{session.title} |
|
</div> |
|
))} |
|
</div> |
|
</aside> |
|
)} |
|
|
|
{!sidebarOpen && ( |
|
<img |
|
src="/opensidebar.png" |
|
alt="Open Sidebar" |
|
className="open-sidebar-button" |
|
onClick={() => setSidebarOpen(true)} |
|
/> |
|
)} |
|
|
|
<main className="main-panel"> |
|
<div className="top-ui-wrapper"> |
|
<div className="top-navbar"> |
|
<img |
|
src="/Cropped_Image.png" |
|
className="nav-logo" |
|
alt="Logo" |
|
onClick={() => navigate('/')} |
|
/> |
|
<span className="nav-link" onClick={() => navigate('/about')}>About</span> |
|
<span className="nav-link" onClick={() => navigate('/about#founders')}>Founders</span> |
|
</div> |
|
|
|
<div className="auth-buttons"> |
|
{!isAuthenticated ? ( |
|
<> |
|
<span className="auth-link" onClick={() => loginWithRedirect()}>Login</span> |
|
<span |
|
className="auth-link" |
|
onClick={() => |
|
loginWithRedirect({ authorizationParams: { screen_hint: 'signup' } }) |
|
} |
|
> |
|
Sign Up |
|
</span> |
|
</> |
|
) : ( |
|
<span className="auth-link" onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}> |
|
Log Out |
|
</span> |
|
)} |
|
</div> |
|
</div> |
|
|
|
{showPrompt && ( |
|
<div className="prompt-banner"> |
|
<h2 className="prompt-text fade-in">How can I help you?</h2> |
|
<p className="suggestion-text">{currentSuggestion}</p> |
|
</div> |
|
)} |
|
|
|
<div className="chat-area"> |
|
{activeSession.messages.map((msg, i) => ( |
|
<MessageBubble key={i} role={msg.role} text={msg.text} /> |
|
))} |
|
</div> |
|
|
|
<div className="chat-box-wrapper"> |
|
<ChatInput onSend={handleSend} /> |
|
</div> |
|
</main> |
|
</div> |
|
</> |
|
) |
|
} |
|
|
|
export default App |
|
|