feat: setup React/Vite frontend with Tyndale AI chat interface
Initialize complete frontend project structure with the following components: - React 18 + Vite development environment with TypeScript - Tailwind CSS for styling with custom animations - Radix UI components for accessible UI primitives - React Router for navigation between landing and chat pages - TanStack Query for efficient API data management Key features implemented: - Landing page with hero, features, and footer sections - Real-time chat interface with message history - Study/Trading mode toggle for different interaction types - Custom hooks for chat state management - API integration layer with backend service - Responsive design with scroll areas and card layouts Docker deployment configuration: - Multi-stage Dockerfile with Node.js build and Nginx production - Custom nginx configuration for SPA routing on port 3000 - Optimized production build process Development tools: - ESLint for code quality - TypeScript for type safety - PostCSS with Autoprefixer - Environment variable configuration with .env.example
This commit is contained in:
+177
@@ -0,0 +1,177 @@
|
||||
import type { ChatMode } from '@/types/chat'
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000'
|
||||
const USE_MOCK_DATA = true // Set to false when backend is ready
|
||||
|
||||
// Session management
|
||||
export const getChatSessionId = (): string | null => {
|
||||
return localStorage.getItem('tyndale_session_id')
|
||||
}
|
||||
|
||||
export const setChatSessionId = (sessionId: string): void => {
|
||||
localStorage.setItem('tyndale_session_id', sessionId)
|
||||
}
|
||||
|
||||
export const clearChatSessionId = (): void => {
|
||||
localStorage.removeItem('tyndale_session_id')
|
||||
}
|
||||
|
||||
// Mock responses for testing
|
||||
const MOCK_RESPONSES = {
|
||||
general: [
|
||||
"A **moving average** is one of the most fundamental technical indicators in trading. It smooths out price data by creating a constantly updated average price over a specific time period.\n\nThere are two main types:\n- **Simple Moving Average (SMA)**: The arithmetic mean of prices over a period\n- **Exponential Moving Average (EMA)**: Gives more weight to recent prices\n\nTraders use moving averages to identify trends, support/resistance levels, and generate trading signals when different MAs cross each other.",
|
||||
"**Algorithmic trading** uses computer programs to execute trades based on predefined criteria. The main advantages include:\n\n1. **Speed**: Algorithms can analyze market conditions and execute orders in milliseconds\n2. **Emotion-free**: Removes psychological biases from trading decisions\n3. **Backtesting**: Strategies can be tested on historical data before risking real capital\n4. **Consistency**: Executes trades exactly as programmed\n\nCommon strategies include market making, arbitrage, trend following, and mean reversion.",
|
||||
"**Risk management** is crucial in trading. Key principles include:\n\n- **Position sizing**: Never risk more than 1-2% of capital on a single trade\n- **Stop losses**: Set predetermined exit points to limit losses\n- **Diversification**: Spread risk across multiple assets or strategies\n- **Risk-reward ratio**: Aim for at least 2:1 reward-to-risk\n\nThe goal is to preserve capital during losing streaks while maximizing gains during winning periods.",
|
||||
"**Market volatility** refers to the rate and magnitude of price changes in a financial instrument. High volatility means larger price swings, while low volatility indicates stability.\n\nTraders measure volatility using:\n- **Standard deviation** of returns\n- **Average True Range (ATR)**\n- **Bollinger Bands**\n- **VIX index** (for equity markets)\n\nVolatility creates both opportunities and risks - more movement means more profit potential but also higher risk.",
|
||||
],
|
||||
strategy: [
|
||||
"When analyzing a **mean reversion strategy**, consider these key metrics:\n\n**Performance Metrics:**\n- Sharpe Ratio: Risk-adjusted returns (target > 1.5)\n- Maximum Drawdown: Largest peak-to-trough decline (keep < 20%)\n- Win Rate: Percentage of profitable trades\n- Profit Factor: Gross profit / Gross loss (target > 1.5)\n\n**Strategy-Specific:**\n- Mean reversion typically works best in range-bound markets\n- Watch for regime changes where markets shift from mean-reverting to trending\n- Consider transaction costs - they can significantly impact high-frequency mean reversion strategies",
|
||||
"**Backtesting results** should be interpreted carefully to avoid overfitting:\n\n**Red flags:**\n- Too many parameters (> 5-6 tunable variables)\n- Perfect or near-perfect equity curve\n- Performance degrades significantly on out-of-sample data\n- Very high Sharpe ratio (> 3) - often indicates curve fitting\n\n**Best practices:**\n- Use walk-forward analysis\n- Test on multiple market regimes\n- Include realistic transaction costs and slippage\n- Reserve 30-40% of data for out-of-sample testing\n\nRemember: Past performance does not guarantee future results.",
|
||||
"For **momentum strategies**, optimization should focus on:\n\n**Entry signals:**\n- Lookback period for momentum calculation (20-250 days typical)\n- Threshold for signal generation (% gain required)\n- Volume confirmation requirements\n\n**Risk management:**\n- Trailing stops to protect profits\n- Maximum holding period\n- Correlation filters to avoid overcrowding\n\n**Portfolio construction:**\n- Number of positions (5-20 for diversification)\n- Position sizing (equal weight vs volatility-adjusted)\n- Rebalancing frequency (weekly to monthly)\n\nMomentum works well in trending markets but can suffer during reversals.",
|
||||
"**Strategy robustness** can be evaluated through:\n\n1. **Parameter sensitivity**: Test nearby parameter values - robust strategies shouldn't degrade rapidly\n2. **Market regime analysis**: Performance across bull/bear/sideways markets\n3. **Asset class generalization**: Does it work on similar instruments?\n4. **Time frame stability**: Consistent performance across different time periods\n5. **Monte Carlo simulation**: Randomize trade sequences to assess statistical significance\n\nA robust strategy maintains profitability across reasonable parameter ranges and different market conditions.",
|
||||
],
|
||||
}
|
||||
|
||||
// Mock streaming function
|
||||
async function* mockChatStream(
|
||||
question: string,
|
||||
mode: ChatMode
|
||||
): AsyncGenerator<StreamEvent> {
|
||||
const sessionId = crypto.randomUUID()
|
||||
yield { type: 'session_id', data: sessionId }
|
||||
|
||||
// Select a random response based on mode
|
||||
const responses = MOCK_RESPONSES[mode]
|
||||
const response = responses[Math.floor(Math.random() * responses.length)]
|
||||
|
||||
// Simulate streaming by yielding words with delays
|
||||
const words = response.split(' ')
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
// Add space before word (except first word)
|
||||
const chunk = i === 0 ? words[i] : ' ' + words[i]
|
||||
yield { type: 'chunk', data: chunk }
|
||||
|
||||
// Random delay between 30-80ms to simulate typing
|
||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 50 + 30))
|
||||
}
|
||||
|
||||
yield { type: 'done', data: null }
|
||||
}
|
||||
|
||||
// SSE stream event types
|
||||
export type StreamEvent =
|
||||
| { type: 'chunk'; data: string }
|
||||
| { type: 'done'; data: null }
|
||||
| { type: 'error'; data: string }
|
||||
| { type: 'session_id'; data: string }
|
||||
|
||||
class ApiClient {
|
||||
private sessionId: string | null = null
|
||||
|
||||
getSessionId(): string | null {
|
||||
return this.sessionId || getChatSessionId()
|
||||
}
|
||||
|
||||
setSessionId(sessionId: string): void {
|
||||
this.sessionId = sessionId
|
||||
setChatSessionId(sessionId)
|
||||
}
|
||||
|
||||
clearSessionId(): void {
|
||||
this.sessionId = null
|
||||
clearChatSessionId()
|
||||
}
|
||||
|
||||
// Streaming chat endpoint (SSE)
|
||||
async *chatStream(
|
||||
question: string,
|
||||
mode: ChatMode,
|
||||
sessionId?: string
|
||||
): AsyncGenerator<StreamEvent> {
|
||||
// Use mock data if enabled
|
||||
if (USE_MOCK_DATA) {
|
||||
yield* mockChatStream(question, mode)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate or reuse session ID
|
||||
const actualSessionId = sessionId || this.getSessionId() || crypto.randomUUID()
|
||||
this.setSessionId(actualSessionId)
|
||||
|
||||
// Yield session ID first
|
||||
yield { type: 'session_id', data: actualSessionId }
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/chat/stream`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
question,
|
||||
mode,
|
||||
sessionId: actualSessionId,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
yield { type: 'error', data: `HTTP error! status: ${response.status}` }
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
yield { type: 'error', data: 'Response body is null' }
|
||||
return
|
||||
}
|
||||
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || ''
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6)
|
||||
|
||||
if (data === '[DONE]') {
|
||||
yield { type: 'done', data: null }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
if (parsed.type === 'error') {
|
||||
yield { type: 'error', data: parsed.message }
|
||||
} else if (parsed.content) {
|
||||
yield { type: 'chunk', data: parsed.content }
|
||||
} else {
|
||||
// Raw string chunk
|
||||
yield { type: 'chunk', data: data }
|
||||
}
|
||||
} catch {
|
||||
// Raw string chunk (not JSON)
|
||||
yield { type: 'chunk', data: data }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: 'done', data: null }
|
||||
} catch (error) {
|
||||
yield {
|
||||
type: 'error',
|
||||
data: error instanceof Error ? error.message : 'Network error',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient()
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user