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%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/pages/home.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 { SearchBar } from "@/components/chat/search-bar";18import { EmptyState } from "@/components/chat/empty-state";19import { StreamingAnswer } from "@/components/streaming-answer";20import { ToolResults } from "@/components/chat/tool-results";21import { AgentSteps } from "@/components/chat/agent-steps";22import { ThinkingWidget } from "@/components/chat/thinking-widget";23import { MonteCarloResults } from "@/components/chat/monte-carlo-results";24import { OptionsPricingCard } from "@/components/financial/options-pricing-card";25import { CustomPythonFigure } from "@/components/chat/custom-python-figure";26import { ResultCard } from "@/components/chat/result-card";27import { SearchingWidget } from "@/components/chat/searching-widget";28import { ErrorState } from "@/components/chat/error-state";29import { ThemeToggle } from "@/components/theme-toggle";30import { ThemeColorPicker } from "@/components/theme-color-picker";31import { ShareButton } from "@/components/chat/share-button";32import { DownloadButtons } from "@/components/chat/download-buttons";33import { ConversationHistory } from "@/components/chat/conversation-history";34import { SessionHistory } from "@/components/chat/session-history";35import { MarketTicker } from "@/components/market-ticker";36import { Sparkles, BookOpen, Plus, Users, User, History, LogOut } from "lucide-react";37import { useLocation } from "wouter";38import { Button } from "@/components/ui/button";39import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";40import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";41import { useChatSession } from "@/hooks/useChatSession";4243export default function Home() {44 const [, setLocation] = useLocation();45 const {46 currentUser,47 handleLogout,48 currentSessionId,49 historyOpen,50 setHistoryOpen,51 handleLoadSession,52 conversationHistory,53 currentQuestion,54 streamingAnswer,55 thinkingContent,56 sources,57 searchQueries,58 toolResults,59 agentSteps,60 statusMessage,61 currentSearchIndex,62 pythonCode,63 monteCarloResults,64 optionsPricingResults,65 customPythonFigures,66 figureRegistry,67 figureUrls,68 resultsRef,69 chatMutation,70 hasSearched,71 handleSearch,72 handleRetry,73 handleNewConversation,74 selectedModel,75 handleModelChange,76 } = useChatSession();7778 return (79 <div className="flex flex-col h-screen bg-background relative overflow-hidden" data-testid="page-home">80 {/* Subtle Background */}81 <div className="fixed inset-0 z-0 pointer-events-none">82 <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,hsl(var(--primary)/0.03),transparent_70%)]" />83 </div>8485 {/* Header */}86 <header className="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur-sm" data-testid="header-main">87 <div className="container flex h-14 items-center justify-between px-6">88 <div className="cursor-pointer" data-testid="logo-container">89 <div className="flex items-center gap-2.5">90 <div className="relative grid place-items-center size-8 rounded-xl bg-hot shadow-hot overflow-hidden" data-testid="logo-icon">91 <span className="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/40 to-transparent opacity-60" />92 <Sparkles className="relative w-4 h-4 text-white" />93 </div>94 <div className="hidden md:flex items-baseline gap-0.5">95 <span className="text-base font-extrabold leading-tight tracking-tight">96 <span className="text-foreground">V</span><span className="gradient-text">Quant</span>97 </span>98 </div>99 </div>100 </div>101 <div className="flex items-center gap-3">102 {(conversationHistory.length > 0 || hasSearched) && (103 <Tooltip>104 <TooltipTrigger asChild>105 <Button variant="ghost" size="icon" onClick={handleNewConversation} data-testid="button-new-conversation" className="h-9 w-9 hover:bg-muted transition-colors">106 <Plus className="h-5 w-5" />107 </Button>108 </TooltipTrigger>109 <TooltipContent><p>Nouvelle conversation</p></TooltipContent>110 </Tooltip>111 )}112 <Tooltip>113 <TooltipTrigger asChild>114 <Button variant="ghost" size="icon" onClick={() => setLocation('/showcase')} data-testid="button-showcase" className="h-9 w-9 hover:bg-muted transition-colors">115 <Users className="h-5 w-5" />116 </Button>117 </TooltipTrigger>118 <TooltipContent><p>Showcase Communautaire</p></TooltipContent>119 </Tooltip>120 <Tooltip>121 <TooltipTrigger asChild>122 <Button variant="ghost" size="icon" onClick={() => setLocation('/docs')} data-testid="button-docs" className="h-9 w-9 hover:bg-muted transition-colors">123 <BookOpen className="h-5 w-5" />124 </Button>125 </TooltipTrigger>126 <TooltipContent><p>Documentation</p></TooltipContent>127 </Tooltip>128 <div className="h-8 w-px bg-border/50 mx-1" />129 {currentUser ? (130 <>131 <Sheet open={historyOpen} onOpenChange={setHistoryOpen}>132 <SheetTrigger asChild>133 <Tooltip>134 <TooltipTrigger asChild>135 <Button variant="ghost" size="icon" className="h-9 w-9 hover:bg-muted transition-colors">136 <History className="h-5 w-5" />137 </Button>138 </TooltipTrigger>139 <TooltipContent><p>Mon historique</p></TooltipContent>140 </Tooltip>141 </SheetTrigger>142 <SheetContent side="right" className="w-80 sm:w-96">143 <SheetHeader><SheetTitle>Mon historique</SheetTitle></SheetHeader>144 <div className="mt-4 h-[calc(100vh-8rem)]">145 <SessionHistory onLoadSession={handleLoadSession} currentSessionId={currentSessionId} />146 </div>147 </SheetContent>148 </Sheet>149 <div className="hidden md:flex items-center gap-1.5 px-2.5 py-1.5 rounded-xl bg-primary/10 border border-primary/20">150 <User className="h-3.5 w-3.5 text-primary" />151 <span className="text-xs font-medium text-primary max-w-[80px] truncate">152 {currentUser.displayName}153 </span>154 </div>155 <Tooltip>156 <TooltipTrigger asChild>157 <Button variant="ghost" size="icon" onClick={handleLogout} className="h-9 w-9 hover:bg-muted transition-colors">158 <LogOut className="h-5 w-5" />159 </Button>160 </TooltipTrigger>161 <TooltipContent><p>Deconnexion</p></TooltipContent>162 </Tooltip>163 </>164 ) : (165 <Tooltip>166 <TooltipTrigger asChild>167 <Button variant="ghost" size="icon" onClick={() => setLocation('/auth')} className="h-9 w-9 hover:bg-muted transition-colors">168 <User className="h-5 w-5" />169 </Button>170 </TooltipTrigger>171 <TooltipContent><p>Mon compte</p></TooltipContent>172 </Tooltip>173 )}174 <div className="h-8 w-px bg-border/50 mx-1" />175 <ThemeColorPicker />176 <ThemeToggle />177 </div>178 </div>179 </header>180181 <MarketTicker />182183 {/* Main Content */}184 <main className="flex-1 overflow-y-auto relative z-10 pb-32" data-testid="main-content">185 <div className="container mx-auto px-4 py-6 max-w-5xl">186 {!hasSearched && <EmptyState onQuestionClick={handleSearch} selectedModel={selectedModel} onModelChange={handleModelChange} />}187188 {chatMutation.isError && (189 <ErrorState error={chatMutation.error?.message || "An error occurred"} onRetry={handleRetry} />190 )}191192 {chatMutation.isPending && !streamingAnswer && !chatMutation.isError && <SearchingWidget />}193194 {conversationHistory.length > 0 && <ConversationHistory history={conversationHistory} />}195196 {!chatMutation.isError && (searchQueries.length > 0 || streamingAnswer) && (197 <>198 {streamingAnswer && !chatMutation.isPending && (199 <div className="mb-6 flex justify-end gap-3" data-testid="share-button-container">200 <DownloadButtons question={currentQuestion} answer={streamingAnswer} contentRef={resultsRef} figureUrls={figureUrls} />201 <ShareButton question={currentQuestion} answer={streamingAnswer} toolResults={toolResults} sources={sources} customPythonFigures={customPythonFigures} />202 </div>203 )}204205 <div className="space-y-6 pb-6" data-testid="results-container" ref={resultsRef}>206 <ThinkingWidget content={thinkingContent} isStreaming={chatMutation.isPending} />207 <StreamingAnswer208 content={streamingAnswer}209 sources={sources}210 isStreaming={chatMutation.isPending}211 searchQueries={searchQueries}212 statusMessage={statusMessage}213 currentSearchIndex={currentSearchIndex}214 figureRegistry={figureRegistry}215 figureUrls={figureUrls}216 />217218 <AgentSteps steps={agentSteps} />219 <ToolResults toolResults={toolResults} pythonCode={pythonCode} />220221 {monteCarloResults && (222 <div data-chart data-chart-title="Monte Carlo Simulation Results">223 <MonteCarloResults pythonCode={pythonCode} results={monteCarloResults} />224 </div>225 )}226227 {optionsPricingResults && (228 <div className="max-w-5xl mx-auto mb-8" data-chart data-chart-title="Options Pricing Analysis">229 <OptionsPricingCard data={optionsPricingResults} />230 </div>231 )}232233 {customPythonFigures.length > 0 && (234 <div className="max-w-5xl mx-auto mb-8" data-chart data-chart-title="Custom Python Analysis Gallery">235 <CustomPythonFigure figureBatches={customPythonFigures} />236 </div>237 )}238239 {sources.length > 0 && !chatMutation.isPending && (240 <div className="max-w-5xl mx-auto" data-testid="web-results-section">241 <h2 className="text-2xl font-semibold mb-6" data-testid="text-web-results-heading">Web Results</h2>242 <div className="space-y-4" data-testid="web-results-list">243 {sources.map((result, index) => (244 <ResultCard key={result.url} result={result} index={index} />245 ))}246 </div>247 </div>248 )}249 </div>250 </>251 )}252 </div>253 </main>254255 {/* Search Bar - Fixed at bottom */}256 <div className="fixed bottom-0 left-0 right-0 z-40 border-t border-border bg-background/95 backdrop-blur-sm">257 <div className="container mx-auto px-4 py-3 max-w-5xl">258 <SearchBar onSearch={handleSearch} isLoading={chatMutation.isPending} placeholder="Posez votre question sur les marches financiers..." selectedModel={selectedModel} onModelChange={handleModelChange} />259 </div>260 </div>261 </div>262 );263}264