// Adapted from agentui (https://github.com/ashish200729/agentui), components/agents/code-block.tsx + agent-code.tsx // Copyright (c) 2026 Saurabh Chauhan. MIT License, see ./LICENSE-agentui.md // 改造:不引 shiki(包体大,且高亮主题带十六进制颜色),纯文本按行渲染;JSON 输入先尝试缩进美化; // 状态色:streaming 用中性色 + 转圈,complete 用 success;高亮行用 warning 洗层;行号/字号/圆角换 token; // 去掉 motion 按压;复制失败静默。 import { Check, Copy, FileCode2, LoaderCircle } from "lucide-react"; import { type ReactNode, useEffect, useRef, useState } from "react"; import { cn } from "~/lib/utils.ts"; export type CodeLanguage = "text" | "json" | "bash" | "diff"; /** JSON 文本尝试缩进美化;不是合法 JSON 原样返回(摘要可能被截断) */ export function prettyMaybeJson(s: string): { text: string; language: CodeLanguage } { const t = s.trim(); if ((t.startsWith("{") && t.endsWith("}")) || (t.startsWith("[") && t.endsWith("]"))) { try { return { text: JSON.stringify(JSON.parse(t), null, 2), language: "json" }; } catch { return { text: s, language: "json" }; } } return { text: s, language: "text" }; } export interface CodeBlockProps { code: string; language?: CodeLanguage; filename?: ReactNode; status?: "streaming" | "complete"; showLineNumbers?: boolean; highlightLines?: number[]; /** 最大高度(Tailwind 类,如 max-h-56) */ maxHeightClassName?: string; wrap?: boolean; copyable?: boolean; /** 不显示顶栏(嵌在别的卡片里时) */ bare?: boolean; tone?: "default" | "destructive"; className?: string; } export function CodeBlock({ code, language = "text", filename, status = "complete", showLineNumbers = false, highlightLines = [], maxHeightClassName = "max-h-56", wrap = true, copyable = true, bare = false, tone = "default", className, }: CodeBlockProps) { const [copied, setCopied] = useState(false); const t = useRef(undefined); useEffect( () => () => { if (t.current) window.clearTimeout(t.current); }, [], ); const streaming = status === "streaming"; const hl = new Set(highlightLines); const lines = code.split("\n"); const copy = async () => { try { await navigator.clipboard?.writeText(code); setCopied(true); if (t.current) window.clearTimeout(t.current); t.current = window.setTimeout(() => setCopied(false), 1600); } catch { // 非安全上下文没有剪贴板 } }; return (
{bare ? null : (
{filename ? {filename} : null} {language} {streaming ? ( ) : ( )} {streaming ? "输出中" : "已完成"} {copyable ? ( ) : null}
)}
          
            {lines.map((line, i) => (
              
                {showLineNumbers ? (
                  {i + 1}
                ) : null}
                
                  {line || " "}
                
              
            ))}
          
        
); }