Ir al contenido
Evaluar mi proyecto
Componentes · IADisponible

Resumen de razonamiento

Línea plegable que resume lo que hizo el asistente y se abre en una línea de tiempo de pasos.

  1. Entender la preguntaPedidos con retraso en la semana en curso.
  2. Consultar pedidosFiltrados por fecha de entrega vencida.
  3. Cruzar con existenciasDos de los tres esperan material de bodega.
  4. Redactar la respuestaCada dato queda ligado a su fuente.
  1. Entender la preguntaPedidos con retraso en la semana en curso.
  2. Consultar pedidosFiltrados por fecha de entrega vencida.
  3. Cruzar con existenciasDos de los tres esperan material de bodega.
  4. Redactar la respuestaCada dato queda ligado a su fuente.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

ai-thinking.tsx103 líneas
Descargar
/**
 * Resumen plegable de lo que hizo un asistente antes de responder. Cerrado muestra una
 * línea ("Razonó durante 4 s"); abierto, la lista de pasos como una línea de tiempo. Con
 * `running` el último paso queda activo y el indicador gira. El panel se anima con
 * grid-template-rows, sin alturas fijas. Los colores se sobrescriben con --gcai-ink,
 * --gcai-surface, --gcai-line, --gcai-muted y --gcai-accent.
 *
 *   <AiThinking
 *     summary="Razonó durante 4 s"
 *     steps={[{ title: "Consultar pedidos", detail: "Filtrados por fecha vencida." }]}
 *   />
 */

"use client";

import { useId, useState, type CSSProperties } from "react";

export type AiThinkingStep = { title: string; detail?: string };

export type AiThinkingProps = {
  summary?: string;
  steps?: AiThinkingStep[];
  /** Mientras es true el último paso se marca como en curso. */
  running?: boolean;
  defaultOpen?: boolean;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gckt-root { --gckt-ink: var(--gcai-ink, #ededee); --gckt-muted: var(--gcai-muted, #9b9ca3); --gckt-line: var(--gcai-line, #26272c); --gckt-surface: var(--gcai-surface, #111113); --gckt-accent: var(--gcai-accent, #84b3ff); box-sizing: border-box; width: min(100%, 560px); margin-inline: auto; border: 1px solid var(--gckt-line); border-radius: 14px; background: var(--gckt-surface); color: var(--gckt-ink); font: 14px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.gckt-toggle { display: flex; align-items: center; gap: 12px; width: 100%; padding: 13px 16px; border: 0; border-radius: 14px; background: transparent; color: inherit; font: inherit; font-weight: 500; text-align: left; cursor: pointer; }
.gckt-toggle:focus-visible { outline: 2px solid var(--gckt-accent); outline-offset: -2px; }
.gckt-mark { flex: none; width: 16px; height: 16px; fill: none; stroke: var(--gckt-muted); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.gckt-root[data-running="true"] .gckt-mark { stroke: var(--gckt-accent); animation: gckt-spin .9s linear infinite; }
.gckt-chevron { margin-left: auto; width: 14px; height: 14px; flex: none; fill: none; stroke: var(--gckt-muted); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform .3s cubic-bezier(.16, 1, .3, 1); }
.gckt-toggle[aria-expanded="true"] .gckt-chevron { transform: rotate(180deg); }
.gckt-panel { display: grid; grid-template-rows: 0fr; visibility: hidden; transition: grid-template-rows .4s cubic-bezier(.16, 1, .3, 1), visibility 0s .4s; }
.gckt-panel[data-open="true"] { grid-template-rows: 1fr; visibility: visible; transition: grid-template-rows .4s cubic-bezier(.16, 1, .3, 1), visibility 0s; }
.gckt-clip { min-height: 0; overflow: hidden; }
.gckt-steps { position: relative; margin: 0; padding: 4px 16px 18px 16px; list-style: none; }
.gckt-step { position: relative; display: grid; gap: 1px; padding: 0 0 14px 26px; }
.gckt-step:last-child { padding-bottom: 0; }
.gckt-step::before { content: ""; position: absolute; left: 3px; top: 5px; width: 9px; height: 9px; box-sizing: border-box; border-radius: 50%; background: var(--gckt-ink); }
.gckt-step::after { content: ""; position: absolute; left: 7px; top: 17px; bottom: 2px; width: 1px; background: var(--gckt-line); }
.gckt-step:last-child::after { display: none; }
.gckt-step[data-active="true"]::before { background: var(--gckt-surface); border: 1.5px solid var(--gckt-accent); animation: gckt-pulse 1.3s ease-in-out infinite; }
.gckt-step b { font-weight: 600; }
.gckt-step span { color: var(--gckt-muted); font-size: 13px; }
@keyframes gckt-spin { to { transform: rotate(360deg); } }
@keyframes gckt-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .gckt-root[data-running="true"] .gckt-mark, .gckt-step[data-active="true"]::before { animation: none; } .gckt-panel, .gckt-panel[data-open="true"], .gckt-chevron { transition: none; } }
`;

const DEFAULT_STEPS: AiThinkingStep[] = [
  { title: "Entender la pregunta", detail: "Pedidos con retraso en la semana en curso." },
  { title: "Consultar pedidos", detail: "Filtrados por fecha de entrega vencida." },
  { title: "Cruzar con existencias", detail: "Dos de los tres esperan material de bodega." },
  { title: "Redactar la respuesta", detail: "Cada dato queda ligado a su fuente." },
];

export function AiThinking({
  summary,
  steps = DEFAULT_STEPS,
  running = false,
  defaultOpen = false,
  className,
  style,
}: AiThinkingProps) {
  const panelId = useId();
  const [open, setOpen] = useState(defaultOpen);
  const label = summary ?? (running ? "Pensando…" : "Razonó durante 4 s");

  return (
    <>
      <style>{CSS}</style>
      <div className={["gckt-root", className].filter(Boolean).join(" ")} style={style} data-running={running}>
        <button type="button" className="gckt-toggle" aria-expanded={open} aria-controls={panelId} onClick={() => setOpen((v) => !v)}>
          {running ? (
            <svg className="gckt-mark" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 1.75a6.25 6.25 0 1 0 6.25 6.25" /></svg>
          ) : (
            <svg className="gckt-mark" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.25" /><path d="M5.2 8.2l2 2 3.6-4" /></svg>
          )}
          {label}
          <svg className="gckt-chevron" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 5l4 4 4-4" /></svg>
        </button>
        <div className="gckt-panel" id={panelId} data-open={open}>
          <div className="gckt-clip">
            <ol className="gckt-steps">
              {steps.map((step, i) => (
                <li key={step.title} className="gckt-step" data-active={running && i === steps.length - 1}>
                  <b>{step.title}</b>
                  {step.detail && <span>{step.detail}</span>}
                </li>
              ))}
            </ol>
          </div>
        </div>
      </div>
    </>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/ai-thinking

Ejemplo de uso de Resumen de razonamiento

Después de copiar ai-thinking.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { AiThinking } from "./ai-thinking";

export default function Ejemplo() {
  return <AiThinking summary="Ejemplo" running />;
}

Propiedades de AiThinking

AiThinking acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
summary?stringOpcional.
steps?AiThinkingStep[]Opcional.
running?booleanMientras es true el último paso se marca como en curso.
defaultOpen?booleanOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Resumen de razonamiento con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gcai-ink#ededee
--gcai-muted#9b9ca3
--gcai-line#26272c
--gcai-surface#111113
--gcai-accent#84b3ff

Accesibilidad y rendimiento

El archivo tiene 103 líneas (6 KB) y define 2 animaciones con @keyframes. Esto es lo que hace Resumen de razonamiento por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18

Acerca del producto

Cerrado ocupa una línea; abierto muestra los pasos con su detalle. Con running el último paso queda activo. El panel se anima sin alturas fijas y se oculta de teclado y lectores cuando está cerrado.

Incluye

Un solo archivo React + TypeScript con estilos dentro. Sin Tailwind ni dependencias externas.

  • Pasos y detalle configurables
  • Estado en curso con indicador
  • Panel animado con grid-template-rows
  • Oculto del teclado cuando está cerrado
  • Respeta prefers-reduced-motion