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%
12.2 KB · 264 lines tsx
Raw Blame History
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