feat(ui): AntDLoadingSpinner + view_logs small files to shadcn

- AntDLoadingSpinner: antd Spin + @ant-design LoadingOutlined \u2192
  lucide Loader2 with animate-spin. Name kept for back-compat with
  many call sites.
- JsonViewer: antd Typography.Text \u2192 plain span with semantic
  tokens. The external react-json-view-lite renderer is unchanged.
- TruncatedValue: antd Typography.Text copyable + antd Tooltip \u2192
  shadcn Tooltip + inline lucide Copy/Check button. Preserves the
  max-width + mono font style.
- TokenFlow: antd Typography.Text \u2192 plain span.

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 11:29:06 +00:00
parent 948c80da15
commit b1f9b8bf09
No known key found for this signature in database
4 changed files with 106 additions and 43 deletions

View file

@ -1,12 +1,26 @@
import { Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
interface AntDLoadingSpinnerProps {
size?: "small" | "default" | "large";
fontSize?: number;
}
/**
* Back-compat spinner. The name is preserved for a growing number of
* call sites; the implementation is now a lucide Loader2 sized via
* Tailwind.
*/
export function AntDLoadingSpinner({ size, fontSize }: AntDLoadingSpinnerProps) {
const indicator = <LoadingOutlined style={fontSize ? { fontSize } : undefined} spin />;
return <Spin indicator={indicator} size={size} />;
const sizeClass = cn(
size === "small" && "h-3 w-3",
size === "large" && "h-6 w-6",
!size || size === "default" ? "h-4 w-4" : "",
);
return (
<Loader2
className={cn("animate-spin text-primary", sizeClass)}
style={fontSize ? { fontSize } : undefined}
/>
);
}

View file

@ -1,21 +1,22 @@
import { Typography } from "antd";
import { JsonView, defaultStyles } from "react-json-view-lite";
import "react-json-view-lite/dist/index.css";
import { JSON_MAX_HEIGHT, COLOR_BG_LIGHT, SPACING_LARGE } from "./constants";
const { Text } = Typography;
import {
JSON_MAX_HEIGHT,
COLOR_BG_LIGHT,
SPACING_LARGE,
} from "./constants";
interface JsonViewerProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
mode: "formatted";
}
/**
* Displays JSON data in formatted tree view.
* Uses an interactive tree component for easy navigation.
*/
export function JsonViewer({ data }: JsonViewerProps) {
if (!data) return <Text type="secondary">No data</Text>;
if (!data) return <span className="text-muted-foreground">No data</span>;
return (
<div

View file

@ -1,7 +1,3 @@
import { Typography } from "antd";
const { Text } = Typography;
interface TokenFlowProps {
prompt?: number;
completion?: number;
@ -10,13 +6,16 @@ interface TokenFlowProps {
/**
* Displays token usage in LiteLLM format: "12 (9 prompt tokens + 3 completion tokens)"
* Shows total with breakdown of prompt and completion tokens.
*/
export function TokenFlow({ prompt = 0, completion = 0, total = 0 }: TokenFlowProps) {
export function TokenFlow({
prompt = 0,
completion = 0,
total = 0,
}: TokenFlowProps) {
return (
<Text>
{total.toLocaleString()} ({prompt.toLocaleString()} prompt tokens + {completion.toLocaleString()} completion
tokens)
</Text>
<span>
{total.toLocaleString()} ({prompt.toLocaleString()} prompt tokens +{" "}
{completion.toLocaleString()} completion tokens)
</span>
);
}

View file

@ -1,7 +1,16 @@
import { Typography, Tooltip } from "antd";
import { DEFAULT_MAX_WIDTH, FONT_FAMILY_MONO, FONT_SIZE_SMALL } from "./constants";
const { Text } = Typography;
import React, { useState } from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Check, Copy } from "lucide-react";
import {
DEFAULT_MAX_WIDTH,
FONT_FAMILY_MONO,
FONT_SIZE_SMALL,
} from "./constants";
interface TruncatedValueProps {
value?: string;
@ -9,27 +18,67 @@ interface TruncatedValueProps {
}
/**
* Displays a truncated value with tooltip and copy functionality.
* Useful for displaying long IDs, URLs, or other text that may overflow.
* Displays a truncated value with hover tooltip and inline copy button.
*/
export function TruncatedValue({ value, maxWidth = DEFAULT_MAX_WIDTH }: TruncatedValueProps) {
if (!value) return <Text type="secondary">-</Text>;
export function TruncatedValue({
value,
maxWidth = DEFAULT_MAX_WIDTH,
}: TruncatedValueProps) {
const [copied, setCopied] = useState(false);
if (!value) return <span className="text-muted-foreground">-</span>;
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_err) {
/* noop */
}
};
return (
<Tooltip title={value}>
<Text
copyable={{ text: value, tooltips: ["Copy", "Copied!"] }}
style={{
maxWidth,
display: "inline-block",
verticalAlign: "bottom",
fontFamily: FONT_FAMILY_MONO,
fontSize: FONT_SIZE_SMALL,
}}
ellipsis
>
{value}
</Text>
</Tooltip>
<span className="inline-flex items-center gap-1 align-bottom">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-block truncate"
style={{
maxWidth,
fontFamily: FONT_FAMILY_MONO,
fontSize: FONT_SIZE_SMALL,
}}
>
{value}
</span>
</TooltipTrigger>
<TooltipContent className="max-w-md break-all">
{value}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip open={copied || undefined}>
<TooltipTrigger asChild>
<button
type="button"
onClick={handleCopy}
className="text-muted-foreground hover:text-foreground"
aria-label="Copy"
>
{copied ? (
<Check className="h-3 w-3 text-emerald-500" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</TooltipTrigger>
<TooltipContent>{copied ? "Copied!" : "Copy"}</TooltipContent>
</Tooltip>
</TooltipProvider>
</span>
);
}