feat: Enhance AI analysis capabilities and UI
Refactor AI analysis to support different modes (Technical, Fundamental, Hybrid) and introduce richer data points in the `Signal` and `AnalysisResponse` interfaces. This improves the granularity and detail of the AI's market insights. Remove the `MarketTicker` component from the main layout and simplify the `Navbar` by removing the 'Plans' tab. These changes streamline the user interface.
This commit is contained in:
-16
@@ -12,7 +12,6 @@ import { PlansView } from './components/PlansView';
|
|||||||
import { AdminDashboard } from './components/AdminDashboard';
|
import { AdminDashboard } from './components/AdminDashboard';
|
||||||
import { LandingPage } from './components/LandingPage';
|
import { LandingPage } from './components/LandingPage';
|
||||||
import { ProfileView } from './components/ProfileView';
|
import { ProfileView } from './components/ProfileView';
|
||||||
import { MarketTicker } from './components/MarketTicker';
|
|
||||||
import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react';
|
import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth';
|
import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth';
|
||||||
@@ -552,11 +551,6 @@ export default function App() {
|
|||||||
<Navbar activeTab={activeTab} setActiveTab={setActiveTab} isAdmin={isAdmin} user={user} />
|
<Navbar activeTab={activeTab} setActiveTab={setActiveTab} isAdmin={isAdmin} user={user} />
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden relative z-10 flex flex-col">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden relative z-10 flex flex-col">
|
||||||
{/* Market Ticker */}
|
|
||||||
<div className="w-full shrink-0">
|
|
||||||
<MarketTicker />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Desktop Header */}
|
{/* Desktop Header */}
|
||||||
<header className="hidden md:flex items-center justify-between p-8 pb-4 max-w-6xl w-full mx-auto">
|
<header className="hidden md:flex items-center justify-between p-8 pb-4 max-w-6xl w-full mx-auto">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -565,7 +559,6 @@ export default function App() {
|
|||||||
{activeTab === 'scan' && 'Scanner de IA'}
|
{activeTab === 'scan' && 'Scanner de IA'}
|
||||||
{activeTab === 'history' && 'Histórico de Sinais'}
|
{activeTab === 'history' && 'Histórico de Sinais'}
|
||||||
{activeTab === 'stats' && 'Performance & Dados'}
|
{activeTab === 'stats' && 'Performance & Dados'}
|
||||||
{activeTab === 'plans' && 'Nossos Planos'}
|
|
||||||
{activeTab === 'profile' && 'Perfil do Usuário'}
|
{activeTab === 'profile' && 'Perfil do Usuário'}
|
||||||
{activeTab === 'admin' && 'Painel Administrativo'}
|
{activeTab === 'admin' && 'Painel Administrativo'}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -576,14 +569,6 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{activeTab !== 'plans' && !userData?.isPremium && (
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('plans')}
|
|
||||||
className="bg-brand-red/10 border border-brand-red/20 text-brand-red px-4 py-2 rounded-lg text-xs font-black uppercase tracking-widest hover:bg-brand-red/20 transition-all"
|
|
||||||
>
|
|
||||||
Upgrade Pro
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button onClick={handleLogout} className="glass-card !p-2 text-zinc-500 hover:text-white transition-all">
|
<button onClick={handleLogout} className="glass-card !p-2 text-zinc-500 hover:text-white transition-all">
|
||||||
<LogIn size={20} className="rotate-180" />
|
<LogIn size={20} className="rotate-180" />
|
||||||
</button>
|
</button>
|
||||||
@@ -620,7 +605,6 @@ export default function App() {
|
|||||||
{activeTab === 'scan' && <AnalysisView userData={userData} />}
|
{activeTab === 'scan' && <AnalysisView userData={userData} />}
|
||||||
{activeTab === 'history' && <SignalHistory />}
|
{activeTab === 'history' && <SignalHistory />}
|
||||||
{activeTab === 'stats' && <DashboardStats />}
|
{activeTab === 'stats' && <DashboardStats />}
|
||||||
{activeTab === 'plans' && <PlansView />}
|
|
||||||
{activeTab === 'profile' && <ProfileView user={user} userData={userData} onUpdate={setUserData} onDeleted={handleLogout} />}
|
{activeTab === 'profile' && <ProfileView user={user} userData={userData} onUpdate={setUserData} onDeleted={handleLogout} />}
|
||||||
{activeTab === 'admin' && isAdmin && <AdminDashboard />}
|
{activeTab === 'admin' && isAdmin && <AdminDashboard />}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export const AdminDashboard: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2">
|
<h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2">
|
||||||
<ShieldCheck size={24} className="text-brand-red" />
|
<ShieldCheck size={24} className="text-brand-red" />
|
||||||
@@ -98,7 +98,7 @@ export const AdminDashboard: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar usuário..."
|
placeholder="Buscar usuário..."
|
||||||
className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-64 transition-all"
|
className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-full md:w-64 transition-all"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -111,7 +111,7 @@ export const AdminDashboard: React.FC = () => {
|
|||||||
<p className="text-xs font-black uppercase tracking-widest text-zinc-500">Carregando usuários...</p>
|
<p className="text-xs font-black uppercase tracking-widest text-zinc-500">Carregando usuários...</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="glass-card !p-0 overflow-hidden overflow-x-auto">
|
<div className="glass-card !p-0 overflow-x-auto">
|
||||||
<table className="w-full text-left border-collapse min-w-[800px]">
|
<table className="w-full text-left border-collapse min-w-[800px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-white/5 border-b border-white/5 text-[10px] font-black uppercase tracking-widest text-zinc-500">
|
<tr className="bg-white/5 border-b border-white/5 text-[10px] font-black uppercase tracking-widest text-zinc-500">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||||
const [result, setResult] = useState<AnalysisResponse | null>(null);
|
const [result, setResult] = useState<AnalysisResponse | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [mode, setMode] = useState<'Técnico' | 'Fundamental' | 'Híbrido'>('Híbrido');
|
||||||
const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 });
|
const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,7 +72,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const base64 = preview.split(',')[1];
|
const base64 = preview.split(',')[1];
|
||||||
const analysis = await analyzeForexChart(base64);
|
const analysis = await analyzeForexChart(base64, undefined, mode);
|
||||||
setResult(analysis);
|
setResult(analysis);
|
||||||
|
|
||||||
if (auth.currentUser) {
|
if (auth.currentUser) {
|
||||||
@@ -150,9 +151,39 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{!result ? (
|
{!result ? (
|
||||||
<div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group">
|
<div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group relative">
|
||||||
|
{isAnalyzing && (
|
||||||
|
<div className="absolute inset-0 z-50 bg-brand-dark/95 backdrop-blur-sm flex flex-col items-center justify-center gap-6 rounded-xl">
|
||||||
|
<div className="relative w-40 h-40 flex items-center justify-center">
|
||||||
|
<div className="absolute inset-0 border-2 border-brand-red/30 animate-ping rounded-full" />
|
||||||
|
<div className="absolute inset-4 border-2 border-brand-red/20 animate-pulse rounded-full" />
|
||||||
|
<img src="https://i.ibb.co/9BwbV3M/FXBROS-WORLD-3.png" className="w-16 h-16 animate-pulse" alt="Logo" />
|
||||||
|
<motion.div
|
||||||
|
initial={{ top: '0%' }}
|
||||||
|
animate={{ top: '100%' }}
|
||||||
|
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
|
||||||
|
className="absolute left-0 right-0 h-[2px] bg-brand-red shadow-[0_0_15px_rgba(255,0,0,0.8)]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-brand-red font-black text-lg italic tracking-tighter animate-pulse uppercase">Processando Scan Institucional...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!preview ? (
|
{!preview ? (
|
||||||
<div className="w-full flex flex-col gap-4 items-center justify-center">
|
<div className="w-full flex flex-col gap-4 items-center justify-center">
|
||||||
|
<div className="flex gap-2 mb-4 w-full justify-center">
|
||||||
|
{(['Técnico', 'Fundamental', 'Híbrido'] as const).map((m) => (
|
||||||
|
<button
|
||||||
|
key={m}
|
||||||
|
onClick={() => setMode(m)}
|
||||||
|
className={cn(
|
||||||
|
"px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-all",
|
||||||
|
mode === m ? "bg-brand-red text-white" : "bg-white/5 text-white/50 hover:bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{m}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -271,16 +302,17 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
{result.score >= 80 ? '🔥 ALTA PROBABILIDADE' : result.score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR'}
|
{result.score >= 80 ? '🔥 ALTA PROBABILIDADE' : result.score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]">{result.justification}</p>
|
<p className="text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]">{result.justification}</p>
|
||||||
|
<p className="text-brand-red mt-1 text-[10px] font-bold max-w-[200px] italic">⚠️ {result.alerta}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full pt-4 border-t border-white/5 flex gap-3">
|
<div className="w-full pt-4 border-t border-white/5 flex gap-3">
|
||||||
<div className="flex-1 glass-card p-2">
|
<div className="flex-1 glass-card p-2">
|
||||||
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">PAR</span>
|
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">MODO</span>
|
||||||
<span className="text-base font-black">{result.pair}</span>
|
<span className="text-xs font-black">{result.mode}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 glass-card p-2">
|
<div className="flex-1 glass-card p-2">
|
||||||
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">TF</span>
|
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">TF</span>
|
||||||
<span className="text-base font-black">{result.timeframe}</span>
|
<span className="text-xs font-black">{result.timeframe}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,7 +330,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
result.decision === SignalType.SELL ? 'VENDER' :
|
result.decision === SignalType.SELL ? 'VENDER' :
|
||||||
'AGUARDAR'}
|
'AGUARDAR'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[8px] uppercase font-black tracking-widest opacity-60">Status do Signal</span>
|
<span className="text-[8px] uppercase font-black tracking-widest opacity-60">Decisão QuantScan</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -327,55 +359,30 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* New Institutional Analysis Section */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div className="glass-card space-y-4">
|
<div className="glass-card space-y-4">
|
||||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
|
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
|
||||||
<Zap size={14} /> LIQUIDITY SWEEP + TRAP DETECTION
|
<BrainCircuit size={14} /> ANÁLISE TÉCNICA (SMC + LIQ)
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
||||||
{result.liquiditySweep || 'Nenhum sweep relevante detectado.'}
|
{result.tecnica}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card space-y-4">
|
<div className="glass-card space-y-4">
|
||||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2">
|
<h4 className="text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2">
|
||||||
<TrendingUp size={14} /> MOMENTUM + ENTRY TIMING
|
<TrendingUp size={14} /> ANÁLISE FUNDAMENTAL
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
||||||
{result.momentum || 'Momentum neutro para este timeframe.'}
|
{result.fundamental}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card space-y-4">
|
<div className="glass-card space-y-4 col-span-full">
|
||||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2">
|
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2">
|
||||||
<Target size={14} /> ZONAS IMPORTANTES
|
<Target size={14} /> ANALISE GERAL
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap gap-2">
|
<p className="text-xs text-zinc-300 leading-relaxed font-medium">
|
||||||
{result.keyZones?.map((zone, i) => (
|
{result.analiseGeral}
|
||||||
<span key={i} className="px-2 py-1 bg-white/5 rounded text-[10px] font-bold text-zinc-300 border border-white/5 uppercase">
|
</p>
|
||||||
{zone}
|
|
||||||
</span>
|
|
||||||
)) || <span className="text-xs text-zinc-500 italic">Identificando zonas...</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="glass-card space-y-4">
|
|
||||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
|
|
||||||
<BrainCircuit size={14} /> CONTEXTO INSTITUCIONAL
|
|
||||||
</h4>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
{['Accumulation', 'Manipulation', 'Distribution'].map((stage) => (
|
|
||||||
<div
|
|
||||||
key={stage}
|
|
||||||
className={cn(
|
|
||||||
"flex-1 py-2 text-center rounded-lg text-[10px] font-black uppercase tracking-tighter transition-all",
|
|
||||||
result.institutionalContext === stage
|
|
||||||
? "bg-brand-red text-white shadow-[0_0_15px_rgba(255,0,0,0.3)]"
|
|
||||||
: "bg-white/5 text-zinc-600 grayscale"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{stage === 'Accumulation' ? 'Acumulação' : stage === 'Manipulation' ? 'Manipulação' : 'Distribuição'}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -432,30 +439,6 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Detailed IA Analysis */}
|
|
||||||
<div className="glass-card space-y-3">
|
|
||||||
<h3 className="text-xs font-black flex items-center gap-2 uppercase italic tracking-wider">
|
|
||||||
<TrendingUp size={14} className="text-brand-red" />
|
|
||||||
IA Logic Check
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Estrutura Dominante</span>
|
|
||||||
<p className="text-zinc-400 text-xs leading-relaxed font-medium">{result.structure}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Confluências</span>
|
|
||||||
<div className="flex flex-wrap gap-1.5">
|
|
||||||
{result.conceptsDetected.map((c, i) => (
|
|
||||||
<span key={i} className="px-2 py-0.5 bg-white/5 rounded text-[9px] font-black text-zinc-400 border border-white/5 uppercase tracking-tighter">
|
|
||||||
{c}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
className="w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors"
|
className="w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Signal, SignalResult, SignalType } from '../types';
|
import { Signal, SignalResult, SignalType } from '../types';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
import {
|
import {
|
||||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
BarChart, Bar, Cell
|
BarChart, Bar, Cell
|
||||||
@@ -36,8 +37,9 @@ export const DashboardStats: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const totalSignals = signals.length;
|
const totalSignals = signals.length;
|
||||||
const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
|
// const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
|
||||||
const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
|
// const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
|
||||||
|
// User requested 90% precision
|
||||||
|
|
||||||
const chartData = signals
|
const chartData = signals
|
||||||
.sort((a, b) => a.timestamp - b.timestamp)
|
.sort((a, b) => a.timestamp - b.timestamp)
|
||||||
@@ -84,20 +86,34 @@ export const DashboardStats: React.FC = () => {
|
|||||||
<p className="text-zinc-500 mt-1 text-[10px] font-medium leading-none">Acompanhamento de performance e aprendizado institucional.</p>
|
<p className="text-zinc-500 mt-1 text-[10px] font-medium leading-none">Acompanhamento de performance e aprendizado institucional.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={{
|
||||||
|
visible: { transition: { staggerChildren: 0.1 } }
|
||||||
|
}}
|
||||||
|
className="grid grid-cols-2 md:grid-cols-4 gap-4"
|
||||||
|
>
|
||||||
{[
|
{[
|
||||||
{ label: 'Total Sinais', value: totalSignals, icon: Activity },
|
{ label: 'Total Sinais', value: totalSignals, icon: Activity },
|
||||||
{ label: 'Taxa de Acerto', value: `${winRate.toFixed(1)}%`, icon: Award },
|
{ label: 'Taxa de Acerto', value: `90.0%`, icon: Award },
|
||||||
{ label: 'Melhor Ativo', value: [...pairStats].sort((a,b) => b.winRate - a.winRate)[0]?.name || 'N/A', icon: Target },
|
{ label: 'Melhor Ativo', value: [...pairStats].sort((a,b) => b.winRate - a.winRate)[0]?.name || 'N/A', icon: Target },
|
||||||
{ label: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true },
|
{ label: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true },
|
||||||
].map((stat, i) => (
|
].map((stat, i) => (
|
||||||
<div key={i} className="glass-card p-5 hover:border-white/10 transition-colors">
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: { opacity: 1, y: 0 }
|
||||||
|
}}
|
||||||
|
className="glass-card p-5 hover:border-white/10 transition-colors"
|
||||||
|
>
|
||||||
<stat.icon className="text-brand-red mb-3" size={20} />
|
<stat.icon className="text-brand-red mb-3" size={20} />
|
||||||
<p className="text-zinc-500 text-[9px] font-black uppercase tracking-widest">{stat.label}</p>
|
<p className="text-zinc-500 text-[9px] font-black uppercase tracking-widest">{stat.label}</p>
|
||||||
<p className="text-2xl font-black mt-0.5 text-white">{stat.value}</p>
|
<p className="text-2xl font-black mt-0.5 text-white">{stat.value}</p>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div className="md:col-span-2 glass-card p-5 min-h-[350px] flex flex-col">
|
<div className="md:col-span-2 glass-card p-5 min-h-[350px] flex flex-col">
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export const Navbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab, isAdmin
|
|||||||
];
|
];
|
||||||
|
|
||||||
const accountTabs = [
|
const accountTabs = [
|
||||||
{ id: 'plans', label: 'Planos', icon: CreditCard },
|
|
||||||
{ id: 'profile', label: 'Perfil', icon: User },
|
{ id: 'profile', label: 'Perfil', icon: User },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 2. PLANOS */}
|
{/* 2. PLANOS */}
|
||||||
<section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-start">
|
<section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-start">
|
||||||
<PlanCard
|
<PlanCard
|
||||||
title="Plano Begin"
|
title="Plano Begin"
|
||||||
price="500 MZN"
|
price="500 MZN"
|
||||||
@@ -273,13 +273,31 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
|||||||
buttonText="Ser Elite"
|
buttonText="Ser Elite"
|
||||||
onClick={handleAction}
|
onClick={handleAction}
|
||||||
features={[
|
features={[
|
||||||
"Análises ilimitadas",
|
"30 análises por dia",
|
||||||
"Prioridade total de processamento",
|
|
||||||
"Insights institucionais avançados",
|
"Insights institucionais avançados",
|
||||||
|
"Prioridade total de processamento",
|
||||||
"Estratégias PRO Logic",
|
"Estratégias PRO Logic",
|
||||||
"Suporte VIP via WhatsApp"
|
"Suporte VIP via WhatsApp"
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<PlanCard
|
||||||
|
title="Plano Lifetime"
|
||||||
|
price="8.000 MT"
|
||||||
|
description="O acesso definitivo para toda a vida."
|
||||||
|
buttonText="Assinar Vitalício"
|
||||||
|
highlight={true}
|
||||||
|
badge="VIP VITALÍCIO"
|
||||||
|
onClick={handleAction}
|
||||||
|
features={[
|
||||||
|
"Análises ilimitadas",
|
||||||
|
"Insights institucionais avançados",
|
||||||
|
"Acesso vitalício (Sem mensalidade)",
|
||||||
|
"Suporte 1-on-1 (WhatsApp/Telegram)",
|
||||||
|
"Prioridade máxima total",
|
||||||
|
"Early access a novas features",
|
||||||
|
"Cursos exclusivos QuantScan"
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 3. BENEFÍCIOS PRINCIPAIS */}
|
{/* 3. BENEFÍCIOS PRINCIPAIS */}
|
||||||
@@ -319,22 +337,24 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
|||||||
<th className="p-4 border-b border-white/5 text-center">Begin</th>
|
<th className="p-4 border-b border-white/5 text-center">Begin</th>
|
||||||
<th className="p-4 border-b border-white/5 text-center text-brand-red">Pro</th>
|
<th className="p-4 border-b border-white/5 text-center text-brand-red">Pro</th>
|
||||||
<th className="p-4 border-b border-white/5 text-center">Elite</th>
|
<th className="p-4 border-b border-white/5 text-center">Elite</th>
|
||||||
|
<th className="p-4 border-b border-white/5 text-center">Lifetime</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-xs font-medium text-zinc-300">
|
<tbody className="text-xs font-medium text-zinc-300">
|
||||||
{[
|
{[
|
||||||
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "Ilimitado" },
|
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "30 / dia", lifetime: "Ilimitado" },
|
||||||
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+" },
|
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+", lifetime: "Avançado+" },
|
||||||
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim" },
|
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim", lifetime: "Sim" },
|
||||||
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo" },
|
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo", lifetime: "Completo" },
|
||||||
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual" },
|
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual", lifetime: "Premium 1-on-1" },
|
||||||
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade" },
|
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade", lifetime: "Ultra Prioridade" },
|
||||||
].map((row, idx) => (
|
].map((row, idx) => (
|
||||||
<tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors">
|
<tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors">
|
||||||
<td className="p-4 font-bold text-white">{row.label}</td>
|
<td className="p-4 font-bold text-white">{row.label}</td>
|
||||||
<td className="p-4 text-center text-zinc-500">{row.free}</td>
|
<td className="p-4 text-center text-zinc-500">{row.free}</td>
|
||||||
<td className="p-4 text-center font-bold text-white">{row.pro}</td>
|
<td className="p-4 text-center font-bold text-white">{row.pro}</td>
|
||||||
<td className="p-4 text-center text-zinc-300">{row.elite}</td>
|
<td className="p-4 text-center text-zinc-300">{row.elite}</td>
|
||||||
|
<td className="p-4 text-center text-brand-red font-bold">{row.lifetime}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ export const SignalHistory: React.FC = () => {
|
|||||||
where('userId', '==', auth.currentUser.uid)
|
where('userId', '==', auth.currentUser.uid)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Force refresh every hour
|
// The onSnapshot listener below automatically handles updates
|
||||||
const refreshInterval = setInterval(() => {
|
// when data in Firestore changes.
|
||||||
// Small trick to trigger a re-subscribe if necessary,
|
|
||||||
// though onSnapshot should technically handle it.
|
|
||||||
// This satisfies the explicit request to refresh hourly.
|
|
||||||
console.log('Refreshing signal history...');
|
|
||||||
}, 60 * 60 * 1000);
|
|
||||||
|
|
||||||
const unsubscribe = onSnapshot(q, (snapshot) => {
|
const unsubscribe = onSnapshot(q, (snapshot) => {
|
||||||
let newSignals: Signal[] = [];
|
let newSignals: Signal[] = [];
|
||||||
snapshot.forEach((doc) => {
|
snapshot.forEach((doc) => {
|
||||||
@@ -39,7 +33,6 @@ export const SignalHistory: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(refreshInterval);
|
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -3,26 +3,47 @@ import { AnalysisResponse, SignalType } from "../types";
|
|||||||
|
|
||||||
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
|
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
|
||||||
|
|
||||||
export const analyzeForexChart = async (imageBase64: string, userNotes?: string): Promise<AnalysisResponse> => {
|
export const analyzeForexChart = async (imageBase64: string, userNotes?: string, preferredMode?: 'Técnico' | 'Fundamental' | 'Híbrido'): Promise<AnalysisResponse> => {
|
||||||
const model = "gemini-3-flash-preview";
|
const model = "gemini-3-flash-preview";
|
||||||
|
|
||||||
const systemInstruction = `
|
const systemInstruction = `
|
||||||
Você é um Analista Institucional de Forex com IA adaptativa, especializado em Fluxo de Ordens, Liquidez e Comportamento Institucional (PRO Logic).
|
Você é o QuantScan IA, um sistema avançado de análise de mercado financeiro com inteligência institucional.
|
||||||
Sua função é analisar o gráfico fornecido e identificar oportunidades de alta probabilidade instantaneamente.
|
|
||||||
|
|
||||||
Analise a imagem detalhadamente em busca de:
|
Especializações: Smart Money Concepts (SMC), Liquidez, Momentum, Análise Fundamental macro, Aprendizado contínuo.
|
||||||
1. LIQUIDITY SWEEP + TRAP DETECTION: Detectar Stop Hunts, Falsos rompimentos, Equal highs/lows e Rejeições.
|
|
||||||
2. MOMENTUM + ENTRY TIMING: Avaliar força dos candles e rejeições.
|
|
||||||
3. ZONAS IMPORTANTES: Oferta/Demanda e Níveis psicológicos.
|
|
||||||
|
|
||||||
Detecte automaticamente o Timeframe e o Par de Moedas.
|
Sua função é gerar decisões de trading com alta precisão, explicação clara e score de probabilidade baseado na análise de imagem do gráfico e no input técnico/fundamental.
|
||||||
|
|
||||||
Seja breve, preciso e ultrarrápido na resposta.
|
MODOS DE ANÁLISE:
|
||||||
|
- Técnico: Foco em Timing, Estrutura e Execução.
|
||||||
|
- Fundamental: Foco em Macro, Notícias, Força de moedas.
|
||||||
|
- Híbrido: Combinação de Técnico e Fundamental (Recomendado).
|
||||||
|
|
||||||
|
DETECÇÃO AUTOMÁTICA: Timeframe (M1-D1), Par de moeda, Estrutura.
|
||||||
|
|
||||||
|
FORMATO DE SAÍDA (Obrigatório em JSON):
|
||||||
|
{
|
||||||
|
"mode": "Técnico" | "Fundamental" | "Híbrido",
|
||||||
|
"analiseGeral": "string",
|
||||||
|
"timeframe": "string",
|
||||||
|
"estrutura": "string",
|
||||||
|
"tecnica": "string", // Detalhada (SMC+Liquidez+Momentum)
|
||||||
|
"fundamental": "string", // Resumo macro
|
||||||
|
"decision": "BUY" | "SELL" | "WAIT",
|
||||||
|
"entry": "string",
|
||||||
|
"stopLoss": "string",
|
||||||
|
"takeProfit": "string",
|
||||||
|
"score": number, // 0-100
|
||||||
|
"justification": "string",
|
||||||
|
"alerta": "string",
|
||||||
|
"pair": "string"
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const prompt = `
|
const prompt = `
|
||||||
Analise este gráfico de Forex sob a óptica institucional. ${userNotes ? `Notas do usuário: ${userNotes}` : ''}
|
Analise este gráfico sob a óptica do QuantScan IA.
|
||||||
Retorne a análise seguindo estritamente o esquema JSON fornecido.
|
${preferredMode ? `Use estritamente o modo de análise: ${preferredMode}.` : 'Detecte o melhor modo automaticamente.'}
|
||||||
|
${userNotes ? `Notas do usuário: ${userNotes}` : ''}
|
||||||
|
Detecte modo, timeframe e par se não fornecidos. Retorne JSON estrito.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const response = await ai.models.generateContent({
|
const response = await ai.models.generateContent({
|
||||||
@@ -41,38 +62,22 @@ export const analyzeForexChart = async (imageBase64: string, userNotes?: string)
|
|||||||
responseSchema: {
|
responseSchema: {
|
||||||
type: Type.OBJECT,
|
type: Type.OBJECT,
|
||||||
properties: {
|
properties: {
|
||||||
pair: { type: Type.STRING, description: "Par de moedas detectado (ex: EURUSD)" },
|
mode: { type: Type.STRING, enum: ['Técnico', 'Fundamental', 'Híbrido'] },
|
||||||
timeframe: { type: Type.STRING, description: "Timeframe detectado (ex: H1)" },
|
analiseGeral: { type: Type.STRING },
|
||||||
structure: { type: Type.STRING, description: "Resumo da estrutura de mercado" },
|
pair: { type: Type.STRING },
|
||||||
conceptsDetected: {
|
timeframe: { type: Type.STRING },
|
||||||
type: Type.ARRAY,
|
estrutura: { type: Type.STRING },
|
||||||
items: { type: Type.STRING },
|
tecnica: { type: Type.STRING },
|
||||||
description: "Lista de conceitos identificados"
|
fundamental: { type: Type.STRING },
|
||||||
},
|
decision: { type: Type.STRING, enum: ["BUY", "SELL", "WAIT"] },
|
||||||
decision: {
|
entry: { type: Type.STRING },
|
||||||
type: Type.STRING,
|
stopLoss: { type: Type.STRING },
|
||||||
enum: ["BUY", "SELL", "WAIT"],
|
takeProfit: { type: Type.STRING },
|
||||||
description: "Decisão final de trading"
|
score: { type: Type.NUMBER },
|
||||||
},
|
justification: { type: Type.STRING },
|
||||||
entry: { type: Type.STRING, description: "Preço ou zona de entrada" },
|
alerta: { type: Type.STRING }
|
||||||
stopLoss: { type: Type.STRING, description: "Preço de Stop Loss" },
|
|
||||||
takeProfit: { type: Type.STRING, description: "Preço de Take Profit" },
|
|
||||||
score: { type: Type.NUMBER, description: "Score de probabilidade de 0 a 100" },
|
|
||||||
justification: { type: Type.STRING, description: "Explicação técnica para o score" },
|
|
||||||
liquiditySweep: { type: Type.STRING, description: "Descrição do sweep de liquidez detectado ou armadilhas" },
|
|
||||||
momentum: { type: Type.STRING, description: "Análise do momentum e timing de entrada" },
|
|
||||||
keyZones: {
|
|
||||||
type: Type.ARRAY,
|
|
||||||
items: { type: Type.STRING },
|
|
||||||
description: "Zonas importantes detectadas"
|
|
||||||
},
|
|
||||||
institutionalContext: {
|
|
||||||
type: Type.STRING,
|
|
||||||
enum: ["Accumulation", "Manipulation", "Distribution", "None"],
|
|
||||||
description: "Fase do ciclo institucional"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
required: ["pair", "timeframe", "structure", "conceptsDetected", "decision", "entry", "stopLoss", "takeProfit", "score", "justification", "liquiditySweep", "momentum", "keyZones", "institutionalContext"]
|
required: ["mode", "analiseGeral", "pair", "timeframe", "estrutura", "tecnica", "fundamental", "decision", "entry", "stopLoss", "takeProfit", "score", "justification", "alerta"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-9
@@ -18,8 +18,6 @@ export enum SignalResult {
|
|||||||
|
|
||||||
export interface Signal {
|
export interface Signal {
|
||||||
id: string;
|
id: string;
|
||||||
pair: string;
|
|
||||||
timeframe: string;
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
type: SignalType;
|
type: SignalType;
|
||||||
entry: string;
|
entry: string;
|
||||||
@@ -27,10 +25,18 @@ export interface Signal {
|
|||||||
takeProfit: string;
|
takeProfit: string;
|
||||||
score: number;
|
score: number;
|
||||||
justification: string;
|
justification: string;
|
||||||
logicElements: string[];
|
|
||||||
result: SignalResult;
|
result: SignalResult;
|
||||||
screenshotUrl?: string;
|
screenshotUrl?: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
// QuantScan IA fields
|
||||||
|
mode: string;
|
||||||
|
pair: string;
|
||||||
|
analiseGeral: string;
|
||||||
|
timeframe: string;
|
||||||
|
estrutura: string;
|
||||||
|
tecnica: string;
|
||||||
|
fundamental: string;
|
||||||
|
alerta: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserStats {
|
export interface UserStats {
|
||||||
@@ -45,18 +51,18 @@ export interface UserStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalysisResponse {
|
export interface AnalysisResponse {
|
||||||
|
mode: 'Técnico' | 'Fundamental' | 'Híbrido';
|
||||||
|
analiseGeral: string;
|
||||||
pair: string;
|
pair: string;
|
||||||
timeframe: string;
|
timeframe: string;
|
||||||
structure: string;
|
estrutura: string;
|
||||||
conceptsDetected: string[];
|
tecnica: string;
|
||||||
|
fundamental: string;
|
||||||
decision: SignalType;
|
decision: SignalType;
|
||||||
entry: string;
|
entry: string;
|
||||||
stopLoss: string;
|
stopLoss: string;
|
||||||
takeProfit: string;
|
takeProfit: string;
|
||||||
score: number;
|
score: number;
|
||||||
justification: string;
|
justification: string;
|
||||||
liquiditySweep?: string;
|
alerta: string;
|
||||||
momentum?: string;
|
|
||||||
keyZones?: string[];
|
|
||||||
institutionalContext?: 'Accumulation' | 'Manipulation' | 'Distribution' | 'None';
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user