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%
19.1 KB · 374 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/pages/showcase.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 { useQuery } from "@tanstack/react-query";19import { useLocation } from "wouter";20import { Sparkles, Search, ArrowLeft, Users, TrendingUp, Filter } from "lucide-react";21import { ThemeToggle } from "@/components/theme-toggle";22import { ThemeColorPicker } from "@/components/theme-color-picker";23import { Button } from "@/components/ui/button";24import { Input } from "@/components/ui/input";25import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";26import { Badge } from "@/components/ui/badge";27import { formatDistanceToNow } from "date-fns";28import { fr } from "date-fns/locale";2930interface SharedReport {31  id: number;32  shareId: string;33  question: string;34  answer: string;35  toolResults: any[];36  createdAt: string;37}3839export default function Showcase() {40  const [, setLocation] = useLocation();41  const [searchQuery, setSearchQuery] = useState("");42  const [toolFilter, setToolFilter] = useState<string | null>(null);4344  // Fetch all shared reports45  const { data: reports, isLoading } = useQuery<SharedReport[]>({46    queryKey: ["/api/shared-reports"],47    refetchInterval: 30000, // Refresh every 30 seconds48  });4950  // Extract unique tools used across all reports51  const allTools = new Set<string>();52  reports?.forEach(report => {53    report.toolResults?.forEach((tr: any) => {54      allTools.add(tr.tool);55    });56  });5758  // Filter reports based on search query and tool filter59  const filteredReports = reports?.filter(report => {60    const matchesSearch = searchQuery === "" ||61      report.question.toLowerCase().includes(searchQuery.toLowerCase()) ||62      report.answer.toLowerCase().includes(searchQuery.toLowerCase());6364    const matchesTool = !toolFilter || report.toolResults?.some((tr: any) => tr.tool === toolFilter);6566    return matchesSearch && matchesTool;67  });6869  // Complete tool metadata mapping for distinct badges70  const TOOL_METADATA: Record<string, { name: string; color: string }> = {71    // Fundamental Analysis72    get_company_profile: { name: 'Company Profile', color: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20' },73    get_income_statement: { name: 'Income Statement', color: 'bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/20' },74    get_balance_sheet: { name: 'Balance Sheet', color: 'bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20' },75    get_cash_flow: { name: 'Cash Flow', color: 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20' },76    get_key_metrics: { name: 'Key Metrics', color: 'bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20' },77    get_financial_ratios: { name: 'Financial Ratios', color: 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20' },78    get_financial_growth: { name: 'Financial Growth', color: 'bg-teal-500/10 text-teal-600 dark:text-teal-400 border-teal-500/20' },7980    // Market Data81    get_stock_quote: { name: 'Stock Quote', color: 'bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20' },82    get_historical_price: { name: 'Historical Prices', color: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20' },83    get_intraday_price: { name: 'Intraday Prices', color: 'bg-lime-500/10 text-lime-600 dark:text-lime-400 border-lime-500/20' },8485    // Technical Indicators86    get_rsi: { name: 'RSI', color: 'bg-cyan-600/10 text-cyan-700 dark:text-cyan-300 border-cyan-600/20' },87    get_macd: { name: 'MACD', color: 'bg-blue-600/10 text-blue-700 dark:text-blue-300 border-blue-600/20' },88    get_ema: { name: 'EMA', color: 'bg-indigo-600/10 text-indigo-700 dark:text-indigo-300 border-indigo-600/20' },89    get_sma: { name: 'SMA', color: 'bg-violet-600/10 text-violet-700 dark:text-violet-300 border-violet-600/20' },90    get_adx: { name: 'ADX', color: 'bg-purple-600/10 text-purple-700 dark:text-purple-300 border-purple-600/20' },91    get_williams_r: { name: 'Williams %R', color: 'bg-fuchsia-500/10 text-fuchsia-600 dark:text-fuchsia-400 border-fuchsia-500/20' },92    get_cci: { name: 'CCI', color: 'bg-pink-500/10 text-pink-600 dark:text-pink-400 border-pink-500/20' },93    get_stochastic: { name: 'Stochastic', color: 'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20' },9495    // News & Events96    get_financial_news: { name: 'Financial News', color: 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20' },97    get_press_releases: { name: 'Press Releases', color: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20' },98    get_stock_news_sentiment: { name: 'News Sentiment', color: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/20' },99100    // Calendar & Events101    get_economic_calendar: { name: 'Economic Calendar', color: 'bg-slate-500/10 text-slate-600 dark:text-slate-400 border-slate-500/20' },102    get_earnings_calendar: { name: 'Earnings Calendar', color: 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20' },103    get_ipo_calendar: { name: 'IPO Calendar', color: 'bg-zinc-500/10 text-zinc-600 dark:text-zinc-400 border-zinc-500/20' },104105    // Trading Activity106    get_insider_trading: { name: 'Insider Trading', color: 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20' },107    get_congressional_trading: { name: 'Congressional Trading', color: 'bg-red-600/10 text-red-700 dark:text-red-300 border-red-600/20' },108    get_senate_trading: { name: 'Senate Trading', color: 'bg-rose-600/10 text-rose-700 dark:text-rose-300 border-rose-600/20' },109    get_institutional_holders: { name: 'Institutional Holders', color: 'bg-pink-600/10 text-pink-700 dark:text-pink-300 border-pink-600/20' },110111    // Forex & Commodities112    get_forex_quote: { name: 'Forex Quote', color: 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20' },113    get_forex_historical: { name: 'Forex Historical', color: 'bg-teal-600/10 text-teal-700 dark:text-teal-300 border-teal-600/20' },114    get_commodity_quotes: { name: 'Commodity Prices', color: 'bg-amber-600/10 text-amber-700 dark:text-amber-300 border-amber-600/20' },115116    // Macro & Rates117    get_treasury_rates: { name: 'Treasury Rates', color: 'bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/20' },118    get_economic_indicator: { name: 'Economic Indicator', color: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20' },119120    // Analyst & Estimates121    get_analyst_estimates: { name: 'Analyst Estimates', color: 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20' },122    get_earnings_surprises: { name: 'Earnings Surprises', color: 'bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20' },123    get_price_target: { name: 'Price Targets', color: 'bg-fuchsia-600/10 text-fuchsia-700 dark:text-fuchsia-300 border-fuchsia-600/20' },124    get_price_target_summary: { name: 'Price Target Summary', color: 'bg-pink-500/10 text-pink-600 dark:text-pink-400 border-pink-500/20' },125    get_upgrades_downgrades: { name: 'Upgrades/Downgrades', color: 'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20' },126127    // Dividends & Splits128    get_dividend_history: { name: 'Dividend History', color: 'bg-green-600/10 text-green-700 dark:text-green-300 border-green-600/20' },129    get_stock_split_history: { name: 'Stock Splits', color: 'bg-emerald-600/10 text-emerald-700 dark:text-emerald-300 border-emerald-600/20' },130131    // SEC & Filings132    get_sec_filings: { name: 'SEC Filings', color: 'bg-gray-600/10 text-gray-700 dark:text-gray-300 border-gray-600/20' },133    get_cot_report: { name: 'COT Report', color: 'bg-slate-600/10 text-slate-700 dark:text-slate-300 border-slate-600/20' },134135    // ESG136    get_esg_score: { name: 'ESG Score', color: 'bg-lime-600/10 text-lime-700 dark:text-lime-300 border-lime-600/20' },137138    // Screening & Search139    get_stock_screener: { name: 'Stock Screener', color: 'bg-orange-600/10 text-orange-700 dark:text-orange-300 border-orange-600/20' },140    search_companies: { name: 'Company Search', color: 'bg-teal-500/10 text-teal-600 dark:text-teal-400 border-teal-500/20' },141    get_stock_peers: { name: 'Peer Companies', color: 'bg-sky-600/10 text-sky-700 dark:text-sky-300 border-sky-600/20' },142143    // ETF144    get_etf_holdings: { name: 'ETF Holdings', color: 'bg-cyan-600/10 text-cyan-700 dark:text-cyan-300 border-cyan-600/20' },145    get_etf_sector_weightings: { name: 'ETF Sectors', color: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20' },146    get_etf_country_weightings: { name: 'ETF Countries', color: 'bg-indigo-600/10 text-indigo-700 dark:text-indigo-300 border-indigo-600/20' },147148    // Crypto149    get_crypto_quote: { name: 'Crypto Quote', color: 'bg-yellow-600/10 text-yellow-700 dark:text-yellow-300 border-yellow-600/20' },150    get_crypto_list: { name: 'Crypto List', color: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20' },151152    // Advanced Analysis153    run_monte_carlo_simulation: { name: 'Monte Carlo Simulation', color: 'bg-purple-600/10 text-purple-700 dark:text-purple-300 border-purple-600/20' },154    calculate_options_price: { name: 'Options Pricing', color: 'bg-blue-600/10 text-blue-700 dark:text-blue-300 border-blue-600/20' },155    estimate_garch_volatility: { name: 'GARCH Volatility', color: 'bg-amber-600/10 text-amber-700 dark:text-amber-300 border-amber-600/20' },156    calculate_var: { name: 'Value at Risk', color: 'bg-red-600/10 text-red-700 dark:text-red-300 border-red-600/20' },157    optimize_portfolio: { name: 'Portfolio Optimization', color: 'bg-green-600/10 text-green-700 dark:text-green-300 border-green-600/20' },158    analyze_risk_metrics: { name: 'Risk Metrics', color: 'bg-orange-600/10 text-orange-700 dark:text-orange-300 border-orange-600/20' },159160    // Research161    web_search_exa: { name: 'Web Search', color: 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20' },162163    // Market Hours & Misc164    get_market_hours: { name: 'Market Hours', color: 'bg-slate-500/10 text-slate-600 dark:text-slate-400 border-slate-500/20' },165    get_company_outlook: { name: 'Company Outlook', color: 'bg-violet-600/10 text-violet-700 dark:text-violet-300 border-violet-600/20' },166    get_social_sentiment: { name: 'Social Sentiment', color: 'bg-pink-500/10 text-pink-600 dark:text-pink-400 border-pink-500/20' },167168    // Data Downloads & Plots169    create_plot: { name: 'Custom Plot', color: 'bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/20' },170    download_fmp_data: { name: 'Data Download', color: 'bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20' },171  };172173  // Get tool metadata with fallback174  const getToolMetadata = (toolName: string) => {175    return TOOL_METADATA[toolName] || {176      name: toolName.replace(/_/g, ' ').replace(/^get /, ''),177      color: 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20'178    };179  };180181  return (182    <div className="min-h-screen bg-background relative overflow-hidden" data-testid="page-showcase">183      {/* Subtle Background */}184      <div className="fixed inset-0 z-0 pointer-events-none">185        <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,hsl(var(--primary)/0.03),transparent_70%)]" />186      </div>187188      {/* Header */}189      <header className="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur-sm">190        <div className="container flex h-14 items-center justify-between px-6">191          <div className="flex items-center gap-4">192            <Button193              variant="ghost"194              size="sm"195              onClick={() => setLocation('/')}196              className="hover:bg-muted transition-colors gap-2"197            >198              <ArrowLeft className="w-4 h-4" />199              <span className="hidden sm:inline font-medium">Retour</span>200            </Button>201            <button202              onClick={() => setLocation('/')}203              className="flex items-center gap-2.5"204            >205              <Sparkles className="w-5 h-5 text-primary" />206              <div className="hidden md:flex items-baseline gap-1.5">207                <span className="text-base font-bold text-foreground leading-tight">208                  VQuant209                </span>210              </div>211            </button>212          </div>213          <div className="flex items-center gap-3">214            <div className="h-8 w-px bg-border/50" />215            <ThemeColorPicker />216            <ThemeToggle />217          </div>218        </div>219      </header>220221      {/* Main Content */}222      <main className="container mx-auto px-4 py-8 max-w-7xl relative z-10">223        {/* Page Header */}224        <div className="mb-10 space-y-6">225          <div className="flex items-center gap-4">226            <div className="p-3 rounded-lg bg-primary/10 border border-primary/20">227              <Users className="w-6 h-6 text-primary" />228            </div>229            <div>230              <h1 className="text-3xl font-bold text-foreground tracking-tight">231                Showcase Communautaire232              </h1>233              <p className="text-muted-foreground text-base mt-1">234                Découvrez toutes les analyses générées par la communauté VQuant235              </p>236            </div>237          </div>238239          {/* Search and Filter Bar */}240          <div className="flex flex-col sm:flex-row gap-4">241            <div className="relative flex-1">242              <Input243                type="text"244                placeholder="Rechercher des analyses..."245                value={searchQuery}246                onChange={(e) => setSearchQuery(e.target.value)}247                className="pl-12 pr-4 h-11 border border-border focus:border-primary bg-card text-base transition-colors"248              />249              <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />250            </div>251            {toolFilter && (252              <Button253                variant="outline"254                size="lg"255                onClick={() => setToolFilter(null)}256                className="gap-2 hover:bg-muted transition-colors"257              >258                <Filter className="w-4 h-4" />259                Effacer filtre260              </Button>261            )}262          </div>263264          {/* Stats */}265          {reports && (266            <div className="flex items-center gap-6 text-sm text-muted-foreground">267              <div className="flex items-center gap-2">268                <TrendingUp className="w-4 h-4" />269                <span>{reports.length} analyses publiques</span>270              </div>271              <div className="flex items-center gap-2">272                <Users className="w-4 h-4" />273                <span>{allTools.size} outils utilisés</span>274              </div>275            </div>276          )}277        </div>278279        {/* Loading State */}280        {isLoading && (281          <div className="text-center py-12">282            <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>283            <p className="text-muted-foreground mt-4">Chargement des analyses...</p>284          </div>285        )}286287        {/* Reports Grid */}288        {filteredReports && filteredReports.length > 0 && (289          <div className="grid md:grid-cols-2 gap-8">290            {filteredReports.map((report) => (291              <Card292                key={report.id}293                className="group cursor-pointer border border-border hover:border-primary/40 transition-colors bg-card"294                onClick={() => setLocation(`/share/${report.shareId}`)}295              >296                <CardHeader className="pb-4">297                  <div className="flex items-start justify-between gap-3">298                    <CardTitle className="text-lg font-bold line-clamp-2 group-hover:text-primary transition-colors">299                      {report.question}300                    </CardTitle>301                  </div>302                  <div className="flex items-center gap-2 mt-2">303                    <div className="w-1.5 h-1.5 rounded-full bg-primary" />304                    <p className="text-xs text-muted-foreground font-medium">305                      {formatDistanceToNow(new Date(report.createdAt), {306                        addSuffix: true,307                        locale: fr308                      })}309                    </p>310                  </div>311                </CardHeader>312                <CardContent className="space-y-4">313                  {/* Answer Preview */}314                  <p className="text-sm text-muted-foreground line-clamp-3 leading-relaxed">315                    {report.answer}316                  </p>317318                  {/* Tools Used */}319                  {report.toolResults && report.toolResults.length > 0 && (320                    <div className="flex flex-wrap gap-2">321                      {Array.from(new Set(report.toolResults.map((tr: any) => tr.tool))).map((tool: any) => {322                        const toolMeta = getToolMetadata(tool);323                        return (324                          <Badge325                            key={tool}326                            variant="outline"327                            className={`text-xs font-medium px-2.5 py-0.5 ${toolMeta.color} cursor-pointer transition-colors`}328                            onClick={(e) => {329                              e.stopPropagation();330                              setToolFilter(tool);331                            }}332                          >333                            {toolMeta.name}334                          </Badge>335                        );336                      })}337                    </div>338                  )}339                </CardContent>340              </Card>341            ))}342          </div>343        )}344345        {/* Empty State */}346        {filteredReports && filteredReports.length === 0 && !isLoading && (347          <Card className="text-center py-12">348            <CardContent>349              <Search className="w-12 h-12 mx-auto text-muted-foreground mb-4" />350              <h3 className="text-lg font-semibold mb-2">Aucune analyse trouvée</h3>351              <p className="text-muted-foreground mb-4">352                {searchQuery || toolFilter353                  ? "Essayez de modifier vos critères de recherche"354                  : "Aucune analyse n'a été partagée pour le moment"}355              </p>356              {(searchQuery || toolFilter) && (357                <Button358                  variant="outline"359                  onClick={() => {360                    setSearchQuery("");361                    setToolFilter(null);362                  }}363                >364                  Réinitialiser les filtres365                </Button>366              )}367            </CardContent>368          </Card>369        )}370      </main>371    </div>372  );373}374