Spaces:
Sleeping
Sleeping
File size: 2,414 Bytes
7540aea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | /**
* ThinkingBlock — displays model thinking/reasoning blocks in the chat.
* Matches original claw-code thinkback feature.
*/
import { useState } from "react";
import { Brain, ChevronDown, ChevronRight, Clock } from "lucide-react";
interface ThinkingBlockProps {
thinking: string;
durationMs?: number;
collapsed?: boolean;
}
export function ThinkingBlock({ thinking, durationMs, collapsed: initialCollapsed = true }: ThinkingBlockProps) {
const [collapsed, setCollapsed] = useState(initialCollapsed);
if (!thinking) return null;
const lines = thinking.split("\n");
const preview = lines.slice(0, 3).join("\n");
const hasMore = lines.length > 3 || thinking.length > 300;
return (
<div className="my-2 rounded-lg border border-purple-500/20 bg-purple-500/5 overflow-hidden">
<button
onClick={() => setCollapsed(!collapsed)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-purple-400 hover:bg-purple-500/10 transition-colors"
>
<Brain className="w-4 h-4 shrink-0" />
<span className="font-medium">Thinking</span>
{durationMs && (
<span className="flex items-center gap-1 text-xs text-muted-foreground ml-auto mr-2">
<Clock className="w-3 h-3" />
{(durationMs / 1000).toFixed(1)}s
</span>
)}
{collapsed ? (
<ChevronRight className="w-4 h-4 shrink-0 ml-auto" />
) : (
<ChevronDown className="w-4 h-4 shrink-0 ml-auto" />
)}
</button>
{!collapsed && (
<div className="px-3 pb-3 text-sm text-muted-foreground font-mono whitespace-pre-wrap border-t border-purple-500/10 pt-2 max-h-96 overflow-y-auto">
{thinking}
</div>
)}
{collapsed && hasMore && (
<div className="px-3 pb-2 text-xs text-muted-foreground font-mono whitespace-pre-wrap opacity-60 line-clamp-2">
{preview.substring(0, 200)}{preview.length >= 200 ? "..." : ""}
</div>
)}
</div>
);
}
/**
* Parse thinking blocks from a message content array.
*/
export function extractThinkingBlocks(content: unknown): { type: "thinking"; thinking: string; durationMs?: number }[] {
if (!Array.isArray(content)) return [];
return content.filter(
(block: any) => block.type === "thinking" && block.thinking
) as { type: "thinking"; thinking: string; durationMs?: number }[];
}
|