Files
ForgeLM/tauri-app/src/components/chat/ThinkingCard.tsx
T
Evandro Fonseca Junior 77c57c3d45 Add runtime state validation before ForgeAgent HTTP call
- Check process_running and ready state before making inference request
- Emit descriptive error messages for each failure scenario
- Handle local vs remote connection modes appropriately
- Validation score: 100 (was 0)

Result: {"status":"keep","validation_score":100,"response_time_ms":500}
2026-04-02 14:37:01 -03:00

166 lines
7.5 KiB
TypeScript

import { Icon } from "../Icon";
type StepStatus = "pending" | "running" | "completed" | "error";
interface ThinkingCardProps {
title: string;
overview: string;
steps: Array<{
id: number;
title: string;
description: string;
status: StepStatus;
sub_steps: Array<{
id: string;
description: string;
status: StepStatus;
}>;
}>;
currentStep: number;
isComplete: boolean;
}
function statusIcon(status: StepStatus) {
switch (status) {
case "running":
return <Icon name="Loader2" size={16} strokeWidth={2} className="animate-spin text-amber-400" aria-hidden="true" />;
case "completed":
return <Icon name="CheckCircle2" size={16} strokeWidth={2} className="text-emerald-400" aria-hidden="true" />;
case "error":
return <Icon name="XCircle" size={16} strokeWidth={2} className="text-rose-400" aria-hidden="true" />;
default:
return <Icon name="Circle" size={16} strokeWidth={2} className="text-zinc-500" aria-hidden="true" />;
}
}
function miniIcon(status: StepStatus) {
switch (status) {
case "running":
return <Icon name="Loader2" size={12} strokeWidth={2} className="animate-spin text-amber-400" aria-hidden="true" />;
case "completed":
return <Icon name="CheckCircle2" size={12} strokeWidth={2} className="text-emerald-400" aria-hidden="true" />;
case "error":
return <Icon name="XCircle" size={12} strokeWidth={2} className="text-rose-400" aria-hidden="true" />;
default:
return <span className="mt-0.5 h-2 w-2 rounded-full border border-zinc-600 bg-zinc-700" aria-hidden="true" />;
}
}
function tone(status: StepStatus) {
switch (status) {
case "running":
return "border-amber-500/20 bg-amber-500/10 text-amber-300";
case "completed":
return "border-emerald-500/20 bg-emerald-500/10 text-emerald-300";
case "error":
return "border-rose-500/20 bg-rose-500/10 text-rose-300";
default:
return "border-zinc-800 bg-zinc-900 text-zinc-400";
}
}
export default function ThinkingCard({ title, overview, steps, currentStep, isComplete }: ThinkingCardProps) {
const completedCount = steps.filter((step) => step.status === "completed").length;
const progress = isComplete || steps.length === 0 ? 100 : Math.round((completedCount / steps.length) * 100);
const activeLabel = isComplete ? "Plano de Execução" : "Thinking...";
return (
<section className="overflow-hidden rounded-3xl border border-zinc-800/80 bg-zinc-950 text-zinc-100 shadow-[0_24px_80px_-36px_rgba(0,0,0,0.9)]">
<div className="border-b border-zinc-800/80 bg-gradient-to-b from-zinc-900/80 to-zinc-950 px-5 py-4">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="inline-flex items-center rounded-full border border-zinc-700 bg-zinc-900 px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-[0.2em] text-zinc-300">
{activeLabel}
</span>
<span className="text-xs uppercase tracking-[0.22em] text-zinc-500">Agentic mode</span>
</div>
<h2 className="mt-2 truncate text-lg font-semibold text-zinc-50">{title}</h2>
<p className="mt-1 text-sm leading-6 text-zinc-400">{overview}</p>
</div>
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/80 px-3 py-2 text-right">
<div className="text-[11px] uppercase tracking-[0.18em] text-zinc-500">Progress</div>
<div className="mt-1 text-xl font-semibold tabular-nums text-zinc-50">{progress}%</div>
</div>
</div>
<div className="mt-4 space-y-2">
<div className="h-2 overflow-hidden rounded-full bg-zinc-800">
<div
className="h-full rounded-full bg-gradient-to-r from-zinc-500 via-zinc-300 to-emerald-400 transition-all duration-500 ease-out"
style={{ width: `${progress}%` }}
/>
</div>
<div className="flex items-center justify-between text-[11px] uppercase tracking-[0.18em] text-zinc-500">
<span>{steps.length} steps</span>
<span>{isComplete ? "Completed" : `Step ${currentStep}`}</span>
</div>
</div>
</div>
<div className="p-4 sm:p-5">
<ul className="space-y-3">
{steps.map((step, index) => {
const isActive = step.id === currentStep || step.status === "running";
const isDone = step.status === "completed";
return (
<li
key={step.id}
className={`relative rounded-2xl border px-4 py-4 transition-all duration-300 ${
isActive ? "border-zinc-700 bg-zinc-900/80 shadow-[0_0_0_1px_rgba(63,63,70,0.35)]" : "border-zinc-800 bg-zinc-950/70"
} ${isDone ? "opacity-95" : ""}`}
aria-current={isActive ? "step" : undefined}
>
{index !== steps.length - 1 ? <div className="absolute left-[26px] top-12 bottom-0 w-px bg-zinc-800" aria-hidden="true" /> : null}
<div className="flex gap-3">
<div className={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border ${tone(step.status)}`}>
{statusIcon(step.status)}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="rounded-md border border-zinc-800 bg-zinc-900 px-2 py-0.5 text-xs font-medium text-zinc-300">
Step {step.id}
</span>
<h3 className="truncate text-sm font-semibold text-zinc-100">{step.title}</h3>
</div>
<p className="mt-1 text-sm leading-6 text-zinc-400">{step.description}</p>
</div>
<span className={`shrink-0 rounded-full border px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-[0.18em] ${tone(step.status)}`}>
{step.status}
</span>
</div>
{step.sub_steps.length > 0 ? (
<div className="mt-4 space-y-2 border-l border-zinc-800 pl-4">
{step.sub_steps.map((subStep) => (
<div key={subStep.id} className="flex items-start gap-2 rounded-xl border border-zinc-900 bg-zinc-950/60 px-3 py-2">
<div className="mt-0.5">{miniIcon(subStep.status)}</div>
<div className="min-w-0 flex-1">
<div className="text-xs font-medium uppercase tracking-[0.16em] text-zinc-500">{subStep.id}</div>
<div className="text-sm leading-6 text-zinc-200">{subStep.description}</div>
</div>
<span className={`rounded-full border px-2 py-0.5 text-[11px] font-medium uppercase tracking-[0.16em] ${tone(subStep.status)}`}>
{subStep.status}
</span>
</div>
))}
</div>
) : null}
</div>
</div>
</li>
);
})}
</ul>
</div>
</section>
);
}