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 && (

)}
{hasClassifier ? (
<>
{result.full_name ?? result.diagnosis}
Confidence: {pct}%
>
) : (
Visual assessment complete β classifier unavailable
)}
{hasClassifier && otherPredictions.length > 0 && (
{otherPredictions.map(p => (
-
{p.full_name ?? p.class}
{Math.round(p.probability * 100)}%
))}
)}
);
}
/* βββ 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)}
);
}