diff --git a/frontend/src/App.css b/frontend/src/App.css index b0e8286..ff8bcd6 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -409,6 +409,41 @@ max-height: none; overflow: visible; } + + .insight-panel { + overflow: visible !important; + } + + .ai-analyst-content { + overflow: visible !important; + } + + .insight-cta { + overflow: visible !important; + } + + .ai-param-selectors { + overflow: visible !important; + position: relative; + z-index: 20; + } + + .glass-dropdown-menu { + top: auto !important; + bottom: calc(100% + 8px) !important; + left: 0 !important; + z-index: 1000 !important; + animation: none !important; + } + + .insight-panel .glass-dropdown-menu { + top: auto !important; + bottom: calc(100% + 8px) !important; + left: 0 !important; + transform: none !important; + z-index: 9999 !important; + animation: none !important; + } } @media (max-width: 768px) { diff --git a/frontend/src/components/AIAnalyst.tsx b/frontend/src/components/AIAnalyst.tsx index 8839c5c..fef1184 100644 --- a/frontend/src/components/AIAnalyst.tsx +++ b/frontend/src/components/AIAnalyst.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; -import Logo from './Logo'; +import { useState, useEffect } from "react"; +import Logo from "./Logo"; interface AIInsight { ticker: string; @@ -42,19 +42,23 @@ export default function AIAnalyst({ strategyLocked = false, onLockInStrategy, }: AIAnalystProps) { - const [targetPriceInput, setTargetPriceInput] = useState(''); - const [stopLossInput, setStopLossInput] = useState(''); - const [activeTab, setActiveTab] = useState<'analysis' | 'history'>('analysis'); - const [selectedHistoryItem, setSelectedHistoryItem] = useState(null); - const [tradingStyle, setTradingStyle] = useState('swing_trading'); - const [riskTolerance, setRiskTolerance] = useState('moderate'); + const [targetPriceInput, setTargetPriceInput] = useState(""); + const [stopLossInput, setStopLossInput] = useState(""); + const [activeTab, setActiveTab] = useState<"analysis" | "history">( + "analysis", + ); + const [selectedHistoryItem, setSelectedHistoryItem] = useState( + null, + ); + const [tradingStyle, setTradingStyle] = useState("swing_trading"); + const [riskTolerance, setRiskTolerance] = useState("moderate"); const [isStyleOpen, setIsStyleOpen] = useState(false); const [isRiskOpen, setIsRiskOpen] = useState(false); // Auto-switch to analysis tab when loading starts useEffect(() => { if (loadingInsight) { - setActiveTab('analysis'); + setActiveTab("analysis"); setSelectedHistoryItem(null); } }, [loadingInsight]); @@ -67,13 +71,17 @@ export default function AIAnalyst({ useEffect(() => { if (insight && currentPrice != null) { const isBullish = insight.bullishProbability >= 50; - const defaultTarget = isBullish ? currentPrice * 1.05 : currentPrice * 0.95; - const defaultStopLoss = isBullish ? currentPrice * 0.98 : currentPrice * 1.02; + const defaultTarget = isBullish + ? currentPrice * 1.05 + : currentPrice * 0.95; + const defaultStopLoss = isBullish + ? currentPrice * 0.98 + : currentPrice * 1.02; setTargetPriceInput(defaultTarget.toFixed(2)); setStopLossInput(defaultStopLoss.toFixed(2)); } else if (!insight) { - setTargetPriceInput(''); - setStopLossInput(''); + setTargetPriceInput(""); + setStopLossInput(""); } }, [insight, currentPrice]); @@ -82,16 +90,22 @@ export default function AIAnalyst({ // Handle **bold** and *italic* (in that order, bold first) const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*)/g); return parts.map((part, j) => { - if (part.startsWith('**') && part.endsWith('**')) { + if (part.startsWith("**") && part.endsWith("**")) { return ( - + {part.slice(2, -2)} ); } - if (part.startsWith('*') && part.endsWith('*')) { + if (part.startsWith("*") && part.endsWith("*")) { return ( - + {part.slice(1, -1)} ); @@ -103,20 +117,37 @@ export default function AIAnalyst({ // Full markdown formatter — handles headings, bullets, bold, italic const formatReason = (text: string) => { if (!text) return null; - const lines = text.split('\n'); + const lines = text.split("\n"); const elements: React.ReactNode[] = []; let bulletBuffer: string[] = []; const flushBullets = (keyBase: string) => { if (bulletBuffer.length === 0) return; elements.push( -
    +
      {bulletBuffer.map((item, bi) => ( -
    • +
    • {parseInline(item, `li-${keyBase}-${bi}`)}
    • ))} -
    +
, ); bulletBuffer = []; }; @@ -129,40 +160,69 @@ export default function AIAnalyst({ } // ### Heading 3 - if (trimmed.startsWith('### ')) { + if (trimmed.startsWith("### ")) { flushBullets(`${i}`); elements.push( -

+

{trimmed.slice(4)} -

+ , ); return; } // ## Heading 2 - if (trimmed.startsWith('## ')) { + if (trimmed.startsWith("## ")) { flushBullets(`${i}`); elements.push( -

+

{parseInline(trimmed.slice(3), `h2-${i}`)} -

+ , ); return; } // # Heading 1 - if (trimmed.startsWith('# ')) { + if (trimmed.startsWith("# ")) { flushBullets(`${i}`); elements.push( -

+

{parseInline(trimmed.slice(2), `h1-${i}`)} -

+ , ); return; } // * bullet or - bullet - if (trimmed.startsWith('* ') || trimmed.startsWith('- ')) { + if (trimmed.startsWith("* ") || trimmed.startsWith("- ")) { bulletBuffer.push(trimmed.slice(2)); return; } @@ -172,11 +232,11 @@ export default function AIAnalyst({ elements.push(

{parseInline(trimmed, `p-${i}`)} -

+

, ); }); - flushBullets('end'); + flushBullets("end"); return elements; }; @@ -184,16 +244,22 @@ export default function AIAnalyst({
- + - -
-
+
+
+
QuantIQ AI Analyst
-
- -
-
- {activeTab === 'analysis' ? ( +
+ {activeTab === "analysis" ? ( <> {!insight && !loadingInsight && ( -
+

- Analyze indicators, watchlists, alerts, and model prediction in a single ReAct workflow. + Analyze indicators, watchlists, alerts, and model prediction + in a single ReAct workflow.

- + {/* Style & Risk selectors */} {/* Style & Risk selectors */} -
+
{/* Trading Style Custom Select */} -
- -
+ +
{ setIsStyleOpen(!isStyleOpen); setIsRiskOpen(false); }} className="premium-select" - style={{ - width: '100%', - padding: '10px 14px', - background: 'rgba(255,255,255,0.02)', - border: '1px solid var(--border-glass)', - borderRadius: '10px', - color: 'var(--text-primary)', - cursor: 'pointer', - fontSize: '12px', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - boxSizing: 'border-box', - minHeight: '38px' + style={{ + width: "100%", + padding: "10px 14px", + background: "rgba(255,255,255,0.02)", + border: "1px solid var(--border-glass)", + borderRadius: "10px", + color: "var(--text-primary)", + cursor: "pointer", + fontSize: "12px", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + boxSizing: "border-box", + minHeight: "38px", }} > - {tradingStyle === 'day_trading' && 'Day Trading'} - {tradingStyle === 'swing_trading' && 'Swing Trading'} - {tradingStyle === 'investing' && 'Long-term Investing'} + {tradingStyle === "day_trading" && "Day Trading"} + {tradingStyle === "swing_trading" && "Swing Trading"} + {tradingStyle === "investing" && "Long-term Investing"}
{isStyleOpen && ( -
-
{ - setTradingStyle('day_trading'); + setTradingStyle("day_trading"); setIsStyleOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: tradingStyle === 'day_trading' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: tradingStyle === 'day_trading' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + tradingStyle === "day_trading" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + tradingStyle === "day_trading" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Day Trading
-
{ - setTradingStyle('swing_trading'); + setTradingStyle("swing_trading"); setIsStyleOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: tradingStyle === 'swing_trading' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: tradingStyle === 'swing_trading' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + tradingStyle === "swing_trading" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + tradingStyle === "swing_trading" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Swing Trading
-
{ - setTradingStyle('investing'); + setTradingStyle("investing"); setIsStyleOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: tradingStyle === 'investing' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: tradingStyle === 'investing' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + tradingStyle === "investing" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + tradingStyle === "investing" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Long-term Investing @@ -378,109 +537,145 @@ export default function AIAnalyst({
{/* Risk Profile Custom Select */} -
- -
+ +
{ setIsRiskOpen(!isRiskOpen); setIsStyleOpen(false); }} className="premium-select" - style={{ - width: '100%', - padding: '10px 14px', - background: 'rgba(255,255,255,0.02)', - border: '1px solid var(--border-glass)', - borderRadius: '10px', - color: 'var(--text-primary)', - cursor: 'pointer', - fontSize: '12px', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - boxSizing: 'border-box', - minHeight: '38px' + style={{ + width: "100%", + padding: "10px 14px", + background: "rgba(255,255,255,0.02)", + border: "1px solid var(--border-glass)", + borderRadius: "10px", + color: "var(--text-primary)", + cursor: "pointer", + fontSize: "12px", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + boxSizing: "border-box", + minHeight: "38px", }} > - {riskTolerance === 'conservative' && 'Conservative'} - {riskTolerance === 'moderate' && 'Moderate'} - {riskTolerance === 'aggressive' && 'Aggressive'} + {riskTolerance === "conservative" && "Conservative"} + {riskTolerance === "moderate" && "Moderate"} + {riskTolerance === "aggressive" && "Aggressive"}
{isRiskOpen && ( -
-
{ - setRiskTolerance('conservative'); + setRiskTolerance("conservative"); setIsRiskOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: riskTolerance === 'conservative' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: riskTolerance === 'conservative' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + riskTolerance === "conservative" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + riskTolerance === "conservative" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Conservative
-
{ - setRiskTolerance('moderate'); + setRiskTolerance("moderate"); setIsRiskOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: riskTolerance === 'moderate' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: riskTolerance === 'moderate' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + riskTolerance === "moderate" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + riskTolerance === "moderate" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Moderate
-
{ - setRiskTolerance('aggressive'); + setRiskTolerance("aggressive"); setIsRiskOpen(false); }} style={{ - padding: '10px 12px', - borderRadius: '8px', - cursor: 'pointer', - color: riskTolerance === 'aggressive' ? 'var(--neon-cyan)' : 'var(--text-secondary)', - background: riskTolerance === 'aggressive' ? 'rgba(0, 242, 254, 0.06)' : 'none', - fontSize: '12px', - transition: 'all 0.15s ease' + padding: "10px 12px", + borderRadius: "8px", + cursor: "pointer", + color: + riskTolerance === "aggressive" + ? "var(--neon-cyan)" + : "var(--text-secondary)", + background: + riskTolerance === "aggressive" + ? "rgba(0, 242, 254, 0.06)" + : "none", + fontSize: "12px", + transition: "all 0.15s ease", }} > Aggressive @@ -490,7 +685,11 @@ export default function AIAnalyst({
-
@@ -499,24 +698,50 @@ export default function AIAnalyst({ {loadingInsight && (
-
- +
+ QuantIQ AI is analyzing {activeTicker}... - - Running ReAct workflow · Scanning indicators · Generating strategy + + Running ReAct workflow · Scanning indicators · Generating + strategy
)} {insightError && ( -
- {insightError} - @@ -525,19 +750,35 @@ export default function AIAnalyst({ {insight && !loadingInsight && (
-
+
{renderGauge(insight.bullishProbability)} -
-
= 50 ? 'tag-bullish' : 'tag-bearish'}`}> - {insight.bullishProbability >= 50 ? 'BULLISH BIAS' : 'BEARISH BIAS'} +
= 50 ? "tag-bullish" : "tag-bearish"}`} + > + {insight.bullishProbability >= 50 + ? "BULLISH BIAS" + : "BEARISH BIAS"}
{formatReason(insight.reason)} @@ -546,24 +787,85 @@ export default function AIAnalyst({ {/* Lock In Strategy — captures the trader's own entry/target/stop-loss, logged as user_entry/user_target/user_stop_loss in strategy_logs and compared server-side against the backend's own ATR-14 AI levels. */} -
+
{strategyLocked ? ( -
+
✓ Strategy Locked
) : ( <> -
-
-