SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
17.5 KB · 423 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/components/chat/empty-state.tsx6 *7 *  Author:    Simon-Pierre Boucher8 *  Contact:   contact@spboucher.ai9 *  Website:   https://www.spboucher.ai10 *  Demo:      https://www.vquant.ai11 *  License:   MIT (see LICENSE)12 *13 *  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { useState } from "react";18import {19  TrendingUp, LineChart, DollarSign, PieChart, Sparkles,20  BarChart3, Activity, TrendingDown, Calculator, Target,21  Building2, Newspaper, Users, Globe, Coins, Gauge,22  Wand2, Crown, Gem, Atom, Feather, Rabbit, Bot,23  Brain, Cpu, Zap, Rocket, Bird, Sun, Earth, Moon24} from "lucide-react";25import {26  Select,27  SelectContent,28  SelectItem,29  SelectTrigger,30  SelectValue,31} from "@/components/ui/select";32import { AnimatePresence, motion } from "framer-motion";33import type { ClaudeModel } from "@/hooks/useChatSession";3435interface EmptyStateProps {36  onQuestionClick?: (question: string) => void;37  selectedModel?: ClaudeModel;38  onModelChange?: (model: ClaudeModel) => void;39}4041const MODEL_OPTIONS: { value: ClaudeModel; label: string; sub: string; icon: typeof Sparkles; disabled?: boolean }[] = [42  { value: "claude-fable-5", label: "Fable 5", sub: "Le plus intelligent (defaut)", icon: Wand2 },43  { value: "claude-opus-4-8", label: "Opus 4.8", sub: "Le plus puissant", icon: Crown },44  { value: "claude-opus-4-7", label: "Opus 4.7", sub: "Equilibre", icon: Gem },45  { value: "claude-opus-4-6", label: "Opus 4.6", sub: "Rapide", icon: Atom },46  { value: "claude-sonnet-4-6", label: "Sonnet 4.6", sub: "Polyvalent", icon: Feather },47  { value: "claude-haiku-4-5-20251001", label: "Haiku 4.5", sub: "Le plus rapide", icon: Rabbit },48  { value: "gpt-5.6-sol", label: "GPT-5.6 Sol", sub: "OpenAI - le plus puissant", icon: Sun },49  { value: "gpt-5.6-terra", label: "GPT-5.6 Terra", sub: "OpenAI - equilibre", icon: Earth },50  { value: "gpt-5.6-luna", label: "GPT-5.6 Luna", sub: "OpenAI - rapide et econome", icon: Moon },51  { value: "gpt-5.5", label: "GPT-5.5", sub: "OpenAI - puissant", icon: Bot },52  { value: "gpt-5.4", label: "GPT-5.4", sub: "OpenAI - contexte 1M", icon: Brain },53  { value: "gpt-5.2", label: "GPT-5.2", sub: "OpenAI - puissant", icon: Cpu },54  { value: "gpt-5.1", label: "GPT-5.1", sub: "OpenAI - equilibre", icon: Zap },55  { value: "gpt-5", label: "GPT-5", sub: "OpenAI - classique", icon: Sparkles },56  { value: "gpt-5-mini", label: "GPT-5 Mini", sub: "OpenAI - rapide", icon: Rocket },57  { value: "gpt-5-nano", label: "GPT-5 Nano", sub: "OpenAI - le plus rapide", icon: Bird },58  { value: "gpt-4.1", label: "GPT-4.1", sub: "OpenAI - contexte 1M", icon: Globe },59  { value: "gpt-4.1-mini", label: "GPT-4.1 Mini", sub: "OpenAI - econome", icon: Feather },60  { value: "gemini-3.5-flash", label: "Gemini 3.5 Flash", sub: "Google - le plus recent", icon: Sparkles },61  { value: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro", sub: "Google - le plus puissant", icon: Gem },62  { value: "gemini-3-flash-preview", label: "Gemini 3 Flash", sub: "Google - equilibre", icon: Zap },63  { value: "gemini-3.1-flash-lite", label: "Gemini 3.1 Flash Lite", sub: "Google - le plus rapide", icon: Bird },64];6566const QUESTION_CATEGORIES = [67  {68    category: "Analyse Fondamentale",69    icon: Building2,70    questions: [71      "Analyse complète d'Apple (AAPL)",72      "Profil et métriques clés de Microsoft (MSFT)",73      "Bilan financier et ratios de Tesla",74      "États financiers complets de Google (GOOGL)",75      "Croissance des revenus d'Amazon sur 5 ans",76      "Flux de trésorerie de Meta (META)",77      "Santé financière et ratios clés de Netflix",78    ]79  },80  {81    category: "Analyse Technique",82    icon: Activity,83    questions: [84      "Indicateurs techniques RSI et MACD pour Tesla",85      "Moyennes mobiles EMA et SMA pour AAPL",86      "Analyse ADX et tendance de NVIDIA",87      "Stochastique et Williams %R pour AMD",88      "Tous les indicateurs techniques pour SPY",89      "Analyse technique complète de Bitcoin",90      "Points d'entrée et sortie pour Amazon",91    ]92  },93  {94    category: "Performance et Historique",95    icon: LineChart,96    questions: [97      "Performance de Tesla sur l'année",98      "Historique des prix Apple sur 5 ans",99      "Comparaison AAPL vs MSFT sur 2 ans",100      "Performance du S&P 500 ce trimestre",101      "Évolution du NASDAQ depuis janvier",102      "Historique intraday de NVDA aujourd'hui",103      "Performance des FAANG cette année",104    ]105  },106  {107    category: "Dividendes et Actions Corporate",108    icon: DollarSign,109    questions: [110      "Historique des dividendes Microsoft",111      "Rendement des dividendes de Coca-Cola",112      "Splits d'actions d'Apple historique",113      "Rachats d'actions et dividendes de JPMorgan",114      "Entreprises avec les meilleurs dividendes en technologie",115      "Calendrier des dividendes pour ce mois",116    ]117  },118  {119    category: "Résultats et Estimations",120    icon: Target,121    questions: [122      "Prochains résultats trimestriels d'Apple",123      "Surprises de résultats pour Tesla Q3",124      "Estimations des analystes pour NVIDIA",125      "Calendrier des résultats cette semaine",126      "Objectifs de prix des analystes pour Meta",127      "Révisions à la hausse ou baisse pour Amazon",128    ]129  },130  {131    category: "Actualités et Événements",132    icon: Newspaper,133    questions: [134      "Dernières nouvelles sur Tesla",135      "Communiqués de presse Apple cette semaine",136      "Actualités économiques du jour",137      "Calendrier économique de la semaine",138      "Prochaines IPO à surveiller",139      "Événements importants en finance cette semaine",140    ]141  },142  {143    category: "Trading d'Initiés et Institutionnel",144    icon: Users,145    questions: [146      "Trading d'initiés récent pour Apple",147      "Achats d'actions par les dirigeants de Tesla",148      "Trading des membres du Congrès ce mois",149      "Positions institutionnelles dans NVIDIA",150      "Changements de participations dans Meta",151    ]152  },153  {154    category: "Marchés Globaux",155    icon: Globe,156    questions: [157      "Taux de change EUR/USD aujourd'hui",158      "Prix des commodités or et pétrole",159      "Taux du Trésor américain actuels",160      "Performance des indices mondiaux",161      "Analyse du marché Forex majeurs",162      "COT Report pour le pétrole",163    ]164  },165  {166    category: "Gestion de Portefeuille",167    icon: PieChart,168    questions: [169      "Optimiser portefeuille AAPL, MSFT, GOOGL",170      "Allocation optimale pour tech stocks",171      "Diversification avec 10 actions",172      "Frontière efficiente pour mon portefeuille",173      "Ratio risque/rendement optimal",174    ]175  },176  {177    category: "Analyse de Risque",178    icon: TrendingDown,179    questions: [180      "Value at Risk (VaR) pour Tesla",181      "Volatilité GARCH de Bitcoin",182      "Métriques de risque pour mon portefeuille",183      "Drawdown maximum de NVIDIA",184      "Corrélations entre FAANG stocks",185      "Beta et volatilité d'Amazon",186    ]187  },188  {189    category: "Simulations et Prévisions",190    icon: BarChart3,191    questions: [192      "Simulation Monte Carlo NVDA $100k",193      "Projection de croissance Apple 5 ans",194      "Scénarios d'investissement Tesla",195      "Simulation de portefeuille diversifié",196      "Prévisions basées sur tendances historiques",197    ]198  },199  {200    category: "Options et Dérivés",201    icon: Calculator,202    questions: [203      "Prix d'option call Apple strike 180",204      "Stratégie d'options pour Tesla",205      "Surface de volatilité implicite NVDA",206      "Greeks pour options Microsoft",207      "Valorisation Black-Scholes pour call",208    ]209  },210  {211    category: "Screener et Comparaisons",212    icon: Gauge,213    questions: [214      "Meilleures actions tech par P/E ratio",215      "Compagnies similaires à Apple",216      "Screener: dividendes > 3% et P/E < 15",217      "Top 10 actions momentum ce mois",218      "Comparaison des géants tech FAANG",219    ]220  },221];222223export function EmptyState({ onQuestionClick, selectedModel, onModelChange }: EmptyStateProps) {224  const [selectedCategoryIndex, setSelectedCategoryIndex] = useState<number | null>(null);225226  const handleCategoryChange = (value: string) => {227    const index = parseInt(value);228    setSelectedCategoryIndex(index);229  };230231  const selectedCategory = selectedCategoryIndex !== null232    ? QUESTION_CATEGORIES[selectedCategoryIndex]233    : null;234235  return (236    <div className="flex flex-col items-center justify-center min-h-[60vh] space-y-8 animate-fade-in">237      <div className="relative text-center space-y-4">238        <div aria-hidden className="pointer-events-none absolute left-1/2 top-[-3rem] -z-10 h-64 w-[40rem] -translate-x-1/2 bg-grid hero-mask opacity-70" />239        <div aria-hidden className="pointer-events-none absolute left-1/2 top-[-1rem] -z-10 h-56 w-56 -translate-x-1/2 rounded-full bg-primary/20 blur-3xl" />240        <div className="flex flex-col items-center justify-center gap-4 mb-2">241          <div className="relative grid place-items-center size-16 rounded-2xl bg-hot shadow-hot animate-float">242            <span className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-br from-white/40 to-transparent opacity-60" />243            <Sparkles className="relative w-8 h-8 text-white" />244          </div>245          <h1 className="text-4xl sm:text-5xl font-extrabold tracking-tight">246            <span className="text-foreground">V</span><span className="gradient-text">Quant</span>247          </h1>248          <div className="inline-flex items-center gap-2 rounded-full border border-border bg-card/70 px-3.5 py-1.5 text-xs font-medium text-muted-foreground backdrop-blur">249            <span className="size-2 rounded-full bg-primary animate-pulse" />250            200+ outils financiers - Python integre - Propulse par Fable 5251          </div>252        </div>253        <p className="text-lg text-muted-foreground max-w-md">254          Votre assistant d'analyse financière propulsé par l'IA255        </p>256      </div>257258      <div className="w-full max-w-4xl space-y-6 px-4">259        <div className="text-center">260          <p className="text-sm text-muted-foreground mb-3">261            Choisissez une catégorie pour découvrir des exemples de questions262          </p>263        </div>264265        {/* Category dropdown */}266        <div>267          <Select onValueChange={handleCategoryChange}>268            <SelectTrigger className="w-full h-12 text-base font-medium bg-card border border-border rounded-lg transition-colors hover:border-primary/40">269              <div className="flex items-center gap-3 px-1">270                <Sparkles className="w-4 h-4 text-primary flex-shrink-0" />271                <SelectValue placeholder="Sélectionnez une catégorie d'analyse..." />272              </div>273            </SelectTrigger>274275            <SelectContent className="w-[var(--radix-select-trigger-width)] max-h-[450px] bg-card border border-border rounded-lg p-1">276              <div className="px-3 py-2 mb-1 border-b border-border">277                <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">278                  {QUESTION_CATEGORIES.length} Catégories Disponibles279                </p>280              </div>281282              <div className="space-y-0.5">283                {QUESTION_CATEGORIES.map((category, idx) => {284                  const Icon = category.icon;285                  return (286                    <SelectItem287                      key={idx}288                      value={idx.toString()}289                      className="cursor-pointer py-3 px-3 text-base rounded-md transition-colors"290                    >291                      <div className="flex items-center gap-3">292                        <div className="p-1.5 rounded-md bg-primary/10 border border-primary/20">293                          <Icon className="w-4 h-4 text-primary" />294                        </div>295                        <div className="flex flex-col flex-1">296                          <span className="font-medium text-foreground">297                            {category.category}298                          </span>299                          <span className="text-xs text-muted-foreground">300                            {category.questions.length} exemples301                          </span>302                        </div>303                      </div>304                    </SelectItem>305                  );306                })}307              </div>308            </SelectContent>309          </Select>310        </div>311312        {/* Questions for selected category */}313        <AnimatePresence mode="wait">314          {selectedCategory && (315            <motion.div316              key={selectedCategoryIndex}317              initial={{ opacity: 0, y: 12 }}318              animate={{ opacity: 1, y: 0 }}319              exit={{ opacity: 0, y: -12 }}320              transition={{ duration: 0.2 }}321              className="space-y-4"322            >323              {/* Category header */}324              <div className="flex items-center gap-3 p-4 rounded-lg bg-card border border-border">325                {(() => {326                  const Icon = selectedCategory.icon;327                  return (328                    <div className="p-2 rounded-md bg-primary/10 border border-primary/20">329                      <Icon className="w-5 h-5 text-primary" />330                    </div>331                  );332                })()}333                <h3 className="text-xl font-bold text-foreground">334                  {selectedCategory.category}335                </h3>336              </div>337338              {/* Questions grid */}339              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">340                {selectedCategory.questions.map((question, qIdx) => (341                  <button342                    key={qIdx}343                    onClick={() => onQuestionClick?.(question)}344                    className="group p-4 text-left rounded-lg bg-card border border-border hover:border-primary/40 transition-colors"345                  >346                    <div className="flex items-start gap-3">347                      <div className="flex-shrink-0 w-7 h-7 rounded-md bg-muted flex items-center justify-center font-medium text-sm text-muted-foreground">348                        {qIdx + 1}349                      </div>350                      <p className="flex-1 text-sm font-medium leading-relaxed group-hover:text-foreground transition-colors">351                        {question}352                      </p>353                    </div>354                  </button>355                ))}356              </div>357            </motion.div>358          )}359        </AnimatePresence>360361        {/* Model selector */}362        <div className="pt-2">363          <p className="text-xs text-muted-foreground mb-2 text-center">364            Modele d IA utilise365          </p>366          <Select value={selectedModel} onValueChange={(v) => onModelChange?.(v as ClaudeModel)}>367            <SelectTrigger className="w-full h-12 text-base font-medium bg-card border border-border rounded-lg transition-colors hover:border-primary/40">368              <div className="flex items-center gap-3 px-1">369                {(() => {370                  const cur = MODEL_OPTIONS.find((m) => m.value === selectedModel) ?? MODEL_OPTIONS[0];371                  const Icon = cur.icon;372                  return (373                    <>374                      <Icon className="w-4 h-4 text-primary flex-shrink-0" />375                      <span className="text-foreground">{cur.label}</span>376                    </>377                  );378                })()}379              </div>380            </SelectTrigger>381            <SelectContent className="w-[var(--radix-select-trigger-width)] bg-card border border-border rounded-lg p-1">382              <div className="px-3 py-2 mb-1 border-b border-border">383                <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">384                  Choisir un modele385                </p>386              </div>387              <div className="space-y-0.5">388                {MODEL_OPTIONS.map((m) => {389                  const Icon = m.icon;390                  return (391                    <SelectItem392                      key={m.value}393                      value={m.value}394                      textValue={m.label}395                      disabled={m.disabled}396                      className="cursor-pointer py-3 px-3 text-base rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed"397                    >398                      <div className="flex items-center gap-3">399                        <div className="p-1.5 rounded-md bg-primary/10 border border-primary/20">400                          <Icon className="w-4 h-4 text-primary" />401                        </div>402                        <div className="flex flex-col flex-1">403                          <span className="font-medium text-foreground">{m.label}</span>404                          <span className="text-xs text-muted-foreground">{m.sub}</span>405                        </div>406                        {m.disabled && (407                          <span className="text-[10px] uppercase tracking-wider font-semibold text-amber-500 border border-amber-500/40 rounded px-1.5 py-0.5">408                            Indispo409                          </span>410                        )}411                      </div>412                    </SelectItem>413                  );414                })}415              </div>416            </SelectContent>417          </Select>418        </div>419      </div>420    </div>421  );422}423