SPB Git

spb/uqo-chat

Public
3.3 KB · 61 lines tsx
1import { useState } from 'react';2import { BookOpen, Calculator, ChevronDown, FileSearch, FileSpreadsheet, Globe, ListChecks, Loader2, TerminalSquare, AlertTriangle, Check } from 'lucide-react';3import type { ToolCallView } from '@/lib/types';4import { fmtDuration } from '@/lib/format';5import { cn } from '@/lib/cn';6import { ToolCard } from '@/components/tools/tool-card';78const META: Record<string, { label: string; icon: typeof Globe }> = {9  search_course_content: { label: 'Recherche dans le matériel du cours', icon: BookOpen },10  execute_python: { label: 'Exécution du calcul Python', icon: TerminalSquare },11  create_excel: { label: 'Création du fichier Excel', icon: FileSpreadsheet },12  web_search: { label: 'Recherche sur le web', icon: Globe },13  analyze_file: { label: 'Analyse du fichier déposé', icon: FileSearch },14  generate_quiz: { label: 'Préparation du quiz', icon: ListChecks },15  financial_calc: { label: 'Calcul financier', icon: Calculator },16};1718export function ToolCallTimeline({ calls, conversationId }: { calls: ToolCallView[]; conversationId: string }) {19  const [open, setOpen] = useState<Record<string, boolean>>({});20  if (!calls.length) return null;21  return (22    <ol className="my-2 space-y-1.5">23      {calls.map((c) => {24        const meta = META[c.name] || { label: c.name, icon: Calculator };25        const Icon = meta.icon;26        const running = c.status === 'running';27        const expanded = open[c.id] ?? (c.name !== 'financial_calc' && c.name !== 'search_course_content');28        return (29          <li key={c.id} className="rounded-xl border border-neutral-line bg-white shadow-card overflow-hidden animate-fadein">30            <button31              onClick={() => setOpen((o) => ({ ...o, [c.id]: !expanded }))}32              className="w-full flex items-center gap-3 px-3 py-2.5 text-left min-h-[44px]"33              aria-expanded={expanded}34            >35              <span className={cn('h-8 w-8 rounded-lg inline-flex items-center justify-center shrink-0', running ? 'bg-uqo-blue-light text-uqo-blue' : c.status === 'error' ? 'bg-red-50 text-semantic-error' : 'bg-uqo-blue-light text-uqo-blue')}>36                {running ? <Loader2 size={16} className="animate-spin" /> : c.status === 'error' ? <AlertTriangle size={16} /> : <Icon size={16} />}37              </span>38              <span className="flex-1 min-w-0">39                <span className="block text-sm font-medium text-neutral-text truncate">{meta.label}</span>40                <span className="block text-xs text-neutral-muted truncate">41                  {running ? c.progress || 'En cours…' : c.summary || c.args_preview}42                </span>43              </span>44              <span className="text-xs text-neutral-muted inline-flex items-center gap-1 shrink-0">45                {!running && c.duration_ms > 0 && fmtDuration(c.duration_ms)}46                {!running && c.status === 'ok' && <Check size={14} className="text-uqo-green" />}47                <ChevronDown size={16} className={cn('transition-transform', expanded && 'rotate-180')} />48              </span>49            </button>50            {expanded && !running && (51              <div className="border-t border-neutral-line px-3 py-3">52                <ToolCard call={c} conversationId={conversationId} />53              </div>54            )}55          </li>56        );57      })}58    </ol>59  );60}61
Files · cd08d441f7c277e8fedd6f3d3be82f02b44553ba
Menu

Theme