import { useEffect, useState } from 'react'; import { ToolCall } from '../types'; import './ToolCallCard.css'; interface ToolCallCardProps { toolCall: ToolCall; } /** One-line summary shown in the collapsed header so results are visible at a glance */ function CollapsedSummary({ toolCall }: { toolCall: ToolCall }) { const r = toolCall.result; if (!r) return null; if (toolCall.tool === 'analyze_image') { const name = r.full_name ?? r.diagnosis; const pct = r.confidence != null ? `${Math.round(r.confidence * 100)}%` : null; if (name) return ( {name}{pct ? ` β€” ${pct}` : ''} ); } if (toolCall.tool === 'compare_images') { const key = r.status_label ?? 'STABLE'; const cfg = STATUS_CONFIG[key] ?? { emoji: 'βšͺ', label: key }; return ( {cfg.emoji} {cfg.label} ); } return null; } export function ToolCallCard({ toolCall }: ToolCallCardProps) { // Auto-expand when the tool completes so results are immediately visible. // User can collapse manually afterwards. const [expanded, setExpanded] = useState(false); useEffect(() => { if (toolCall.status === 'complete') setExpanded(true); }, [toolCall.status]); const isLoading = toolCall.status === 'calling'; const isError = toolCall.status === 'error'; const icon = toolCall.tool === 'compare_images' ? 'πŸ”„' : 'πŸ”¬'; const label = toolCall.tool.replace(/_/g, ' '); return (
{expanded && !isLoading && toolCall.result && (
{toolCall.tool === 'analyze_image' && ( )} {toolCall.tool === 'compare_images' && ( )} {toolCall.tool !== 'analyze_image' && toolCall.tool !== 'compare_images' && ( )}
)}
); } /* ─── analyze_image renderer ─────────────────────────────────────────────── */ function AnalyzeImageResult({ result }: { result: ToolCall['result'] }) { if (!result) return null; const hasClassifier = result.diagnosis != null; const topPrediction = result.all_predictions?.[0]; const otherPredictions = result.all_predictions?.slice(1) ?? []; const confidence = result.confidence ?? topPrediction?.probability ?? 0; const pct = Math.round(confidence * 100); const statusColor = pct >= 70 ? '#ef4444' : pct >= 40 ? '#f59e0b' : '#22c55e'; return (
{result.image_url && ( Analyzed lesion )}
{hasClassifier ? ( <>

{result.full_name ?? result.diagnosis}

Confidence: {pct}%

) : (

Visual assessment complete β€” classifier unavailable

)}
{hasClassifier && otherPredictions.length > 0 && ( )}
); } /* ─── compare_images renderer ────────────────────────────────────────────── */ const STATUS_CONFIG: Record = { STABLE: { label: 'Stable', color: '#22c55e', emoji: '🟒' }, MINOR_CHANGE: { label: 'Minor Change', color: '#f59e0b', emoji: '🟑' }, SIGNIFICANT_CHANGE: { label: 'Significant Change', color: '#ef4444', emoji: 'πŸ”΄' }, IMPROVED: { label: 'Improved', color: '#3b82f6', emoji: 'πŸ”΅' }, }; function CompareImagesResult({ result }: { result: ToolCall['result'] }) { if (!result) return null; const statusKey = result.status_label ?? 'STABLE'; const status = STATUS_CONFIG[statusKey] ?? { label: statusKey, color: '#6b7280', emoji: 'βšͺ' }; const featureChanges = Object.entries(result.feature_changes ?? {}); return (
Status: {status.label} {status.emoji}
{featureChanges.length > 0 && (
    {featureChanges.map(([name, vals]) => { const delta = vals.curr - vals.prev; const sign = delta > 0 ? '+' : ''; return (
  • {name} 0.1 ? '#f59e0b' : '#6b7280' }}> {sign}{(delta * 100).toFixed(1)}%
  • ); })}
)} {result.summary && (

{result.summary}

)}
); } /* ─── Generic (unknown tool) renderer ───────────────────────────────────── */ function GenericResult({ result }: { result: ToolCall['result'] }) { return (
      {JSON.stringify(result, null, 2)}
    
); }