import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { BookOpen, Download, FileSpreadsheet, FileText, Image as ImageIcon, Pin, Wand2, X } from 'lucide-react'; import { useUI } from '@/stores/ui'; import { api, downloadFile } from '@/lib/api'; import type { UploadedFile } from '@/lib/types'; import { useChat } from '@/stores/chat'; import { fmtBytes } from '@/lib/format'; export function ArtifactsPanel({ conversationId, onClose }: { conversationId: string; onClose?: () => void }) { const { data: files, refetch } = useQuery({ queryKey: ['files', conversationId], queryFn: () => api(`/conversations/${conversationId}/files`) }); const msgs = useChat((s) => s.messages[conversationId] || []); const sources = useMemo(() => { const seen = new Map(); for (const m of msgs) for (const t of m.tool_calls || []) if (t.name === 'search_course_content') { const list = (t.payload as { sources?: { module: string; section: string; url: string; course: string }[] }).sources || []; for (const s of list) seen.set(s.url || s.module + s.section, s); } return [...seen.values()]; }, [msgs]); const setDraft = useUI((s) => s.setDraft); const setPanel = useUI((s) => s.setPanel); const icon = (t: string) => (t === 'xlsx' ? FileSpreadsheet : t === 'image' ? ImageIcon : FileText); const pin = async (f: UploadedFile) => { await api(`/files/${f.file_id}/pin`, { method: 'POST', body: JSON.stringify({ pinned: !f.pinned }) }); refetch(); }; return (

Fichiers et sources

{onClose && }

Fichiers ({files?.length || 0})

{!files?.length &&

Les fichiers déposés et les classeurs produits apparaîtront ici (conservés 7 jours, 30 si épinglés).

}
    {files?.map((f) => { const Icon = icon(f.type); return (
  • {f.filename}{f.kind === 'upload' ? 'déposé' : 'produit'} · {fmtBytes(f.size)} {(f.type === 'xlsx' || f.type === 'csv') && ( )}
  • ); })}

Sources du cours citées ({sources.length})

); }