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%
11.9 KB · 321 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/pages/slides-generator.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, useEffect } from "react";18import { Button } from "@/components/ui/button";19import { Input } from "@/components/ui/input";20import { Label } from "@/components/ui/label";21import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";22import { ArrowLeft, Presentation, Loader2, FileCheck, Sparkles } from "lucide-react";23import { useLocation } from "wouter";24import { useToast } from "@/hooks/use-toast";2526export default function SlidesGenerator() {27  const [, setLocation] = useLocation();28  const { toast } = useToast();29  const [authorName, setAuthorName] = useState("");30  const [companyName, setCompanyName] = useState("VQuant");31  const [question, setQuestion] = useState("");32  const [answer, setAnswer] = useState("");33  const [figureUrls, setFigureUrls] = useState<string[]>([]);34  const [isGenerating, setIsGenerating] = useState(false);35  const [isGenerated, setIsGenerated] = useState(false);3637  useEffect(() => {38    // Récupérer le contenu depuis sessionStorage39    const storedContent = sessionStorage.getItem('slidesContent');40    if (storedContent) {41      try {42        const { question: q, answer: a, figureUrls: f } = JSON.parse(storedContent);43        setQuestion(q);44        setAnswer(a);45        if (f) setFigureUrls(f);46      } catch (error) {47        console.error('Error parsing slides content:', error);48        toast({49          title: "Erreur",50          description: "Impossible de charger le contenu. Retournez à la page principale.",51          variant: "destructive",52        });53      }54    } else {55      toast({56        title: "Aucun contenu",57        description: "Aucun contenu à convertir. Retournez à la page principale.",58        variant: "destructive",59      });60    }61  }, []);6263  const handleGenerateSlides = async () => {64    if (!authorName.trim()) {65      toast({66        title: "Nom requis",67        description: "Veuillez entrer votre nom en tant qu'auteur",68        variant: "destructive",69      });70      return;71    }7273    setIsGenerating(true);7475    try {76      console.log('[SlidesGenerator] Sending request to generate Beamer PDF...');7778      const response = await fetch('/api/convert-to-slides', {79        method: 'POST',80        headers: {81          'Content-Type': 'application/json',82        },83        body: JSON.stringify({84          question,85          answer,86          author: authorName,87          company: companyName,88          figureUrls,89        }),90      });9192      console.log('[SlidesGenerator] Response status:', response.status);93      console.log('[SlidesGenerator] Response content-type:', response.headers.get('content-type'));9495      // Check if response is an error (JSON)96      const contentType = response.headers.get('content-type');97      if (contentType?.includes('application/json')) {98        const errorData = await response.json();99        throw new Error(errorData.error || 'Failed to generate slides');100      }101102      // Check if response is OK103      if (!response.ok) {104        throw new Error(`Server error: ${response.status} ${response.statusText}`);105      }106107      // Response should be PDF blob108      console.log('[SlidesGenerator] Downloading PDF blob...');109      const blob = await response.blob();110      console.log('[SlidesGenerator] PDF blob size:', blob.size, 'bytes');111112      if (blob.size === 0) {113        throw new Error('PDF is empty');114      }115116      // Download the PDF117      const url = URL.createObjectURL(blob);118      const a = document.createElement('a');119      a.href = url;120      a.download = `VQuant-Presentation-${Date.now()}.pdf`;121      document.body.appendChild(a);122      a.click();123      document.body.removeChild(a);124      URL.revokeObjectURL(url);125126      console.log('[SlidesGenerator] ✓ PDF downloaded successfully');127128      toast({129        title: "Présentation générée!",130        description: "Votre présentation Beamer PDF a été téléchargée avec succès!",131      });132133      // Mark as generated for UI update134      setIsGenerated(true);135    } catch (error) {136      console.error('[SlidesGenerator] Error generating slides:', error);137      toast({138        title: "Erreur de génération",139        description: error instanceof Error ? error.message : "Une erreur s'est produite lors de la génération des slides. Veuillez réessayer.",140        variant: "destructive",141      });142    } finally {143      setIsGenerating(false);144    }145  };146147  const handleGenerateAnother = () => {148    setIsGenerated(false);149    setAuthorName("");150    sessionStorage.removeItem('slidesContent');151  };152153  return (154    <div className="min-h-screen bg-background">155      <div className="container mx-auto px-4 py-8 max-w-4xl">156        <Button157          variant="ghost"158          onClick={() => setLocation('/')}159          className="mb-6"160        >161          <ArrowLeft className="w-4 h-4 mr-2" />162          Retour163        </Button>164165        {!isGenerated ? (166          <Card>167            <CardHeader>168              <CardTitle className="flex items-center gap-2">169                <Presentation className="w-6 h-6 text-primary" />170                Générateur de Présentation171              </CardTitle>172              <CardDescription>173                Convertissez votre analyse en présentation reveal.js professionnelle avec Claude AI174              </CardDescription>175            </CardHeader>176            <CardContent className="space-y-6">177              {/* Preview of content */}178              <div className="space-y-2">179                <Label>Contenu à convertir</Label>180                <div className="p-4 bg-muted/50 rounded-lg max-h-48 overflow-y-auto">181                  <p className="font-semibold text-sm mb-2">Question :</p>182                  <p className="text-sm mb-4">{question}</p>183                  <p className="font-semibold text-sm mb-2">Réponse :</p>184                  <p className="text-sm line-clamp-6">{answer}</p>185                </div>186              </div>187188              {/* Author name input */}189              <div className="space-y-2">190                <Label htmlFor="author">Nom de l'auteur *</Label>191                <Input192                  id="author"193                  placeholder="Votre nom"194                  value={authorName}195                  onChange={(e) => setAuthorName(e.target.value)}196                  className="max-w-md"197                />198              </div>199200              {/* Company name input */}201              <div className="space-y-2">202                <Label htmlFor="company">Société / Organisation</Label>203                <Input204                  id="company"205                  placeholder="Nom de votre société"206                  value={companyName}207                  onChange={(e) => setCompanyName(e.target.value)}208                  className="max-w-md"209                />210              </div>211212              {/* Generate button */}213              <Button214                onClick={handleGenerateSlides}215                disabled={isGenerating || !authorName.trim()}216                className="w-full sm:w-auto gap-2"217                size="lg"218              >219                {isGenerating ? (220                  <>221                    <Loader2 className="w-4 h-4 animate-spin" />222                    Génération en cours avec Claude AI...223                  </>224                ) : (225                  <>226                    <Presentation className="w-4 h-4" />227                    Générer la Présentation228                  </>229                )}230              </Button>231232              {/* Info box */}233              <div className="mt-6 p-4 bg-blue-500/10 border border-blue-500/20 rounded-lg">234                <p className="text-sm text-muted-foreground">235                  <strong className="text-foreground">🎯 Comment ça marche :</strong>236                  <br />237                  1. Claude Opus 4.8 analyse votre contenu et extrait les points clés238                  <br />239                  2. Il structure l'analyse en slides professionnelles240                  <br />241                  3. LaTeX Beamer compile le tout en PDF de haute qualité242                  <br />243                  4. Le PDF est téléchargé automatiquement (30-90 secondes)244                  <br />245                  <br />246                  <strong className="text-foreground">✨ Résultat :</strong> Présentation PDF de qualité académique/institutionnelle!247                </p>248              </div>249            </CardContent>250          </Card>251        ) : (252          <Card className="border-2 border-green-500/20">253            <CardHeader>254              <CardTitle className="flex items-center gap-2 text-green-600 dark:text-green-500">255                <FileCheck className="w-6 h-6" />256                Présentation PDF Générée avec Succès!257              </CardTitle>258              <CardDescription>259                Votre présentation Beamer PDF professionnelle a été téléchargée260              </CardDescription>261            </CardHeader>262            <CardContent className="space-y-4">263              <div className="p-6 bg-gradient-to-br from-green-500/10 to-emerald-500/10 border-2 border-green-500/20 rounded-xl text-center">264                <Sparkles className="w-12 h-12 text-green-500 mx-auto mb-4" />265                <p className="text-lg font-semibold mb-2">Le PDF est dans vos téléchargements!</p>266                <p className="text-sm text-muted-foreground">267                  Ouvrez-le avec n'importe quel lecteur PDF (Adobe, Preview, etc.)268                </p>269              </div>270271              <div className="p-4 bg-blue-500/10 border border-blue-500/20 rounded-lg">272                <p className="text-sm">273                  <strong className="text-foreground">✨ Votre présentation contient :</strong>274                </p>275                <ul className="text-sm space-y-1 mt-2 ml-4">276                  <li>• Slide de titre avec vos informations</li>277                  <li>• Sommaire automatique</li>278                  <li>• Slides de contenu structurées</li>279                  <li>• Métriques clés mises en évidence</li>280                  <li>• Slide de conclusion</li>281                  <li>• Design professionnel LaTeX Beamer</li>282                </ul>283              </div>284285              <div className="p-4 bg-purple-500/10 border border-purple-500/20 rounded-lg">286                <p className="text-sm">287                  <strong className="text-foreground">🎯 Technologie utilisée :</strong>288                </p>289                <ul className="text-sm space-y-1 mt-2 ml-4">290                  <li>• <strong>Claude Opus 4.8</strong> - Structure intelligente du contenu</li>291                  <li>• <strong>LaTeX Beamer</strong> - Rendu professionnel de qualité académique</li>292                  <li>• <strong>Theme Madrid</strong> - Design moderne et épuré</li>293                </ul>294              </div>295296              <Button297                variant="default"298                size="lg"299                onClick={handleGenerateAnother}300                className="w-full gap-2"301              >302                <Presentation className="w-4 h-4" />303                Générer une Nouvelle Présentation304              </Button>305306              <Button307                variant="outline"308                onClick={() => setLocation('/')}309                className="w-full"310              >311                <ArrowLeft className="w-4 h-4 mr-2" />312                Retour à l'accueil313              </Button>314            </CardContent>315          </Card>316        )}317      </div>318    </div>319  );320}321