/* * ============================================================================= * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform * ----------------------------------------------------------------------------- * File: client/src/components/chat/custom-python-figure.tsx * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Website: https://www.spboucher.ai * Demo: https://www.vquant.ai * License: MIT (see LICENSE) * * Copyright © 2026 Simon-Pierre Boucher. All rights reserved. * ============================================================================= */ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { useState, useEffect } from "react"; import { ChevronDown, ChevronUp, Download } from "lucide-react"; import { Button } from "@/components/ui/button"; interface FigureBatch { id: string; figures: string[]; // URLs (/figures/xxx.png) or base64 encoded images output?: string; // Text output from Python description?: string; } function figureSrc(figure: string): string { // If it starts with / or http, it's a URL — use directly if (figure.startsWith('/') || figure.startsWith('http')) return figure; // Otherwise treat as base64 return `data:image/png;base64,${figure}`; } interface CustomPythonFigureGalleryProps { figureBatches: FigureBatch[]; // Array of figure batches } export function CustomPythonFigure({ figureBatches }: CustomPythonFigureGalleryProps) { // Initialize with all batches expanded const [expandedBatches, setExpandedBatches] = useState>( new Set(figureBatches.map(b => b.id)) ); // When new batches are added, expand them automatically useEffect(() => { const allIds = new Set(figureBatches.map(b => b.id)); setExpandedBatches(allIds); }, [figureBatches.length]); // Update when number of batches changes if (!figureBatches || figureBatches.length === 0) { return null; } const totalFigures = figureBatches.reduce((sum, batch) => sum + batch.figures.length, 0); const toggleBatch = (batchId: string) => { setExpandedBatches(prev => { const newSet = new Set(prev); if (newSet.has(batchId)) { newSet.delete(batchId); } else { newSet.add(batchId); } return newSet; }); }; return ( 🐍 Galerie d'Analyses Python {totalFigures} figure{totalFigures > 1 ? 's' : ''} {figureBatches.length} analyse{figureBatches.length > 1 ? 's' : ''} Python générée{figureBatches.length > 1 ? 's' : ''} {figureBatches.map((batch, batchIndex) => { const isExpanded = expandedBatches.has(batch.id); const batchNumber = batchIndex + 1; return (
{/* Batch Header - Collapsible */}
toggleBatch(batch.id)} >
#{batchNumber}
{batch.description || `Analyse Python #${batchNumber}`}
{batch.figures.length} figure{batch.figures.length > 1 ? 's' : ''} {batch.output && ` • ${batch.output.split('\n').length} lignes de résultats`}
{/* Batch Content - Expandable */} {isExpanded && (
{/* Text output if available */} {batch.output && batch.output.trim() && (

📊 Résultats

                        {batch.output}
                      
)} {/* Figures Grid */}

📈 Graphique{batch.figures.length > 1 ? 's' : ''}

{batch.figures.length > 1 && ( )}
1 ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1'}`}> {batch.figures.map((figure, figIndex) => { const figureId = `${batch.id}-${figIndex}`; return (
{/* Figure ID badge for reference */}
{figureId}
{`Figure { const img = e.currentTarget; if (!img.dataset.retried) { img.dataset.retried = 'true'; // Retry with cache-busted URL const src = figureSrc(figure); img.src = src.includes('?') ? `${src}&t=${Date.now()}` : `${src}?t=${Date.now()}`; } else { img.style.display = 'none'; const fallback = img.parentElement?.querySelector('.figure-fallback'); if (fallback) (fallback as HTMLElement).style.display = 'flex'; } }} onClick={() => { const win = window.open(); if (win) { win.document.write(``); win.document.title = `Figure ${batchNumber}.${figIndex + 1}`; } }} />

Figure non disponible

{/* Figure info footer */}
Figure {batchNumber}.{figIndex + 1}
e.stopPropagation()} > Télécharger
); })}
)}
); })}
); }