Python 64.6%
TypeScript 33.7%
CSS 0.8%
1import { useState } from 'react';2import {3 AlertTriangle, BarChart3, BookOpen, Calculator, Check, ChevronDown, FileEdit, FileSearch, FileSpreadsheet,4 FileText, Globe, ListChecks, Loader2, Ruler, Sigma, TerminalSquare,5} from 'lucide-react';6import type { ToolCallView } from '@/lib/types';7import { fmtDuration } from '@/lib/format';8import { cn } from '@/lib/cn';9import { ToolCard } from '@/components/tools/tool-card';1011export const TOOL_META: Record<string, { label: string; icon: typeof Globe; tint: string }> = {12 search_course_content: { label: 'Matériel du cours', icon: BookOpen, tint: 'bg-uqo-blue-light text-uqo-blue' },13 execute_python: { label: 'Calcul Python', icon: TerminalSquare, tint: 'bg-[#e8f1ff] text-[#1d4ed8]' },14 create_excel: { label: 'Classeur Excel', icon: FileSpreadsheet, tint: 'bg-[#e3f3e0] text-[#217346]' },15 edit_excel: { label: 'Modification du classeur Excel', icon: FileEdit, tint: 'bg-[#e3f3e0] text-[#217346]' },16 inspect_excel: { label: 'Lecture du classeur Excel', icon: FileSearch, tint: 'bg-[#e3f3e0] text-[#217346]' },17 web_search: { label: 'Recherche web', icon: Globe, tint: 'bg-[#fff4e0] text-[#b45309]' },18 analyze_file: { label: 'Analyse du fichier déposé', icon: FileSearch, tint: 'bg-uqo-blue-light text-uqo-blue' },19 generate_quiz: { label: 'Quiz', icon: ListChecks, tint: 'bg-[#f3e8ff] text-[#7e22ce]' },20 financial_calc: { label: 'Calcul financier', icon: Calculator, tint: 'bg-neutral-surface text-neutral-text' },21 appraisal_calc: { label: "Calcul d'évaluation", icon: Sigma, tint: 'bg-neutral-surface text-neutral-text' },22 unit_convert: { label: "Conversion d'unités", icon: Ruler, tint: 'bg-neutral-surface text-neutral-text' },23 make_chart: { label: 'Graphique', icon: BarChart3, tint: 'bg-[#e8f1ff] text-[#1d4ed8]' },24 create_docx: { label: 'Document Word', icon: FileText, tint: 'bg-[#e6ecfb] text-[#2b579a]' },25};2627const COLLAPSED_BY_DEFAULT = new Set(['financial_calc', 'search_course_content', 'unit_convert', 'inspect_excel']);2829export function ToolCallTimeline({ calls, conversationId }: { calls: ToolCallView[]; conversationId: string }) {30 const [open, setOpen] = useState<Record<string, boolean>>({});31 if (!calls.length) return null;32 return (33 <ol className="my-2 space-y-2">34 {calls.map((c) => {35 const meta = TOOL_META[c.name] || { label: c.name, icon: Calculator, tint: 'bg-neutral-surface text-neutral-text' };36 const Icon = meta.icon;37 const running = c.status === 'running';38 const error = c.status === 'error';39 const expanded = open[c.id] ?? !COLLAPSED_BY_DEFAULT.has(c.name);40 return (41 <li key={c.id} className={cn('rounded-2xl border bg-white shadow-card overflow-hidden animate-fadein', error ? 'border-semantic-error/40' : 'border-neutral-line')}>42 <button43 onClick={() => setOpen((o) => ({ ...o, [c.id]: !expanded }))}44 className="w-full flex items-center gap-3 px-3 py-2.5 text-left min-h-[52px]"45 aria-expanded={expanded}46 >47 <span className={cn('h-9 w-9 rounded-xl inline-flex items-center justify-center shrink-0', error ? 'bg-red-50 text-semantic-error' : meta.tint)}>48 {running ? <Loader2 size={17} className="animate-spin" /> : error ? <AlertTriangle size={17} /> : <Icon size={17} />}49 </span>50 <span className="flex-1 min-w-0">51 <span className="flex items-center gap-2">52 <span className="text-sm font-semibold text-neutral-text truncate">{meta.label}</span>53 {running && <span className="text-[10px] uppercase tracking-wide rounded-md bg-uqo-blue-light text-uqo-blue px-1.5 py-0.5">en cours</span>}54 {error && <span className="text-[10px] uppercase tracking-wide rounded-md bg-red-50 text-semantic-error px-1.5 py-0.5">échec, corrigé par le tuteur</span>}55 </span>56 <span className="block text-xs text-neutral-muted truncate">57 {running ? c.progress || 'En cours…' : error ? 'Voir le détail' : c.summary || c.args_preview}58 </span>59 </span>60 <span className="text-xs text-neutral-muted inline-flex items-center gap-1.5 shrink-0">61 {!running && c.duration_ms > 0 && <span className="tabular-nums">{fmtDuration(c.duration_ms)}</span>}62 {!running && !error && <Check size={14} className="text-uqo-green" />}63 <ChevronDown size={16} className={cn('transition-transform', expanded && 'rotate-180')} />64 </span>65 </button>66 {expanded && !running && (67 <div className="border-t border-neutral-line/80 px-3 py-3 bg-[#fbfcfe]">68 <ToolCard call={c} conversationId={conversationId} />69 </div>70 )}71 </li>72 );73 })}74 </ol>75 );76}77