// Adapted from agentui (https://github.com/ashish200729/agentui), components/agents/streaming-response.tsx // Copyright (c) 2026 Saurabh Chauhan. MIT License, see ./LICENSE-agentui.md // 改造:上游是"整段替换"模型,这里由调用方把 text_delta 累积成全文再传入;新增流式光标(animate-pulse, // reduced motion 下静止);去掉点赞/点踩(本项目不收集反馈)与 motion 按压动画;来源列表接 Citations; // error 态用 destructive 语义色。 import { Check, Copy, RotateCcw } from "lucide-react"; import { type ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { cn } from "~/lib/utils.ts"; import { type CitationItem, CitationList } from "./citations.tsx"; export type StreamingResponseStatus = "streaming" | "complete" | "error"; export interface StreamingResponseProps { children: ReactNode; status?: StreamingResponseStatus; /** 复制按钮复制的纯文本 */ copyText?: string; onRetry?: () => void; sources?: CitationItem[]; /** 外层已有 aria-live(MessageScroller 的 log)时设 false,避免重复播报 */ announce?: boolean; showActions?: boolean; className?: string; contentClassName?: string; } /** 复制成功提示的停留时长(交互停留,非动效) */ const COPIED_HOLD_MS = 1600; function ActionButton({ label, onClick, children }: { label: string; onClick: () => void; children: ReactNode }) { return ( {children} ); } /** 流式光标:只在 streaming 时出现 */ export function StreamingCursor({ className }: { className?: string }) { return ( ); } export function StreamingResponse({ children, status = "streaming", copyText, onRetry, sources = [], announce = true, showActions = true, className, contentClassName, }: StreamingResponseProps) { const [copied, setCopied] = useState(false); const t = useRef(undefined); const streaming = status === "streaming"; useEffect( () => () => { if (t.current) window.clearTimeout(t.current); }, [], ); const copy = useCallback(async () => { if (!copyText) return; try { await navigator.clipboard?.writeText(copyText); setCopied(true); if (t.current) window.clearTimeout(t.current); t.current = window.setTimeout(() => setCopied(false), COPIED_HOLD_MS); } catch { // 剪贴板不可用(非安全上下文)时静默 } }, [copyText]); const actions = showActions && !streaming && (copyText || onRetry); return ( {children} {streaming ? : null} {actions ? ( {copyText ? ( {copied ? : } ) : null} {onRetry ? ( ) : null} {copied ? "已复制" : ""} ) : null} {sources.length && !streaming ? : null} ); }