折叠/步骤
Thinking Process
Composed thinking step with status, content blocks, and sub-steps
思考中生成的内容
明确研究目标与边界
明确研究目标与边界,我将调用知识和搜索工具。
调取知识:
我正在调取知识库资料
📄
AI发展趋势.pdf📄
AI发展历史.doc对比岗位与简历关键信息
正在抽取关键技能并计算匹配度...
生成结论与问题清单
已生成 10 个面试问题,并输出风险点说明。
已取消
正在汇总候选人的关键信息与风险点...
"use client";
import { BookOpen } from "lucide-react";
import { ThinkingLoadingDotsPrimitive } from "@/components/wuhan/blocks/thinking-process/thinking-process-01";
import { ThinkingStep } from "@/components/composed/thinking-process";
import type { ThinkingStepItemProps } from "@/components/composed/thinking-step-item/thinking-step-item";
export function ThinkingProcessDemo() {
const subSteps = [
{
status: "success",
title: "明确研究目标与边界",
items: [
{
content: "明确研究目标与边界,我将调用知识和搜索工具。",
toolCall: {
icon: <BookOpen className="size-4" />,
title: "调取知识",
content: "我正在调取知识库资料",
},
files: [
{ icon: "📄", name: "AI发展趋势.pdf" },
{ icon: "📄", name: "AI发展历史.doc" },
],
},
],
defaultOpen: true,
},
{
status: "loading",
title: "对比岗位与简历关键信息",
items: [{ content: "正在抽取关键技能并计算匹配度..." }],
defaultOpen: true,
},
{
status: "success",
title: "生成结论与问题清单",
items: [{ content: "已生成 10 个面试问题,并输出风险点说明。" }],
defaultOpen: true,
},
] satisfies ThinkingStepItemProps[];
return (
<div className="w-full h-full flex flex-col gap-4 max-w-2xl">
{/* 思考中状态 - 不显示时间,标题闪烁 */}
<ThinkingStep
status="thinking"
title="思考中..."
content="思考中生成的内容"
/>
{/* 已完成状态 - 显示时间 */}
<ThinkingStep
status="completed"
title="思考完成"
duration={14}
content="用户想要了解AI发展的趋势。这是一个比较开放的问题,需要从多个维度来概括当前的主要方向。考虑到用户可能不是专业人士,应该用清晰的结构和易懂的语言来组织信息。"
/>
{/* 已完成状态 - 使用 contentBlocks 穿插渲染(文字 + 子步骤 + 文字) */}
<ThinkingStep
status="completed"
title="思考完成(contentBlocks)"
duration={30}
contentBlocks={[
{
type: "text",
key: "intro",
content: "下面是本次分析的关键子步骤(可与文字穿插渲染):",
},
{ type: "subSteps", key: "steps", steps: subSteps },
{
type: "text",
key: "outro",
content: "以上步骤完成后,已生成最终结论与问题清单。",
},
{ type: "node", key: "node", node: <h1>自定义组件</h1> },
]}
/>
{/* 长耗时提示 - 默认收起,点击可展开看到提示 */}
<ThinkingStep status="thinking" title="搜索中..." longRunning />
{/* 已取消状态 - 默认展开且会自动追加一个“已取消”子步骤 */}
<ThinkingStep
status="cancelled"
title="已取消"
contentBlocks={[{ type: "subSteps", key: "steps", steps: subSteps }]}
/>
{/* 思考中状态 - 自定义图标(loading dots) */}
<ThinkingStep
status="thinking"
title="思考中(自定义图标)"
icon={<ThinkingLoadingDotsPrimitive />}
content="正在汇总候选人的关键信息与风险点..."
/>
</div>
);
}
Thinking Process 是一个单个思考步骤块的组合组件,适合展示 AI 思考/任务执行的状态与内容,并支持“文字 + 子步骤”混合渲染。
安装
概述
- 定位:展示单个思考步骤(状态、标题、可折叠内容)
- 组合方式:通过
contentBlocks可穿插文字与子步骤 - 适用范围:开箱即用;若需深度布局/交互定制,使用 primitives
Usage
基本
import { ThinkingStep } from "@/registry/wuhan/composed/thinking-process/thinking-process";
export function Example() {
return (
<ThinkingStep
status="completed"
title="思考完成"
duration={14}
content="分析完成,已生成详细方案。"
defaultOpen
/>
);
}内容块(推荐)
import { ThinkingStep } from "@/registry/wuhan/composed/thinking-process/thinking-process";
const steps = [
{ status: "success", title: "子步骤 1", items: [{ content: "已完成" }] },
{ status: "loading", title: "子步骤 2", items: [{}] },
];
<ThinkingStep
status="completed"
title="分析完成"
contentBlocks={[
{ type: "text", key: "intro", content: "下面是关键子步骤:" },
{ type: "subSteps", key: "steps", steps },
{ type: "text", key: "outro", content: "步骤完成后将生成结论。" },
]}
defaultOpen
/>;长耗时提示
<ThinkingStep status="thinking" title="搜索中..." longRunning />自定义文案
<ThinkingStep
status="completed"
title="思考完成"
labels={{
longRunningHint: "任务需要一段时间,可稍后查看",
cancelledStepTitle: "已终止",
}}
/>;API (Composed)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
status | 'pending' | 'thinking' | 'completed' | 'idle' | 'running' | 'success' | 'error' | 'cancelled' | 'pending' | 步骤状态 |
title | React.ReactNode | - | 步骤标题 |
content | React.ReactNode | - | 步骤内容 |
contentBlocks | ThinkingStepContentBlock[] | - | 文字/子步骤/自定义节点穿插 |
subSteps | unknown[] | - | 子步骤数据(需配合 renderSubSteps) |
renderSubSteps | (subSteps) => ReactNode | - | 子步骤渲染器 |
duration | number | - | 耗时(秒) |
headerMeta | React.ReactNode | - | 头部右侧自定义信息 |
icon | React.ReactNode | - | 自定义图标 |
arrowIcon | React.ReactNode | - | 自定义箭头 |
hint | React.ReactNode | - | 长耗时提示文案 |
longRunning | boolean | false | 长耗时场景默认收起 |
labels | ThinkingStepLabels | - | 文案与统计配置 |
triggerId | string | - | 触发器 id(无障碍) |
contentId | string | - | 内容区域 id(无障碍) |
defaultOpen | boolean | false | 默认展开 |
open | boolean | - | 受控展开 |
onOpenChange | (open) => void | - | 展开变化回调 |
Behavior Notes
contentBlocks存在时忽略content / subSteps / renderSubSteps,且不会回退到其他内容来源status="cancelled"会自动追加一个“已取消”子步骤duration仅在成功态(success/completed)显示,且有headerMeta时会优先显示headerMetahint/longRunning仅在没有正文内容时展示(避免出现“空内容容器”)
代码演示
默认状态
分析完成,已生成详细方案。
"use client";
import { ThinkingStep } from "@/components/composed/thinking-process/thinking-process";
export function ThinkingProcessDefault() {
return (
<div className="w-full h-full max-w-2xl">
<ThinkingStep
status="completed"
title="思考完成"
duration={14}
content="分析完成,已生成详细方案。"
defaultOpen
/>
</div>
);
}
内容块
下面是关键子步骤:
整理需求
明确目标与范围。
调取知识:
读取历史资料
分析方案
正在生成分析结构...
以上步骤已完成。
"use client";
import { BookOpen } from "lucide-react";
import { ThinkingStep } from "@/components/composed/thinking-process/thinking-process";
import type { ThinkingStepItemProps } from "@/components/composed/thinking-step-item/thinking-step-item";
export function ThinkingProcessCustom() {
const steps: ThinkingStepItemProps[] = [
{
status: "success",
title: "整理需求",
items: [
{
content: "明确目标与范围。",
toolCall: {
icon: <BookOpen className="size-4" />,
title: "调取知识",
content: "读取历史资料",
},
},
],
},
{
status: "loading",
title: "分析方案",
items: [{ content: "正在生成分析结构..." }],
},
];
return (
<div className="w-full h-full max-w-2xl">
<ThinkingStep
status="completed"
title="思考完成"
duration={24}
contentBlocks={[
{ type: "text", key: "intro", content: "下面是关键子步骤:" },
{ type: "subSteps", key: "steps", steps },
{ type: "text", key: "outro", content: "以上步骤已完成。" },
]}
defaultOpen
/>
</div>
);
}
长时运行
"use client";
import { ThinkingStep } from "@/components/composed/thinking-process/thinking-process";
export function ThinkingProcessWithHint() {
return (
<div className="w-full h-full max-w-2xl space-y-4">
<ThinkingStep status="thinking" title="搜索中..." longRunning />
<ThinkingStep
status="thinking"
title="处理中..."
hint="任务将持续数分钟,可稍后查看"
longRunning
/>
</div>
);
}
状态与标签
配置项
基础信息
体验与提示
外观与可访问性
流式控制
状态:pending·阶段:intro·已暂停
AI 消息预览(ThinkingStep 内嵌)
"use client";
import * as React from "react";
import { BookOpen, ChevronRight } from "lucide-react";
import {
ThinkingLoadingDotsPrimitive,
type ThinkingStepStatus,
} from "@/components/wuhan/blocks/thinking-process/thinking-process-01";
import { ThinkingStep } from "@/components/composed/thinking-process/thinking-process";
import type { ThinkingStepItemProps } from "@/components/composed/thinking-step-item/thinking-step-item";
type StreamSpeed = "slow" | "medium" | "fast";
type Phase =
| {
type: "text";
key: string;
fullText: string;
}
| {
type: "step";
key: string;
step: ThinkingStepItemProps;
};
const INTRO_TEXT =
"用户想要了解 AI 发展的趋势。这是一个比较开放的问题,需要从多个维度来概括当前的主要方向。考虑到用户可能不是专业人士,我会先明确范围,再提炼关键维度。";
const OUTRO_TEXT =
"关键步骤完成后,我会基于维度给出结论,并附上可执行的建议与风险提示。";
const FULL_STEPS = [
{
status: "success",
title: "明确研究目标与边界",
items: [
{
content: "明确研究目标与边界,我将调用知识和搜索工具。",
toolCall: {
icon: <BookOpen className="size-4" />,
title: "调取知识",
content: "我正在调取知识库资料",
},
files: [
{ icon: "📄", name: "AI发展趋势.pdf" },
{ icon: "📄", name: "AI发展历史.doc" },
],
},
],
},
{
status: "loading",
title: "对比岗位与简历关键信息",
items: [
{ content: "正在抽取关键技能并计算匹配度..." },
{ content: "对照 JD 与历史项目,补齐软技能维度。" },
],
},
{
status: "success",
title: "生成结论与问题清单",
items: [
{ content: "已生成 10 个面试问题,并输出风险点说明。" },
{ content: "补充候选人潜力评估与跟进建议。" },
],
},
] satisfies ThinkingStepItemProps[];
const SPEED_CONFIG: Record<
StreamSpeed,
{ interval: number; textStep: number }
> = {
slow: { interval: 320, textStep: 2 },
medium: { interval: 160, textStep: 4 },
fast: { interval: 80, textStep: 8 },
};
export function ThinkingProcessDebugging() {
const phases = React.useMemo<Phase[]>(
() => [
{ type: "text", key: "intro", fullText: INTRO_TEXT },
{ type: "step", key: "step-1", step: FULL_STEPS[0] },
{ type: "step", key: "step-2", step: FULL_STEPS[1] },
{ type: "step", key: "step-3", step: FULL_STEPS[2] },
{ type: "text", key: "outro", fullText: OUTRO_TEXT },
],
[],
);
const [title, setTitle] = React.useState("");
const [headerMeta, setHeaderMeta] = React.useState("");
const [useHeaderMeta, setUseHeaderMeta] = React.useState(false);
const [duration, setDuration] = React.useState(0);
const [longRunning, setLongRunning] = React.useState(false);
const [hint, setHint] = React.useState("");
const [longRunningHint, setLongRunningHint] = React.useState("");
const [cancelledStepTitle, setCancelledStepTitle] = React.useState("");
const [triggerId, setTriggerId] = React.useState("");
const [contentId, setContentId] = React.useState("");
const [useCustomIcon, setUseCustomIcon] = React.useState(false);
const [useCustomArrow, setUseCustomArrow] = React.useState(false);
const [speed, setSpeed] = React.useState<StreamSpeed>("medium");
const [isStreaming, setIsStreaming] = React.useState(false);
const [streamState, setStreamState] = React.useState({
phaseIndex: 0,
textProgress: 0,
stepProgress: 0,
});
const isCompleted = streamState.phaseIndex >= phases.length;
const hasStarted =
streamState.phaseIndex > 0 ||
streamState.textProgress > 0 ||
streamState.stepProgress > 0;
const resolvedStatus: ThinkingStepStatus = isCompleted
? "completed"
: hasStarted
? "thinking"
: "pending";
const resolvedHeaderMeta =
useHeaderMeta && headerMeta.trim().length > 0
? headerMeta.trim()
: undefined;
const resolvedHint = hint.trim().length > 0 ? hint.trim() : undefined;
const resolvedLongRunningHint =
longRunningHint.trim().length > 0 ? longRunningHint.trim() : undefined;
const resolvedCancelledStepTitle =
cancelledStepTitle.trim().length > 0
? cancelledStepTitle.trim()
: undefined;
const resolvedLabels =
resolvedLongRunningHint || resolvedCancelledStepTitle
? {
longRunningHint: resolvedLongRunningHint,
cancelledStepTitle: resolvedCancelledStepTitle,
}
: undefined;
const resolvedTitle =
title.trim().length > 0
? title.trim()
: resolvedStatus === "completed"
? "思考完成"
: "思考中...";
const contentBlocks = React.useMemo(() => {
const blocks: Array<
| { type: "text"; key: string; content: React.ReactNode }
| {
type: "subSteps";
key: string;
steps: ThinkingStepItemProps[];
}
> = [];
const stepsBuffer: ThinkingStepItemProps[] = [];
const currentPhase = phases[streamState.phaseIndex];
const flushSteps = () => {
if (stepsBuffer.length === 0) return;
blocks.push({
type: "subSteps",
key: `steps-${blocks.length}`,
steps: [...stepsBuffer],
});
stepsBuffer.length = 0;
};
phases.forEach((phase, index) => {
if (phase.type === "text") {
flushSteps();
if (isCompleted || index < streamState.phaseIndex) {
blocks.push({
type: "text",
key: phase.key,
content: phase.fullText,
});
return;
}
if (currentPhase?.key === phase.key) {
blocks.push({
type: "text",
key: phase.key,
content: phase.fullText.slice(0, streamState.textProgress) || "",
});
}
return;
}
if (phase.type === "step") {
if (!isCompleted && index > streamState.phaseIndex) return;
const totalItems = phase.step.items?.length ?? 0;
const isCurrent = currentPhase?.key === phase.key && !isCompleted;
const visibleCount = isCompleted
? totalItems
: isCurrent
? Math.min(totalItems, streamState.stepProgress)
: totalItems;
const status: ThinkingStepItemProps["status"] = isCompleted
? "success"
: isCurrent
? "loading"
: "success";
stepsBuffer.push({
...phase.step,
status,
items: phase.step.items?.slice(0, visibleCount),
defaultOpen: true,
});
}
});
flushSteps();
return blocks;
}, [
isCompleted,
phases,
streamState.phaseIndex,
streamState.stepProgress,
streamState.textProgress,
]);
React.useEffect(() => {
if (!isStreaming) return;
const config = SPEED_CONFIG[speed];
const timer = window.setInterval(() => {
setStreamState((prev) => {
const phase = phases[prev.phaseIndex];
if (!phase) return prev;
if (phase.type === "text") {
const nextText = Math.min(
prev.textProgress + config.textStep,
phase.fullText.length,
);
if (nextText >= phase.fullText.length) {
return {
phaseIndex: prev.phaseIndex + 1,
textProgress: 0,
stepProgress: 0,
};
}
return { ...prev, textProgress: nextText };
}
const totalItems = phase.step.items?.length ?? 0;
const nextStep = Math.min(prev.stepProgress + 1, totalItems);
if (nextStep >= totalItems) {
return {
phaseIndex: prev.phaseIndex + 1,
textProgress: 0,
stepProgress: 0,
};
}
return { ...prev, stepProgress: nextStep };
});
}, config.interval);
return () => window.clearInterval(timer);
}, [isStreaming, phases, speed]);
React.useEffect(() => {
if (!isStreaming) return;
const timer = window.setInterval(
() => setDuration((prev) => prev + 1),
1000,
);
return () => window.clearInterval(timer);
}, [isStreaming]);
React.useEffect(() => {
if (!isStreaming) return;
if (isCompleted) {
setIsStreaming(false);
}
}, [isCompleted, isStreaming]);
const handleStart = () => {
setStreamState({ phaseIndex: 0, textProgress: 0, stepProgress: 0 });
setDuration(0);
setIsStreaming(true);
};
const handleStop = () => {
setIsStreaming(false);
};
const handleComplete = () => {
setIsStreaming(false);
setStreamState({
phaseIndex: phases.length,
textProgress: 0,
stepProgress: 0,
});
};
const handleReset = () => {
setIsStreaming(false);
setStreamState({ phaseIndex: 0, textProgress: 0, stepProgress: 0 });
setDuration(0);
};
return (
<div className="w-full h-full max-w-5xl flex flex-col gap-6">
<div className="flex flex-col gap-4 rounded-xl border border-border/60 bg-background/80 p-4">
<div className="text-sm font-medium text-foreground">配置项</div>
<details
className="group rounded-lg border border-border/60 bg-background p-3"
open
>
<summary className="flex cursor-pointer items-center justify-between text-sm font-medium text-foreground">
基础信息
<ChevronRight className="size-4 transition-transform group-open:rotate-90" />
</summary>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
标题
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
右侧文案(headerMeta)
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="如:耗时 12s / 已使用 3 个工具"
value={headerMeta}
onChange={(event) => setHeaderMeta(event.target.value)}
disabled={!useHeaderMeta}
/>
</label>
<label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={useHeaderMeta}
onChange={(event) => setUseHeaderMeta(event.target.checked)}
/>
启用 headerMeta(否则展示 duration)
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
时长(秒)
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
type="number"
min={0}
value={duration}
onChange={(event) =>
setDuration(Number(event.target.value) || 0)
}
/>
</label>
</div>
</details>
<details className="group rounded-lg border border-border/60 bg-background p-3">
<summary className="flex cursor-pointer items-center justify-between text-sm font-medium text-foreground">
体验与提示
<ChevronRight className="size-4 transition-transform group-open:rotate-90" />
</summary>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={longRunning}
onChange={(event) => setLongRunning(event.target.checked)}
/>
longRunning
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
hint
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="可选,留空则走默认提示"
value={hint}
onChange={(event) => setHint(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
labels.longRunningHint
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="覆盖默认长耗时提示文案"
value={longRunningHint}
onChange={(event) => setLongRunningHint(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
labels.cancelledStepTitle
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="取消时追加步骤标题"
value={cancelledStepTitle}
onChange={(event) => setCancelledStepTitle(event.target.value)}
/>
</label>
</div>
</details>
<details className="group rounded-lg border border-border/60 bg-background p-3">
<summary className="flex cursor-pointer items-center justify-between text-sm font-medium text-foreground">
外观与可访问性
<ChevronRight className="size-4 transition-transform group-open:rotate-90" />
</summary>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={useCustomIcon}
onChange={(event) => setUseCustomIcon(event.target.checked)}
/>
icon(自定义 Loading 图标)
</label>
<label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={useCustomArrow}
onChange={(event) => setUseCustomArrow(event.target.checked)}
/>
arrowIcon(自定义折叠箭头)
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
triggerId
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="可选,自定义 aria-controls"
value={triggerId}
onChange={(event) => setTriggerId(event.target.value)}
/>
</label>
<label className="flex flex-col gap-2 text-xs text-muted-foreground">
contentId
<input
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
placeholder="可选,自定义 aria-labelledby"
value={contentId}
onChange={(event) => setContentId(event.target.value)}
/>
</label>
</div>
</details>
<details
className="group rounded-lg border border-border/60 bg-background p-3"
open
>
<summary className="flex cursor-pointer items-center justify-between text-sm font-medium text-foreground">
流式控制
<ChevronRight className="size-4 transition-transform group-open:rotate-90" />
</summary>
<div className="mt-3 flex flex-col gap-3">
<label className="flex flex-col gap-2 text-xs text-muted-foreground md:max-w-[240px]">
速度
<select
className="h-9 rounded-md border border-border/60 bg-background px-3 text-sm text-foreground"
value={speed}
onChange={(event) =>
setSpeed(event.target.value as StreamSpeed)
}
>
<option value="slow">慢</option>
<option value="medium">中</option>
<option value="fast">快</option>
</select>
</label>
<div className="flex flex-wrap gap-2">
<button
type="button"
className="h-9 rounded-md border border-border/60 bg-background px-4 text-sm text-foreground"
onClick={handleStart}
>
开始流式
</button>
<button
type="button"
className="h-9 rounded-md border border-border/60 bg-background px-4 text-sm text-foreground"
onClick={handleStop}
>
暂停
</button>
<button
type="button"
className="h-9 rounded-md border border-border/60 bg-background px-4 text-sm text-foreground"
onClick={handleComplete}
>
一键完成
</button>
<button
type="button"
className="h-9 rounded-md border border-border/60 bg-background px-4 text-sm text-foreground"
onClick={handleReset}
>
重置
</button>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
状态:
<span className="text-foreground">{resolvedStatus}</span>
<span>·</span>
<span>阶段:{phases[streamState.phaseIndex]?.key ?? "完成"}</span>
<span>·</span>
<span>{isStreaming ? "流式中" : "已暂停"}</span>
</div>
</div>
</details>
</div>
<div className="rounded-xl border border-border/60 bg-muted/30 p-4">
<div className="mb-3 text-xs text-muted-foreground">
AI 消息预览(ThinkingStep 内嵌)
</div>
<div className="flex flex-col gap-3 rounded-lg border border-border/40 bg-background p-4">
<ThinkingStep
status={resolvedStatus}
title={resolvedTitle}
duration={duration}
headerMeta={resolvedHeaderMeta}
longRunning={longRunning}
hint={resolvedHint}
labels={resolvedLabels}
triggerId={triggerId.trim() || undefined}
contentId={contentId.trim() || undefined}
icon={useCustomIcon ? <ThinkingLoadingDotsPrimitive /> : undefined}
arrowIcon={
useCustomArrow ? <ChevronRight className="size-4" /> : undefined
}
contentBlocks={contentBlocks}
/>
</div>
</div>
</div>
);
}
Primitives (Advanced)
需要高度定制时使用 primitives:
import {
ThinkingStepPrimitive,
ThinkingStepHeaderPrimitive,
ThinkingStepContentPrimitive,
ThinkingStatusLabelPrimitive,
ThinkingIconContainerPrimitive,
ThinkingTimeLabelPrimitive,
ThinkingCollapseArrowPrimitive,
} from "@/registry/wuhan/blocks/thinking-process/thinking-process-01";Design Tokens
组件只消费全局 token(如 --text-* / --bg-* / --gap-* / --radius-*)。