Upload 31 files
Browse files- backend/__pycache__/gemini.cpython-311.pyc +0 -0
- backend/__pycache__/server.cpython-311.pyc +0 -0
- backend/gemini.py +85 -0
- backend/requirements.txt +0 -0
- backend/server.py +30 -0
- public/Cropped_Image.png +0 -0
- public/image.png +0 -0
- public/newchat.png +0 -0
- public/opensidebar.png +0 -0
- public/vite.svg +1 -0
- src/.DS_Store +0 -0
- src/App.tsx +223 -0
- src/TextToCodeModel(1).py +111 -0
- src/assets/react.svg +1 -0
- src/components/AboutPage.tsx +84 -0
- src/components/History.tsx +14 -0
- src/components/MessageBubble.tsx +19 -0
- src/components/Metrics.tsx +30 -0
- src/components/Spinner.tsx +7 -0
- src/components/chatinput.tsx +38 -0
- src/main.tsx +26 -0
- src/styles/AboutPage.css +102 -0
- src/styles/App.css +276 -0
- src/styles/History.css +38 -0
- src/styles/MessageBubble.css +36 -0
- src/styles/Metrics.css +33 -0
- src/styles/Spinner.css +14 -0
- src/styles/chatinput.css +44 -0
- src/styles/index.css +58 -0
- src/styles/styles.css +23 -0
- src/vite-env.d.ts +1 -0
backend/__pycache__/gemini.cpython-311.pyc
ADDED
Binary file (4.36 kB). View file
|
|
backend/__pycache__/server.cpython-311.pyc
ADDED
Binary file (1.59 kB). View file
|
|
backend/gemini.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import backtrader as bt
|
2 |
+
import yfinance as yf
|
3 |
+
import re
|
4 |
+
import google.generativeai as genai
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
import pandas as pd
|
7 |
+
import numpy as np
|
8 |
+
from datetime import datetime
|
9 |
+
|
10 |
+
|
11 |
+
# Configure the Generative AI model
|
12 |
+
MODEL_ID = "models/gemini-2.0-flash"
|
13 |
+
API_KEY = "ENTER API_KEY HERE"
|
14 |
+
genai.configure(api_key=API_KEY)
|
15 |
+
model = genai.GenerativeModel(MODEL_ID)
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
|
20 |
+
# ====== 1. Get Strategy Code from Gemini ======
|
21 |
+
def get_strategy_code_from_gemini(user_input: str):
|
22 |
+
prompt = f"""
|
23 |
+
You are a financial assistant that writes Backtrader strategies.
|
24 |
+
|
25 |
+
|
26 |
+
Take this user request: "{user_input}"
|
27 |
+
Generate a complete Backtrader strategy (Python) using bt.Strategy with these requirements:
|
28 |
+
1. Initial capital is $100
|
29 |
+
2. Only whole share orders (no fractional shares)
|
30 |
+
3. Must include both buy and sell logic
|
31 |
+
4. Strategy should have at least one indicator (like SMA, RSI, etc.)
|
32 |
+
5. Include proper order management (check for pending orders)
|
33 |
+
6. Include logging of trades
|
34 |
+
7. Output only valid Python code (no explanation or markdown formatting)
|
35 |
+
"""
|
36 |
+
response = model.generate_content(prompt)
|
37 |
+
strategy_code = response.text.strip()
|
38 |
+
strategy_code = strategy_code.replace("```python", "").replace("```", "")
|
39 |
+
return strategy_code
|
40 |
+
|
41 |
+
|
42 |
+
# ====== 2. Convert Gemini Output into a Strategy Class ======
|
43 |
+
def create_strategy_from_code(code_string: str):
|
44 |
+
local_scope = {}
|
45 |
+
try:
|
46 |
+
exec(code_string, globals(), local_scope)
|
47 |
+
for obj in local_scope.values():
|
48 |
+
if isinstance(obj, type) and issubclass(obj, bt.Strategy):
|
49 |
+
return obj
|
50 |
+
raise ValueError("No valid strategy class found in Gemini output.")
|
51 |
+
except Exception as e:
|
52 |
+
raise ValueError(f"Error creating strategy from code: {str(e)}")
|
53 |
+
|
54 |
+
|
55 |
+
# ====== 3. Remove extraneous main execution block ======
|
56 |
+
def extract_strategy_only(code_str: str) -> str:
|
57 |
+
main_block_start = code_str.find("if __name__ == '__main__':")
|
58 |
+
return code_str[:main_block_start].strip() if main_block_start != -1 else code_str.strip()
|
59 |
+
|
60 |
+
|
61 |
+
# ====== 4. Main Workflow ======
|
62 |
+
def full_workflow(user_input: str):
|
63 |
+
# Extract ticker and dates
|
64 |
+
ticker_match = re.search(r'\b([A-Z]{2,5})\b', user_input)
|
65 |
+
ticker = ticker_match.group(1) if ticker_match else "AAPL"
|
66 |
+
|
67 |
+
|
68 |
+
dates = re.findall(r'(\d{4}-\d{2}-\d{2})', user_input)
|
69 |
+
start_date = dates[0] if len(dates) > 0 else '2022-01-01'
|
70 |
+
end_date = dates[1] if len(dates) > 1 else '2023-01-01'
|
71 |
+
|
72 |
+
|
73 |
+
print("\n🤖 Generating strategy...")
|
74 |
+
strategy_code = get_strategy_code_from_gemini(user_input)
|
75 |
+
|
76 |
+
|
77 |
+
print(extract_strategy_only(strategy_code))
|
78 |
+
|
79 |
+
|
80 |
+
# ====== 7. Example Execution ======
|
81 |
+
if __name__ == "__main__":
|
82 |
+
user_input = "Create RSI strategy for MSFT, buy below 30 sell above 70, from 2021-01-01 to 2022-12-31"
|
83 |
+
full_workflow(user_input)
|
84 |
+
|
85 |
+
generate_answer = get_strategy_code_from_gemini
|
backend/requirements.txt
ADDED
Binary file (194 Bytes). View file
|
|
backend/server.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from pydantic import BaseModel
|
4 |
+
from gemini import generate_answer
|
5 |
+
|
6 |
+
app = FastAPI()
|
7 |
+
|
8 |
+
# ✅ Allow requests from your frontend URL
|
9 |
+
app.add_middleware(
|
10 |
+
CORSMiddleware,
|
11 |
+
allow_origins=[
|
12 |
+
"https://fintrack-flax.vercel.app", # Vercel deployment URL
|
13 |
+
"http://localhost:5173", # Vite default port
|
14 |
+
"http://127.0.0.1:5173", # Some systems resolve as 127.0.0.1 instead
|
15 |
+
],
|
16 |
+
allow_credentials=True,
|
17 |
+
allow_methods=["*"],
|
18 |
+
allow_headers=["*"],
|
19 |
+
)
|
20 |
+
|
21 |
+
class StrategyRequest(BaseModel):
|
22 |
+
user_input: str
|
23 |
+
|
24 |
+
@app.post("/generate-strategy")
|
25 |
+
async def generate_strategy(request: StrategyRequest):
|
26 |
+
try:
|
27 |
+
response = generate_answer(request.user_input)
|
28 |
+
return {"strategy_code": response}
|
29 |
+
except Exception as e:
|
30 |
+
return {"error": str(e)}
|
public/Cropped_Image.png
ADDED
![]() |
public/image.png
ADDED
![]() |
public/newchat.png
ADDED
![]() |
public/opensidebar.png
ADDED
![]() |
public/vite.svg
ADDED
|
src/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
src/App.tsx
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useEffect, useRef } from 'react'
|
2 |
+
import { useAuth0 } from '@auth0/auth0-react'
|
3 |
+
import { useNavigate } from 'react-router-dom'
|
4 |
+
import ChatInput from './components/chatinput'
|
5 |
+
import MessageBubble from './components/MessageBubble'
|
6 |
+
import './styles/App.css'
|
7 |
+
|
8 |
+
type Message = { role: 'user' | 'bot'; text: string }
|
9 |
+
type Session = { id: number; title: string; messages: Message[] }
|
10 |
+
|
11 |
+
function App() {
|
12 |
+
const { loginWithRedirect, logout, isAuthenticated } = useAuth0()
|
13 |
+
const navigate = useNavigate()
|
14 |
+
|
15 |
+
const [sidebarOpen, setSidebarOpen] = useState(true)
|
16 |
+
const [showPrompt, setShowPrompt] = useState(true)
|
17 |
+
const [sessions, setSessions] = useState<Session[]>([
|
18 |
+
{ id: 1, title: 'Session 1', messages: [] }
|
19 |
+
])
|
20 |
+
const [activeSessionId, setActiveSessionId] = useState(1)
|
21 |
+
const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0]
|
22 |
+
const videoRef = useRef<HTMLVideoElement>(null)
|
23 |
+
|
24 |
+
const handleSend = async (text: string) => {
|
25 |
+
const userMessage: Message = { role: 'user', text }
|
26 |
+
setSessions(prev =>
|
27 |
+
prev.map(session =>
|
28 |
+
session.id === activeSessionId
|
29 |
+
? { ...session, messages: [...session.messages, userMessage] }
|
30 |
+
: session
|
31 |
+
)
|
32 |
+
)
|
33 |
+
setShowPrompt(false)
|
34 |
+
|
35 |
+
try {
|
36 |
+
const res = await fetch('https://fintrack-backend-8dji.onrender.com/generate-strategy', {
|
37 |
+
method: 'POST',
|
38 |
+
headers: { 'Content-Type': 'application/json' },
|
39 |
+
body: JSON.stringify({ user_input: text })
|
40 |
+
});
|
41 |
+
|
42 |
+
const data = await res.json()
|
43 |
+
const botText = data.strategy_code || data.error || 'Error: Empty response.'
|
44 |
+
|
45 |
+
const botMessage: Message = { role: 'bot', text: botText }
|
46 |
+
setSessions(prev =>
|
47 |
+
prev.map(session =>
|
48 |
+
session.id === activeSessionId
|
49 |
+
? { ...session, messages: [...session.messages, botMessage] }
|
50 |
+
: session
|
51 |
+
)
|
52 |
+
)
|
53 |
+
} catch (err) {
|
54 |
+
const errorMessage: Message = { role: 'bot', text: '⚠️ Error: Failed to connect to server.' }
|
55 |
+
setSessions(prev =>
|
56 |
+
prev.map(session =>
|
57 |
+
session.id === activeSessionId
|
58 |
+
? { ...session, messages: [...session.messages, errorMessage] }
|
59 |
+
: session
|
60 |
+
)
|
61 |
+
)
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
const handleNewChat = () => {
|
66 |
+
const newId = sessions.length + 1
|
67 |
+
const newSession: Session = { id: newId, title: `Session ${newId}`, messages: [] }
|
68 |
+
setSessions(prev => [newSession, ...prev])
|
69 |
+
setActiveSessionId(newId)
|
70 |
+
setShowPrompt(true)
|
71 |
+
}
|
72 |
+
|
73 |
+
const suggestions = [
|
74 |
+
'Enter a stock trading strategy...',
|
75 |
+
'Backtest a moving average crossover...',
|
76 |
+
'Analyze S&P 500 signals...',
|
77 |
+
'Try a momentum-based portfolio...',
|
78 |
+
'Run a mean reversion strategy...',
|
79 |
+
'Backtest Bollinger Band breakouts...'
|
80 |
+
]
|
81 |
+
const [currentSuggestion, setCurrentSuggestion] = useState(suggestions[0])
|
82 |
+
|
83 |
+
useEffect(() => {
|
84 |
+
const interval = setInterval(() => {
|
85 |
+
setCurrentSuggestion(prev => {
|
86 |
+
let next = prev
|
87 |
+
while (next === prev) {
|
88 |
+
next = suggestions[Math.floor(Math.random() * suggestions.length)]
|
89 |
+
}
|
90 |
+
return next
|
91 |
+
})
|
92 |
+
}, 3000)
|
93 |
+
return () => clearInterval(interval)
|
94 |
+
}, [])
|
95 |
+
|
96 |
+
useEffect(() => {
|
97 |
+
const handleVisibility = () => {
|
98 |
+
if (videoRef.current) {
|
99 |
+
if (document.visibilityState === 'visible') {
|
100 |
+
videoRef.current.play().catch(() => {})
|
101 |
+
} else {
|
102 |
+
videoRef.current.pause()
|
103 |
+
}
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
document.addEventListener('visibilitychange', handleVisibility)
|
108 |
+
return () => {
|
109 |
+
document.removeEventListener('visibilitychange', handleVisibility)
|
110 |
+
}
|
111 |
+
}, [])
|
112 |
+
|
113 |
+
return (
|
114 |
+
<>
|
115 |
+
<video
|
116 |
+
ref={videoRef}
|
117 |
+
className="background-video"
|
118 |
+
autoPlay
|
119 |
+
loop
|
120 |
+
muted
|
121 |
+
playsInline
|
122 |
+
src="https://i.imgur.com/RCoLmZ9.mp4"
|
123 |
+
/>
|
124 |
+
|
125 |
+
<div className="app-container">
|
126 |
+
{sidebarOpen && (
|
127 |
+
<aside className="sidebar">
|
128 |
+
<div className="sidebar-header">
|
129 |
+
<img
|
130 |
+
src="/image.png"
|
131 |
+
alt="Collapse Sidebar"
|
132 |
+
className="sidebar-icon"
|
133 |
+
onClick={() => setSidebarOpen(false)}
|
134 |
+
/>
|
135 |
+
<h1 className="site-title">FinTrack</h1>
|
136 |
+
<img
|
137 |
+
src="/newchat.png"
|
138 |
+
alt="New Chat"
|
139 |
+
className="sidebar-icon"
|
140 |
+
onClick={handleNewChat}
|
141 |
+
/>
|
142 |
+
</div>
|
143 |
+
|
144 |
+
<div className="history-list">
|
145 |
+
{sessions.map(session => (
|
146 |
+
<div
|
147 |
+
key={session.id}
|
148 |
+
className={`history-item ${session.id === activeSessionId ? 'active' : ''}`}
|
149 |
+
onClick={() => setActiveSessionId(session.id)}
|
150 |
+
>
|
151 |
+
{session.title}
|
152 |
+
</div>
|
153 |
+
))}
|
154 |
+
</div>
|
155 |
+
</aside>
|
156 |
+
)}
|
157 |
+
|
158 |
+
{!sidebarOpen && (
|
159 |
+
<img
|
160 |
+
src="/opensidebar.png"
|
161 |
+
alt="Open Sidebar"
|
162 |
+
className="open-sidebar-button"
|
163 |
+
onClick={() => setSidebarOpen(true)}
|
164 |
+
/>
|
165 |
+
)}
|
166 |
+
|
167 |
+
<main className="main-panel">
|
168 |
+
<div className="top-ui-wrapper">
|
169 |
+
<div className="top-navbar">
|
170 |
+
<img
|
171 |
+
src="/Cropped_Image.png"
|
172 |
+
className="nav-logo"
|
173 |
+
alt="Logo"
|
174 |
+
onClick={() => navigate('/')}
|
175 |
+
/>
|
176 |
+
<span className="nav-link" onClick={() => navigate('/about')}>About</span>
|
177 |
+
<span className="nav-link" onClick={() => navigate('/about#founders')}>Founders</span>
|
178 |
+
</div>
|
179 |
+
|
180 |
+
<div className="auth-buttons">
|
181 |
+
{!isAuthenticated ? (
|
182 |
+
<>
|
183 |
+
<span className="auth-link" onClick={() => loginWithRedirect()}>Login</span>
|
184 |
+
<span
|
185 |
+
className="auth-link"
|
186 |
+
onClick={() =>
|
187 |
+
loginWithRedirect({ authorizationParams: { screen_hint: 'signup' } })
|
188 |
+
}
|
189 |
+
>
|
190 |
+
Sign Up
|
191 |
+
</span>
|
192 |
+
</>
|
193 |
+
) : (
|
194 |
+
<span className="auth-link" onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}>
|
195 |
+
Log Out
|
196 |
+
</span>
|
197 |
+
)}
|
198 |
+
</div>
|
199 |
+
</div>
|
200 |
+
|
201 |
+
{showPrompt && (
|
202 |
+
<div className="prompt-banner">
|
203 |
+
<h2 className="prompt-text fade-in">How can I help you?</h2>
|
204 |
+
<p className="suggestion-text">{currentSuggestion}</p>
|
205 |
+
</div>
|
206 |
+
)}
|
207 |
+
|
208 |
+
<div className="chat-area">
|
209 |
+
{activeSession.messages.map((msg, i) => (
|
210 |
+
<MessageBubble key={i} role={msg.role} text={msg.text} />
|
211 |
+
))}
|
212 |
+
</div>
|
213 |
+
|
214 |
+
<div className="chat-box-wrapper">
|
215 |
+
<ChatInput onSend={handleSend} />
|
216 |
+
</div>
|
217 |
+
</main>
|
218 |
+
</div>
|
219 |
+
</>
|
220 |
+
)
|
221 |
+
}
|
222 |
+
|
223 |
+
export default App
|
src/TextToCodeModel(1).py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datasets import load_dataset
|
2 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, TextStreamer, DataCollatorForLanguageModeling
|
3 |
+
from peft import LoraConfig, get_peft_model, TaskType
|
4 |
+
from trl import SFTTrainer
|
5 |
+
import torch
|
6 |
+
|
7 |
+
|
8 |
+
# Clear CUDA cache before loading models
|
9 |
+
torch.cuda.empty_cache()
|
10 |
+
|
11 |
+
device = torch.device("cuda")
|
12 |
+
|
13 |
+
dataset = load_dataset("koutch/stackoverflow_python")
|
14 |
+
|
15 |
+
dataset = dataset["train"]
|
16 |
+
|
17 |
+
def format_example(example):
|
18 |
+
return {
|
19 |
+
"text": f"### Question:\n{example['question_body'].strip()}\n\n### Answer:\n{example['answer_body'].strip()}"
|
20 |
+
}
|
21 |
+
|
22 |
+
dataset = dataset.map(format_example)
|
23 |
+
dataset = dataset.filter(lambda x: x["text"] is not None and len(x["text"]) > 0)
|
24 |
+
dataset = dataset.shuffle(seed=42).select(range(30_000))
|
25 |
+
|
26 |
+
tokenizer = AutoTokenizer.from_pretrained("deepseek-ai/DeepSeek-R1-Distill-Qwen-7B")
|
27 |
+
|
28 |
+
tokenizer.add_special_tokens({'additional_special_tokens': ["<|user|>", "<|assistant|>"]})
|
29 |
+
|
30 |
+
data_collator = DataCollatorForLanguageModeling(
|
31 |
+
tokenizer=tokenizer,
|
32 |
+
mlm=False # Causal LM, so no masked LM
|
33 |
+
)
|
34 |
+
|
35 |
+
model = AutoModelForCausalLM.from_pretrained("deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",device_map="cuda",torch_dtype=torch.float16)
|
36 |
+
|
37 |
+
model.resize_token_embeddings(len(tokenizer))
|
38 |
+
|
39 |
+
lora_config = LoraConfig(
|
40 |
+
r=8,
|
41 |
+
lora_alpha=32,
|
42 |
+
target_modules=["q_proj", "v_proj"],
|
43 |
+
lora_dropout=0.1,
|
44 |
+
bias="none",
|
45 |
+
task_type=TaskType.CAUSAL_LM
|
46 |
+
)
|
47 |
+
|
48 |
+
model = get_peft_model(model, lora_config)
|
49 |
+
|
50 |
+
def tokenize_function(example):
|
51 |
+
result = tokenizer(
|
52 |
+
example["text"],
|
53 |
+
truncation=True,
|
54 |
+
padding="max_length",
|
55 |
+
max_length=1024,
|
56 |
+
)
|
57 |
+
result["text"] = example["text"] # Keep original text
|
58 |
+
return result
|
59 |
+
|
60 |
+
tokenized_dataset = dataset.map(tokenize_function, batched=True)
|
61 |
+
|
62 |
+
# Step 3: Training arguments
|
63 |
+
training_args = TrainingArguments(
|
64 |
+
output_dir="/home/deshpa70",
|
65 |
+
per_device_train_batch_size=2,
|
66 |
+
gradient_accumulation_steps=2,
|
67 |
+
num_train_epochs=1,
|
68 |
+
logging_dir="./logs",
|
69 |
+
save_total_limit=2,
|
70 |
+
logging_steps=250,
|
71 |
+
save_steps=500,
|
72 |
+
learning_rate=3e-4,
|
73 |
+
bf16=True,
|
74 |
+
optim="paged_adamw_8bit",
|
75 |
+
report_to="none"
|
76 |
+
)
|
77 |
+
|
78 |
+
trainer = SFTTrainer(
|
79 |
+
peft_config=lora_config,
|
80 |
+
model=model,
|
81 |
+
train_dataset=tokenized_dataset,
|
82 |
+
args=training_args,
|
83 |
+
data_collator=data_collator
|
84 |
+
)
|
85 |
+
|
86 |
+
trainer.train()
|
87 |
+
model.eval()
|
88 |
+
|
89 |
+
while True:
|
90 |
+
prompt = input("\n Enter your programming question (or type 'exit' to quit):\n> ")
|
91 |
+
if prompt.lower() == 'exit':
|
92 |
+
break
|
93 |
+
|
94 |
+
formatted_prompt = f"<|user|>\n{prompt}\n<|assistant|>\n"
|
95 |
+
inputs = tokenizer(formatted_prompt, return_tensors="pt").to(device)
|
96 |
+
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
|
97 |
+
|
98 |
+
|
99 |
+
output = model.generate(
|
100 |
+
**inputs,
|
101 |
+
max_new_tokens=50000,
|
102 |
+
do_sample=True,
|
103 |
+
top_p=0.9,
|
104 |
+
temperature=0.8,
|
105 |
+
repetition_penalty=1.1,
|
106 |
+
streamer=streamer
|
107 |
+
)
|
108 |
+
|
109 |
+
|
110 |
+
# Clear CUDA cache before loading models
|
111 |
+
torch.cuda.empty_cache()
|
src/assets/react.svg
ADDED
|
src/components/AboutPage.tsx
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useEffect, useRef } from 'react'
|
2 |
+
import { useNavigate } from 'react-router-dom'
|
3 |
+
import '../styles/AboutPage.css'
|
4 |
+
|
5 |
+
function AboutPage() {
|
6 |
+
const videoRef = useRef<HTMLVideoElement>(null)
|
7 |
+
const navigate = useNavigate()
|
8 |
+
|
9 |
+
useEffect(() => {
|
10 |
+
const hash = window.location.hash
|
11 |
+
if (hash === '#founders') {
|
12 |
+
const el = document.getElementById('founders-section')
|
13 |
+
el?.scrollIntoView({ behavior: 'smooth' })
|
14 |
+
}
|
15 |
+
|
16 |
+
const handleVisibility = () => {
|
17 |
+
if (videoRef.current) {
|
18 |
+
if (document.visibilityState === 'visible') {
|
19 |
+
videoRef.current.play().catch(() => {})
|
20 |
+
} else {
|
21 |
+
videoRef.current.pause()
|
22 |
+
}
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
document.addEventListener('visibilitychange', handleVisibility)
|
27 |
+
return () => {
|
28 |
+
document.removeEventListener('visibilitychange', handleVisibility)
|
29 |
+
}
|
30 |
+
}, [])
|
31 |
+
|
32 |
+
return (
|
33 |
+
<>
|
34 |
+
<video
|
35 |
+
ref={videoRef}
|
36 |
+
className="about-video"
|
37 |
+
autoPlay
|
38 |
+
loop
|
39 |
+
muted
|
40 |
+
playsInline
|
41 |
+
src="https://i.imgur.com/RCoLmZ9.mp4"
|
42 |
+
/>
|
43 |
+
|
44 |
+
{/* 🔼 Top navbar centered like on home screen */}
|
45 |
+
<div className="about-navbar-wrapper">
|
46 |
+
<div className="top-navbar">
|
47 |
+
<img
|
48 |
+
src="/Cropped_Image.png"
|
49 |
+
className="nav-logo"
|
50 |
+
alt="Logo"
|
51 |
+
onClick={() => navigate('/')}
|
52 |
+
/>
|
53 |
+
<span className="nav-link" onClick={() => navigate('/about')}>About</span>
|
54 |
+
<span className="nav-link" onClick={() => navigate('/about#founders')}>Founders</span>
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
|
58 |
+
<div className="about-page-scroll">
|
59 |
+
<section className="about-section" id="about-section">
|
60 |
+
<h1>About <span className="highlight">FinTrack</span></h1>
|
61 |
+
<p>
|
62 |
+
FinTrack is an intuitive, natural-language–powered stock strategy builder.
|
63 |
+
Just describe your idea — whether it’s backtesting a moving average crossover,
|
64 |
+
analyzing tech stocks, or deploying a momentum strategy — and our AI handles the rest.
|
65 |
+
</p>
|
66 |
+
</section>
|
67 |
+
|
68 |
+
<section className="founders-section" id="founders-section">
|
69 |
+
<h2>Founders</h2>
|
70 |
+
<p>Meet the team behind FinTrack:</p>
|
71 |
+
<ul>
|
72 |
+
<li><a href="https://www.linkedin.com/in/vishruthg/" target="_blank" rel="noopener noreferrer"><strong>Vishruth</strong></a> – Frontend</li>
|
73 |
+
<li><a href="https://www.linkedin.com/in/ronilmitra/" target="_blank" rel="noopener noreferrer"><strong>Ronil</strong></a> – Frontend</li>
|
74 |
+
<li><a href="https://www.linkedin.com/in/kalyan-archakam/" target="_blank" rel="noopener noreferrer"><strong>Kalyan</strong></a> – Backend</li>
|
75 |
+
<li><a href="https://www.linkedin.com/in/sudhanva-deshpande-81a94a280/" target="_blank" rel="noopener noreferrer"><strong>Sudhanva</strong></a> – Backend</li>
|
76 |
+
<li><a href="https://www.linkedin.com/in/navan-dendukuri-64a03b360/" target="_blank" rel="noopener noreferrer"><strong>Navan</strong></a> – Backend</li>
|
77 |
+
</ul>
|
78 |
+
</section>
|
79 |
+
</div>
|
80 |
+
</>
|
81 |
+
)
|
82 |
+
}
|
83 |
+
|
84 |
+
export default AboutPage
|
src/components/History.tsx
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
function History() {
|
2 |
+
return (
|
3 |
+
<aside className="history-panel">
|
4 |
+
<h2>History</h2>
|
5 |
+
<ul>
|
6 |
+
<li className="history-item">Session 1</li>
|
7 |
+
<li className="history-item">Session 2</li>
|
8 |
+
<li className="history-item">Session 3</li>
|
9 |
+
</ul>
|
10 |
+
</aside>
|
11 |
+
);
|
12 |
+
}
|
13 |
+
|
14 |
+
export default History;
|
src/components/MessageBubble.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import '../styles/MessageBubble.css'
|
2 |
+
|
3 |
+
function MessageBubble({ role, text }: { role: 'user' | 'bot'; text: string }) {
|
4 |
+
const isCode = text.includes('\n') && role === 'bot'
|
5 |
+
|
6 |
+
return (
|
7 |
+
<div className={`message-bubble ${role}`}>
|
8 |
+
{isCode ? (
|
9 |
+
<pre className="code-block">
|
10 |
+
<code>{text}</code>
|
11 |
+
</pre>
|
12 |
+
) : (
|
13 |
+
<p>{text}</p>
|
14 |
+
)}
|
15 |
+
</div>
|
16 |
+
)
|
17 |
+
}
|
18 |
+
|
19 |
+
export default MessageBubble
|
src/components/Metrics.tsx
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from 'react'
|
2 |
+
import '../styles/Metrics.css'
|
3 |
+
|
4 |
+
function Metrics() {
|
5 |
+
const [tab, setTab] = useState('code')
|
6 |
+
return (
|
7 |
+
<div className="metrics-panel">
|
8 |
+
<div className="tab-buttons">
|
9 |
+
<button className={tab === 'code' ? 'active' : ''} onClick={() => setTab('code')}>Code</button>
|
10 |
+
<button className={tab === 'chart' ? 'active' : ''} onClick={() => setTab('chart')}>Chart</button>
|
11 |
+
<button className={tab === 'metrics' ? 'active' : ''} onClick={() => setTab('metrics')}>Metrics</button>
|
12 |
+
</div>
|
13 |
+
<div className="tab-content">
|
14 |
+
{tab === 'code' && <pre>{`function backtest() {
|
15 |
+
// strategy here
|
16 |
+
}`}</pre>}
|
17 |
+
{tab === 'chart' && <p>[Chart output goes here]</p>}
|
18 |
+
{tab === 'metrics' && (
|
19 |
+
<ul>
|
20 |
+
<li>Return: 10%</li>
|
21 |
+
<li>Sharpe: 1.4</li>
|
22 |
+
<li>Max Drawdown: 5%</li>
|
23 |
+
</ul>
|
24 |
+
)}
|
25 |
+
</div>
|
26 |
+
</div>
|
27 |
+
)
|
28 |
+
}
|
29 |
+
|
30 |
+
export default Metrics
|
src/components/Spinner.tsx
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import '../styles/Spinner.css'
|
2 |
+
|
3 |
+
function Spinner() {
|
4 |
+
return <div className="spinner"></div>
|
5 |
+
}
|
6 |
+
|
7 |
+
export default Spinner
|
src/components/chatinput.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from 'react'
|
2 |
+
import '../styles/chatinput.css' // Make sure file is named exactly like this
|
3 |
+
|
4 |
+
function ChatInput({ onSend }: { onSend: (msg: string) => void }) {
|
5 |
+
const [input, setInput] = useState('')
|
6 |
+
|
7 |
+
const handleKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
8 |
+
if (e.key === 'Enter' && input.trim()) {
|
9 |
+
onSend(input)
|
10 |
+
setInput('')
|
11 |
+
}
|
12 |
+
}
|
13 |
+
|
14 |
+
return (
|
15 |
+
<div className="chat-input-wrapper">
|
16 |
+
<input
|
17 |
+
className="chat-input"
|
18 |
+
type="text"
|
19 |
+
placeholder="Ask something..."
|
20 |
+
value={input}
|
21 |
+
onChange={(e) => setInput(e.target.value)}
|
22 |
+
onKeyDown={handleKey}
|
23 |
+
/>
|
24 |
+
<button className="send-button" onClick={handleSend}>
|
25 |
+
Send
|
26 |
+
</button>
|
27 |
+
</div>
|
28 |
+
)
|
29 |
+
|
30 |
+
function handleSend() {
|
31 |
+
if (input.trim()) {
|
32 |
+
onSend(input)
|
33 |
+
setInput('')
|
34 |
+
}
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
export default ChatInput
|
src/main.tsx
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StrictMode } from 'react'
|
2 |
+
import { createRoot } from 'react-dom/client'
|
3 |
+
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
4 |
+
import { Auth0Provider } from '@auth0/auth0-react'
|
5 |
+
import './styles/index.css'
|
6 |
+
import App from './App'
|
7 |
+
import AboutPage from './components/AboutPage'
|
8 |
+
|
9 |
+
createRoot(document.getElementById('root')!).render(
|
10 |
+
<StrictMode>
|
11 |
+
<Auth0Provider
|
12 |
+
domain="fintrack-dev.us.auth0.com"
|
13 |
+
clientId="tM4JJsEXpaSaYU84C7aXxlp0bWhXcwyM"
|
14 |
+
authorizationParams={{
|
15 |
+
redirect_uri: window.location.origin,
|
16 |
+
}}
|
17 |
+
>
|
18 |
+
<BrowserRouter>
|
19 |
+
<Routes>
|
20 |
+
<Route path="/" element={<App />} />
|
21 |
+
<Route path="/about" element={<AboutPage />} />
|
22 |
+
</Routes>
|
23 |
+
</BrowserRouter>
|
24 |
+
</Auth0Provider>
|
25 |
+
</StrictMode>
|
26 |
+
)
|
src/styles/AboutPage.css
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.about-video {
|
2 |
+
position: fixed;
|
3 |
+
top: 0;
|
4 |
+
left: 0;
|
5 |
+
width: 100vw;
|
6 |
+
height: 100vh;
|
7 |
+
object-fit: cover;
|
8 |
+
z-index: 0;
|
9 |
+
opacity: 0.15;
|
10 |
+
filter: blur(5px) brightness(0.9);
|
11 |
+
pointer-events: none;
|
12 |
+
}
|
13 |
+
|
14 |
+
/* 🔼 Centered NAVBAR on AboutPage */
|
15 |
+
.about-navbar-wrapper {
|
16 |
+
position: fixed;
|
17 |
+
top: 1.5rem;
|
18 |
+
left: 50%;
|
19 |
+
transform: translateX(-50%);
|
20 |
+
z-index: 5;
|
21 |
+
}
|
22 |
+
|
23 |
+
.top-navbar {
|
24 |
+
display: flex;
|
25 |
+
align-items: center;
|
26 |
+
justify-content: center;
|
27 |
+
gap: 2rem;
|
28 |
+
background-color: rgba(0, 0, 0, 0.4);
|
29 |
+
padding: 0.75rem 1.5rem;
|
30 |
+
border-radius: 50px;
|
31 |
+
}
|
32 |
+
|
33 |
+
.nav-logo {
|
34 |
+
width: 32px;
|
35 |
+
height: 32px;
|
36 |
+
cursor: pointer;
|
37 |
+
}
|
38 |
+
|
39 |
+
.nav-link {
|
40 |
+
font-size: 1rem;
|
41 |
+
font-weight: 500;
|
42 |
+
cursor: pointer;
|
43 |
+
color: var(--color-text);
|
44 |
+
}
|
45 |
+
|
46 |
+
/* 🔁 Scroll layout */
|
47 |
+
.about-page-scroll {
|
48 |
+
position: relative;
|
49 |
+
z-index: 2;
|
50 |
+
height: 100vh;
|
51 |
+
overflow-y: scroll;
|
52 |
+
scroll-snap-type: y mandatory;
|
53 |
+
}
|
54 |
+
|
55 |
+
.about-section,
|
56 |
+
.founders-section {
|
57 |
+
scroll-snap-align: start;
|
58 |
+
min-height: 100vh;
|
59 |
+
padding: 5rem 2rem;
|
60 |
+
max-width: 900px;
|
61 |
+
margin: 0 auto;
|
62 |
+
color: var(--color-text);
|
63 |
+
font-family: 'Poppins', sans-serif;
|
64 |
+
display: flex;
|
65 |
+
flex-direction: column;
|
66 |
+
justify-content: center;
|
67 |
+
text-align: center;
|
68 |
+
}
|
69 |
+
|
70 |
+
.about-section h1,
|
71 |
+
.founders-section h2 {
|
72 |
+
font-size: 2.5rem;
|
73 |
+
margin-bottom: 1rem;
|
74 |
+
}
|
75 |
+
|
76 |
+
.highlight {
|
77 |
+
background: linear-gradient(to right, #38d9a9, #66ffe3);
|
78 |
+
-webkit-background-clip: text;
|
79 |
+
-webkit-text-fill-color: transparent;
|
80 |
+
font-weight: 800;
|
81 |
+
}
|
82 |
+
|
83 |
+
.about-section p,
|
84 |
+
.founders-section p,
|
85 |
+
.founders-section ul {
|
86 |
+
font-size: 1.1rem;
|
87 |
+
line-height: 1.6;
|
88 |
+
color: var(--color-muted);
|
89 |
+
max-width: 700px;
|
90 |
+
margin: 0 auto;
|
91 |
+
}
|
92 |
+
|
93 |
+
.founders-section ul {
|
94 |
+
list-style: none;
|
95 |
+
padding-left: 0;
|
96 |
+
margin-top: 1.5rem;
|
97 |
+
}
|
98 |
+
|
99 |
+
.founders-section li {
|
100 |
+
padding: 0.5rem 0;
|
101 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
102 |
+
}
|
src/styles/App.css
ADDED
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap');
|
2 |
+
|
3 |
+
:root {
|
4 |
+
--color-bg: #0a192f;
|
5 |
+
--color-panel: rgba(0, 0, 0, 0.6);
|
6 |
+
--color-accent: #38d9a9;
|
7 |
+
--color-text: #ffffff;
|
8 |
+
--color-muted: #9ca3af;
|
9 |
+
--color-error: #ef4444;
|
10 |
+
font-family: 'Poppins', 'Segoe UI', sans-serif;
|
11 |
+
}
|
12 |
+
|
13 |
+
/* Reset */
|
14 |
+
body {
|
15 |
+
margin: 0;
|
16 |
+
padding: 0;
|
17 |
+
background-color: var(--color-bg);
|
18 |
+
color: var(--color-text);
|
19 |
+
font-size: 16px;
|
20 |
+
line-height: 1.5;
|
21 |
+
font-family: 'Poppins', 'Segoe UI', sans-serif;
|
22 |
+
overflow: hidden;
|
23 |
+
}
|
24 |
+
|
25 |
+
/* 🎥 Background video */
|
26 |
+
.background-video {
|
27 |
+
position: fixed;
|
28 |
+
top: 0;
|
29 |
+
left: 0;
|
30 |
+
width: 100vw;
|
31 |
+
height: 100vh;
|
32 |
+
object-fit: cover;
|
33 |
+
z-index: 0;
|
34 |
+
opacity: 0.15;
|
35 |
+
filter: blur(5px) brightness(0.9);
|
36 |
+
pointer-events: none;
|
37 |
+
}
|
38 |
+
|
39 |
+
/* Layout */
|
40 |
+
.app-container {
|
41 |
+
display: flex;
|
42 |
+
height: 100vh;
|
43 |
+
position: relative;
|
44 |
+
z-index: 1;
|
45 |
+
background-color: transparent;
|
46 |
+
color: var(--color-text);
|
47 |
+
}
|
48 |
+
|
49 |
+
/* Sidebar */
|
50 |
+
.sidebar {
|
51 |
+
width: 280px;
|
52 |
+
height: 100vh;
|
53 |
+
padding: 2rem 1.5rem;
|
54 |
+
background-color: rgba(0, 0, 0, 0.45);
|
55 |
+
backdrop-filter: blur(16px);
|
56 |
+
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.3);
|
57 |
+
z-index: 2;
|
58 |
+
display: flex;
|
59 |
+
flex-direction: column;
|
60 |
+
justify-content: flex-start;
|
61 |
+
position: relative;
|
62 |
+
}
|
63 |
+
|
64 |
+
.sidebar-header {
|
65 |
+
display: flex;
|
66 |
+
justify-content: space-between;
|
67 |
+
align-items: center;
|
68 |
+
height: 40px;
|
69 |
+
margin-bottom: 2.5rem;
|
70 |
+
position: relative;
|
71 |
+
}
|
72 |
+
|
73 |
+
.sidebar-icon {
|
74 |
+
width: 28px;
|
75 |
+
height: 28px;
|
76 |
+
cursor: pointer;
|
77 |
+
opacity: 0.9;
|
78 |
+
transition: transform 0.2s ease;
|
79 |
+
}
|
80 |
+
.sidebar-icon:hover {
|
81 |
+
transform: scale(1.1);
|
82 |
+
opacity: 1;
|
83 |
+
}
|
84 |
+
|
85 |
+
.site-title {
|
86 |
+
position: absolute;
|
87 |
+
left: 50%;
|
88 |
+
transform: translateX(-50%);
|
89 |
+
font-size: 1.75rem;
|
90 |
+
font-weight: 800;
|
91 |
+
background: linear-gradient(to right, #38d9a9, #66ffe3);
|
92 |
+
-webkit-background-clip: text;
|
93 |
+
-webkit-text-fill-color: transparent;
|
94 |
+
margin: 0;
|
95 |
+
}
|
96 |
+
|
97 |
+
.open-sidebar-button {
|
98 |
+
position: absolute;
|
99 |
+
top: 1.5rem;
|
100 |
+
left: 1.5rem;
|
101 |
+
width: 28px;
|
102 |
+
height: 28px;
|
103 |
+
cursor: pointer;
|
104 |
+
z-index: 3;
|
105 |
+
transition: transform 0.2s ease;
|
106 |
+
}
|
107 |
+
.open-sidebar-button:hover {
|
108 |
+
transform: scale(1.1);
|
109 |
+
}
|
110 |
+
|
111 |
+
/* 🧠 Session History */
|
112 |
+
.history-list {
|
113 |
+
display: flex;
|
114 |
+
flex-direction: column;
|
115 |
+
gap: 0.5rem;
|
116 |
+
}
|
117 |
+
.history-item {
|
118 |
+
padding: 0.6rem 1rem;
|
119 |
+
border-radius: 999px; /* bubble style */
|
120 |
+
background-color: rgba(51, 65, 85, 0.8);
|
121 |
+
cursor: pointer;
|
122 |
+
font-size: 0.95rem;
|
123 |
+
transition: background-color 0.2s ease, color 0.2s ease;
|
124 |
+
text-align: center;
|
125 |
+
}
|
126 |
+
.history-item:hover {
|
127 |
+
background-color: #38d9a9;
|
128 |
+
color: #0f172a;
|
129 |
+
}
|
130 |
+
.history-item.active {
|
131 |
+
background-color: #38d9a9;
|
132 |
+
color: #0f172a;
|
133 |
+
font-weight: 600;
|
134 |
+
}
|
135 |
+
|
136 |
+
/* 🔼 Top navbar */
|
137 |
+
.top-ui-wrapper {
|
138 |
+
display: flex;
|
139 |
+
align-items: center;
|
140 |
+
justify-content: space-between;
|
141 |
+
margin-bottom: 2rem;
|
142 |
+
padding: 0 2rem;
|
143 |
+
}
|
144 |
+
.top-navbar {
|
145 |
+
display: flex;
|
146 |
+
align-items: center;
|
147 |
+
gap: 2rem;
|
148 |
+
background-color: rgba(0, 0, 0, 0.4);
|
149 |
+
padding: 0.75rem 1.5rem;
|
150 |
+
border-radius: 50px;
|
151 |
+
margin: 0 auto;
|
152 |
+
}
|
153 |
+
.nav-logo {
|
154 |
+
width: 32px;
|
155 |
+
height: 32px;
|
156 |
+
cursor: pointer;
|
157 |
+
}
|
158 |
+
.nav-link {
|
159 |
+
font-size: 1rem;
|
160 |
+
font-weight: 500;
|
161 |
+
cursor: pointer;
|
162 |
+
}
|
163 |
+
|
164 |
+
/* 🔐 Auth buttons */
|
165 |
+
.auth-buttons {
|
166 |
+
display: flex;
|
167 |
+
gap: 2rem;
|
168 |
+
background-color: rgba(0, 0, 0, 0.4);
|
169 |
+
padding: 0.75rem 1.5rem;
|
170 |
+
border-radius: 50px;
|
171 |
+
}
|
172 |
+
.auth-link {
|
173 |
+
font-size: 1rem;
|
174 |
+
font-weight: 500;
|
175 |
+
cursor: pointer;
|
176 |
+
}
|
177 |
+
|
178 |
+
/* 👨💻 Main */
|
179 |
+
.main-panel {
|
180 |
+
flex: 1;
|
181 |
+
display: flex;
|
182 |
+
flex-direction: column;
|
183 |
+
padding: 1rem 2rem 8rem;
|
184 |
+
overflow-y: auto;
|
185 |
+
background-color: transparent;
|
186 |
+
position: relative;
|
187 |
+
z-index: 2;
|
188 |
+
}
|
189 |
+
|
190 |
+
|
191 |
+
/* 💬 Prompt */
|
192 |
+
.prompt-banner {
|
193 |
+
display: flex;
|
194 |
+
flex-direction: column;
|
195 |
+
align-items: center;
|
196 |
+
justify-content: center;
|
197 |
+
height: 100%;
|
198 |
+
min-height: 300px;
|
199 |
+
}
|
200 |
+
.prompt-text {
|
201 |
+
font-size: 3rem;
|
202 |
+
font-weight: 900;
|
203 |
+
background: linear-gradient(to right, #38d9a9, #66ffe3);
|
204 |
+
-webkit-background-clip: text;
|
205 |
+
-webkit-text-fill-color: transparent;
|
206 |
+
text-align: center;
|
207 |
+
}
|
208 |
+
.suggestion-text {
|
209 |
+
font-size: 0.9rem;
|
210 |
+
color: var(--color-muted);
|
211 |
+
margin-top: 1rem;
|
212 |
+
}
|
213 |
+
|
214 |
+
/* 💬 Chat area */
|
215 |
+
.chat-area {
|
216 |
+
flex: 1;
|
217 |
+
display: flex;
|
218 |
+
flex-direction: column;
|
219 |
+
justify-content: flex-end;
|
220 |
+
padding-bottom: 2rem;
|
221 |
+
}
|
222 |
+
|
223 |
+
/* 📝 Chat box */
|
224 |
+
.chat-box-wrapper {
|
225 |
+
display: flex;
|
226 |
+
justify-content: center;
|
227 |
+
padding: 1.5rem;
|
228 |
+
position: fixed;
|
229 |
+
bottom: 0;
|
230 |
+
left: 280px; /* width of your sidebar */
|
231 |
+
right: 0;
|
232 |
+
z-index: 3;
|
233 |
+
background: linear-gradient(to top, rgba(10, 25, 47, 0.95), transparent); /* optional: smooth fade */
|
234 |
+
}
|
235 |
+
|
236 |
+
.chat-box-wrapper > .chat-input-wrapper {
|
237 |
+
width: 100%;
|
238 |
+
max-width: 900px;
|
239 |
+
padding: 1rem 1.25rem;
|
240 |
+
border-radius: 16px;
|
241 |
+
background-color: rgba(0, 0, 0, 0.5);
|
242 |
+
backdrop-filter: blur(10px);
|
243 |
+
display: flex;
|
244 |
+
gap: 1rem;
|
245 |
+
align-items: center;
|
246 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
247 |
+
}
|
248 |
+
|
249 |
+
/* 🌐 Utility */
|
250 |
+
button {
|
251 |
+
cursor: pointer;
|
252 |
+
transition: all 0.2s ease-in-out;
|
253 |
+
}
|
254 |
+
a {
|
255 |
+
color: var(--color-accent);
|
256 |
+
text-decoration: none;
|
257 |
+
}
|
258 |
+
a:hover {
|
259 |
+
text-decoration: underline;
|
260 |
+
}
|
261 |
+
|
262 |
+
.about-page {
|
263 |
+
padding: 2rem;
|
264 |
+
background-color: var(--color-bg);
|
265 |
+
min-height: 100vh;
|
266 |
+
color: var(--color-text);
|
267 |
+
position: relative;
|
268 |
+
z-index: 1;
|
269 |
+
overflow-y: auto;
|
270 |
+
}
|
271 |
+
|
272 |
+
.about-content {
|
273 |
+
max-width: 900px;
|
274 |
+
margin: 0 auto;
|
275 |
+
padding-top: 3rem;
|
276 |
+
}
|
src/styles/History.css
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.chat-input-wrapper {
|
2 |
+
display: flex;
|
3 |
+
align-items: center;
|
4 |
+
justify-content: center;
|
5 |
+
gap: 1rem;
|
6 |
+
padding: 1rem 2rem;
|
7 |
+
margin-top: auto;
|
8 |
+
background-color: rgba(0, 0, 0, 0.6);
|
9 |
+
border-top: 1px solid #334155;
|
10 |
+
position: sticky;
|
11 |
+
bottom: 0;
|
12 |
+
z-index: 10;
|
13 |
+
}
|
14 |
+
|
15 |
+
.chat-input {
|
16 |
+
flex: 1;
|
17 |
+
max-width: 800px;
|
18 |
+
padding: 1rem 1.25rem;
|
19 |
+
background-color: #0f172a;
|
20 |
+
color: #ffffff;
|
21 |
+
border: 1px solid #334155;
|
22 |
+
border-radius: 12px;
|
23 |
+
font-size: 1rem;
|
24 |
+
}
|
25 |
+
|
26 |
+
.send-button {
|
27 |
+
background-color: var(--color-accent);
|
28 |
+
color: #0f172a;
|
29 |
+
font-weight: 600;
|
30 |
+
border: none;
|
31 |
+
padding: 1rem 1.5rem;
|
32 |
+
border-radius: 12px;
|
33 |
+
transition: background-color 0.2s;
|
34 |
+
}
|
35 |
+
|
36 |
+
.send-button:hover {
|
37 |
+
background-color: #2ec4a1;
|
38 |
+
}
|
src/styles/MessageBubble.css
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.message-bubble {
|
2 |
+
max-width: 70%;
|
3 |
+
padding: 0.75rem 1rem;
|
4 |
+
border-radius: 12px;
|
5 |
+
margin-bottom: 0.75rem;
|
6 |
+
font-size: 0.95rem;
|
7 |
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
8 |
+
line-height: 1.4;
|
9 |
+
font-family: 'Poppins', sans-serif;
|
10 |
+
}
|
11 |
+
|
12 |
+
.message-bubble.user {
|
13 |
+
background-color: #38d9a9;
|
14 |
+
color: #0f172a;
|
15 |
+
align-self: flex-end;
|
16 |
+
}
|
17 |
+
|
18 |
+
.message-bubble.bot {
|
19 |
+
background-color: #334155;
|
20 |
+
color: #ffffff;
|
21 |
+
align-self: flex-start;
|
22 |
+
}
|
23 |
+
|
24 |
+
/* 🧠 Code formatting for bot responses */
|
25 |
+
.code-block {
|
26 |
+
background-color: #1e293b;
|
27 |
+
font-family: 'Fira Code', 'Courier New', monospace;
|
28 |
+
font-size: 0.85rem;
|
29 |
+
padding: 1rem;
|
30 |
+
border-radius: 10px;
|
31 |
+
overflow-x: auto;
|
32 |
+
white-space: pre-wrap;
|
33 |
+
word-break: break-word;
|
34 |
+
line-height: 1.5;
|
35 |
+
margin: 0;
|
36 |
+
}
|
src/styles/Metrics.css
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.metrics-panel {
|
2 |
+
background-color: #1e293b;
|
3 |
+
color: #e2e8f0;
|
4 |
+
border-radius: 12px;
|
5 |
+
padding: 1.5rem;
|
6 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
7 |
+
margin-top: 1rem;
|
8 |
+
}
|
9 |
+
|
10 |
+
.tab-buttons button {
|
11 |
+
background-color: #334155;
|
12 |
+
color: #ffffff;
|
13 |
+
padding: 0.5rem 1rem;
|
14 |
+
margin-right: 0.5rem;
|
15 |
+
border: none;
|
16 |
+
border-radius: 6px;
|
17 |
+
font-weight: 500;
|
18 |
+
transition: background-color 0.2s ease;
|
19 |
+
}
|
20 |
+
|
21 |
+
.tab-buttons .active {
|
22 |
+
background-color: #38d9a9;
|
23 |
+
color: #0f172a;
|
24 |
+
}
|
25 |
+
|
26 |
+
.tab-content {
|
27 |
+
margin-top: 1rem;
|
28 |
+
background-color: #0f172a;
|
29 |
+
padding: 1rem;
|
30 |
+
border-radius: 8px;
|
31 |
+
font-family: monospace;
|
32 |
+
color: #e2e8f0;
|
33 |
+
}
|
src/styles/Spinner.css
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.spinner {
|
2 |
+
width: 32px;
|
3 |
+
height: 32px;
|
4 |
+
border: 4px solid #f3f3f3;
|
5 |
+
border-top: 4px solid #38d9a9;
|
6 |
+
border-radius: 50%;
|
7 |
+
animation: spin 1s linear infinite;
|
8 |
+
margin: 2rem auto;
|
9 |
+
}
|
10 |
+
|
11 |
+
@keyframes spin {
|
12 |
+
0% { transform: rotate(0deg); }
|
13 |
+
100% { transform: rotate(360deg); }
|
14 |
+
}
|
src/styles/chatinput.css
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.chat-input-wrapper {
|
2 |
+
display: flex;
|
3 |
+
align-items: center;
|
4 |
+
justify-content: center;
|
5 |
+
gap: 1rem;
|
6 |
+
padding: 1rem 2rem;
|
7 |
+
margin-top: auto;
|
8 |
+
|
9 |
+
background-color: rgba(0, 0, 0, 0.4); /* translucent black */
|
10 |
+
backdrop-filter: blur(12px); /* soft blur to match sidebar */
|
11 |
+
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
12 |
+
|
13 |
+
position: sticky;
|
14 |
+
bottom: 0;
|
15 |
+
z-index: 10;
|
16 |
+
}
|
17 |
+
|
18 |
+
.chat-input {
|
19 |
+
flex: 1;
|
20 |
+
max-width: 800px;
|
21 |
+
padding: 1rem 1.25rem;
|
22 |
+
background-color: rgba(15, 23, 42, 0.6);
|
23 |
+
color: #ffffff;
|
24 |
+
border: 1px solid #334155;
|
25 |
+
border-radius: 12px;
|
26 |
+
font-size: 1rem;
|
27 |
+
font-family: 'Poppins', sans-serif;
|
28 |
+
outline: none;
|
29 |
+
}
|
30 |
+
|
31 |
+
.send-button {
|
32 |
+
background-color: #38d9a9;
|
33 |
+
color: #0f172a;
|
34 |
+
font-weight: 600;
|
35 |
+
border: none;
|
36 |
+
padding: 1rem 1.5rem;
|
37 |
+
border-radius: 12px;
|
38 |
+
transition: background-color 0.2s ease;
|
39 |
+
font-family: 'Poppins', sans-serif;
|
40 |
+
}
|
41 |
+
|
42 |
+
.send-button:hover {
|
43 |
+
background-color: #2ec4a1;
|
44 |
+
}
|
src/styles/index.css
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap');
|
2 |
+
|
3 |
+
/* Root theme variables */
|
4 |
+
:root {
|
5 |
+
--color-bg: #0a192f;
|
6 |
+
--color-panel: rgba(0, 0, 0, 0.6);
|
7 |
+
--color-accent: #38d9a9;
|
8 |
+
--color-text: #ffffff;
|
9 |
+
--color-muted: #9ca3af;
|
10 |
+
--color-error: #ef4444;
|
11 |
+
|
12 |
+
font-family: 'Poppins', 'Segoe UI', sans-serif;
|
13 |
+
}
|
14 |
+
|
15 |
+
/* Global Reset */
|
16 |
+
* {
|
17 |
+
margin: 0;
|
18 |
+
padding: 0;
|
19 |
+
box-sizing: border-box;
|
20 |
+
}
|
21 |
+
|
22 |
+
html, body, #root {
|
23 |
+
height: 100%;
|
24 |
+
width: 100%;
|
25 |
+
font-family: 'Poppins', 'Segoe UI', sans-serif;
|
26 |
+
background-color: var(--color-bg);
|
27 |
+
color: var(--color-text);
|
28 |
+
overflow: hidden;
|
29 |
+
}
|
30 |
+
|
31 |
+
a {
|
32 |
+
color: var(--color-accent);
|
33 |
+
text-decoration: none;
|
34 |
+
}
|
35 |
+
|
36 |
+
a:hover {
|
37 |
+
text-decoration: underline;
|
38 |
+
}
|
39 |
+
|
40 |
+
button {
|
41 |
+
font-family: 'Poppins', sans-serif;
|
42 |
+
cursor: pointer;
|
43 |
+
transition: all 0.2s ease-in-out;
|
44 |
+
}
|
45 |
+
|
46 |
+
/* Make scrollbars prettier on webkit */
|
47 |
+
::-webkit-scrollbar {
|
48 |
+
width: 6px;
|
49 |
+
}
|
50 |
+
|
51 |
+
::-webkit-scrollbar-track {
|
52 |
+
background: transparent;
|
53 |
+
}
|
54 |
+
|
55 |
+
::-webkit-scrollbar-thumb {
|
56 |
+
background-color: rgba(255, 255, 255, 0.2);
|
57 |
+
border-radius: 3px;
|
58 |
+
}
|
src/styles/styles.css
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.card {
|
2 |
+
background-color: var(--color-panel);
|
3 |
+
padding: 1.5rem;
|
4 |
+
border-radius: 12px;
|
5 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
6 |
+
margin-bottom: 1rem;
|
7 |
+
}
|
8 |
+
|
9 |
+
.section-title {
|
10 |
+
font-size: 1.25rem;
|
11 |
+
font-weight: 600;
|
12 |
+
margin-bottom: 1rem;
|
13 |
+
color: var(--color-text);
|
14 |
+
}
|
15 |
+
|
16 |
+
.accent-text {
|
17 |
+
color: var(--color-accent);
|
18 |
+
}
|
19 |
+
|
20 |
+
.muted-text {
|
21 |
+
color: var(--color-muted);
|
22 |
+
}
|
23 |
+
|
src/vite-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="vite/client" />
|