Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

190 lines
5.9 KiB
TypeScript

"use client";
import { cn } from "@/lib/utils";
import { useScrollLock } from "@assistant-ui/react";
import { AlertCircle, ChevronRight } from "lucide-react";
import { useRef, useState, type ReactNode } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const TRACE_ANIMATION_DURATION = 200;
function ToolJsonBlock({ label, value }: { label: string; value: string }) {
return (
<div className="space-y-1">
<p className="text-muted-foreground/60 text-[10px] font-medium tracking-wide uppercase">
{label}
</p>
<pre className="text-muted-foreground bg-muted/50 max-h-60 overflow-auto rounded-md p-2.5 text-[11px] leading-relaxed">
{value}
</pre>
</div>
);
}
export function ToolTraceCard({
icon,
signature,
description,
args,
result,
}: {
icon: ReactNode;
signature: string;
description: ReactNode;
args?: Record<string, unknown>;
result: unknown;
}) {
const rootRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const lockScroll = useScrollLock(rootRef, TRACE_ANIMATION_DURATION);
const hasArgs = !!args && Object.keys(args).length > 0;
return (
<Collapsible
ref={rootRef}
open={open}
onOpenChange={(next) => {
lockScroll();
setOpen(next);
}}
className="group/tool-trace max-w-full"
style={
{
"--animation-duration": `${TRACE_ANIMATION_DURATION}ms`,
} as React.CSSProperties
}
>
<CollapsibleTrigger className="text-muted-foreground hover:text-foreground flex w-fit max-w-full cursor-pointer items-center gap-2 py-0.5 transition-colors select-none">
<span className="flex size-4 shrink-0 items-center justify-center [&_svg]:size-3.5">
{icon}
</span>
<span className="min-w-0 truncate font-mono text-[13px]">
{signature}
</span>
<span className="text-muted-foreground/60 truncate text-xs">
{description}
</span>
<ChevronRight className="size-3.5 shrink-0 transition-transform group-data-[state=open]/tool-trace:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
"relative overflow-hidden outline-none",
"data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down ease-out",
"data-[state=closed]:fill-mode-forwards data-[state=closed]:pointer-events-none",
"data-[state=closed]:duration-(--animation-duration) data-[state=open]:duration-(--animation-duration)",
)}
>
<div className="my-1 ml-6 space-y-2">
<ToolJsonBlock
label="Parameters"
value={hasArgs ? JSON.stringify(args, null, 2) : "{}"}
/>
<ToolJsonBlock
label="Result"
value={JSON.stringify(result, null, 2)}
/>
</div>
</CollapsibleContent>
</Collapsible>
);
}
export function ToolStatusCard({
icon,
signature,
message,
loading = false,
}: {
icon: ReactNode;
signature: string;
message: string;
loading?: boolean;
}) {
return (
<div className="text-muted-foreground flex max-w-full items-center gap-2 py-0.5">
<span
className={cn(
"flex size-4 shrink-0 items-center justify-center [&_svg]:size-3.5",
loading && "animate-pulse",
)}
>
{icon}
</span>
<span className="min-w-0 truncate font-mono text-[13px]">
{signature}
</span>
<span className="text-muted-foreground/60 truncate text-xs">
{message}
</span>
</div>
);
}
export function ToolErrorCard({
signature,
error,
args,
}: {
signature: string;
error?: string;
args?: Record<string, unknown>;
}) {
const rootRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const lockScroll = useScrollLock(rootRef, TRACE_ANIMATION_DURATION);
const message = error || "Unknown error";
const hasArgs = !!args && Object.keys(args).length > 0;
return (
<Collapsible
ref={rootRef}
open={open}
onOpenChange={(next) => {
lockScroll();
setOpen(next);
}}
className="group/tool-error max-w-full"
style={
{
"--animation-duration": `${TRACE_ANIMATION_DURATION}ms`,
} as React.CSSProperties
}
>
<CollapsibleTrigger className="text-destructive hover:text-destructive/80 flex w-fit max-w-full cursor-pointer items-center gap-2 py-0.5 text-left transition-colors select-none">
<span className="flex size-4 shrink-0 items-center justify-center [&_svg]:size-3.5">
<AlertCircle />
</span>
<span className="min-w-0 truncate font-mono text-[13px]">
{signature}
</span>
<span className="text-destructive/70 truncate text-xs">{message}</span>
<ChevronRight className="size-3.5 shrink-0 transition-transform group-data-[state=open]/tool-error:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
"relative overflow-hidden outline-none",
"data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down ease-out",
"data-[state=closed]:fill-mode-forwards data-[state=closed]:pointer-events-none",
"data-[state=closed]:duration-(--animation-duration) data-[state=open]:duration-(--animation-duration)",
)}
>
<div className="my-1 ml-6 space-y-2">
<ToolJsonBlock
label="Parameters"
value={hasArgs ? JSON.stringify(args, null, 2) : "{}"}
/>
<div className="bg-destructive/20 rounded-md p-2.5">
<p className="text-destructive text-xs leading-relaxed break-words">
{message}
</p>
</div>
</div>
</CollapsibleContent>
</Collapsible>
);
}