1import { useState } from 'react';2import { ChevronDown } from 'lucide-react';3import type { ToolCallView } from '@/lib/types';4import { CodeBlock } from '@/components/chat/code-block';5import { ArtifactChips } from './artifact-chips';6import { cn } from '@/lib/cn';78export function PythonExecCard({ call, conversationId }: { call: ToolCallView; conversationId: string }) {9 const p = call.payload as { code?: string; description?: string; stdout?: string; stderr?: string; exit_code?: number; artifacts?: ToolCallView['artifacts'] };10 const [showCode, setShowCode] = useState(false);11 const images = (p.artifacts || call.artifacts || []).filter((a) => a.type === 'image');12 const others = (p.artifacts || call.artifacts || []).filter((a) => a.type !== 'image');13 return (14 <div className="space-y-2">15 {p.description && <p className="text-sm text-neutral-muted">{p.description}</p>}16 <button onClick={() => setShowCode((v) => !v)} className="text-sm text-uqo-blue inline-flex items-center gap-1 min-h-[44px]">17 <ChevronDown size={16} className={cn('transition-transform', showCode && 'rotate-180')} /> {showCode ? 'Masquer le code' : 'Voir le code'}18 </button>19 {showCode && p.code && <CodeBlock code={p.code} lang="python" conversationId={conversationId} />}20 {(p.stdout || p.stderr) && (21 <div className="rounded-lg bg-neutral-surface p-3 font-mono text-xs overflow-x-auto scroll-thin">22 {p.stdout && <pre className="whitespace-pre-wrap">{p.stdout}</pre>}23 {p.stderr && <pre className="whitespace-pre-wrap text-semantic-error mt-2">{p.stderr}</pre>}24 </div>25 )}26 {images.map((a) => (27 <figure key={a.file_id} className="rounded-xl border border-neutral-line overflow-hidden bg-white">28 <img src={`/api/v1/files/${a.file_id}`} alt={a.filename || 'graphique'} className="w-full max-h-[420px] object-contain" loading="lazy" />29 </figure>30 ))}31 <ArtifactChips artifacts={others} />32 </div>33 );34}35