Merge pull request #36738 from BerriAI/litellm_/eloquent-wu-3ab1d5

refactor(ui): migrate HistoryTree and CollapsibleMessage to shadcn
This commit is contained in:
yuneng-jiang 2026-08-13 13:23:27 -07:00 committed by GitHub
commit faea98ee7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 120 deletions

View file

@ -3794,21 +3794,11 @@
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/HistoryTree.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx": {
"no-nested-ternary": {
"count": 3

View file

@ -37,4 +37,18 @@ describe("CollapsibleMessage", () => {
await user.click(screen.getByText("SYSTEM"));
expect(screen.getByText("Toggle me")).toBeInTheDocument();
});
it("should expand with Enter and collapse with Space from the keyboard", async () => {
const user = userEvent.setup();
render(<CollapsibleMessage label="SYSTEM" content="Toggle me" defaultExpanded={false} />);
expect(screen.getByText("Toggle me")).not.toBeVisible();
await user.tab();
await user.keyboard("{Enter}");
expect(screen.getByText("Toggle me")).toBeVisible();
await user.keyboard(" ");
expect(screen.getByText("Toggle me")).not.toBeVisible();
});
});

View file

@ -4,10 +4,8 @@
*/
import { useState } from "react";
import { Typography } from "antd";
import { DownOutlined, RightOutlined } from "@ant-design/icons";
const { Text } = Typography;
import { ChevronDown, ChevronRight } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
interface CollapsibleMessageProps {
label: string;
@ -17,7 +15,6 @@ interface CollapsibleMessageProps {
export function CollapsibleMessage({ label, content, defaultExpanded = false }: CollapsibleMessageProps) {
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
const [isHovered, setIsHovered] = useState(false);
const charCount = content?.length || 0;
if (!content || charCount === 0) {
@ -25,60 +22,23 @@ export function CollapsibleMessage({ label, content, defaultExpanded = false }:
}
return (
<div style={{ marginBottom: 8 }}>
{/* Clickable Header with hover state */}
<div
onClick={() => setIsExpanded(!isExpanded)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
display: "flex",
alignItems: "center",
gap: 6,
cursor: "pointer",
padding: "4px 0",
borderRadius: 4,
background: isHovered ? "#f5f5f5" : "transparent",
transition: "background 0.15s ease",
marginBottom: isExpanded ? 4 : 0,
}}
>
<Collapsible open={isExpanded} onOpenChange={setIsExpanded} className="mb-2">
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded py-1 text-left transition-colors hover:bg-muted">
{isExpanded ? (
<DownOutlined style={{ fontSize: 10, color: "#8c8c8c" }} />
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
) : (
<RightOutlined style={{ fontSize: 10, color: "#8c8c8c" }} />
<ChevronRight className="size-3 shrink-0 text-muted-foreground" />
)}
<Text type="secondary" style={{ fontSize: 10, letterSpacing: "0.5px", textTransform: "uppercase" }}>
{label}
</Text>
<Text type="secondary" style={{ fontSize: 10 }}>
({charCount.toLocaleString()} chars)
</Text>
</div>
<span className="text-[10px] uppercase tracking-[0.5px] text-muted-foreground">{label}</span>
<span className="text-[10px] text-muted-foreground">({charCount.toLocaleString()} chars)</span>
</CollapsibleTrigger>
{/* Content with smooth animation */}
<div
style={{
maxHeight: isExpanded ? "2000px" : "0px",
overflow: "hidden",
transition: "max-height 0.2s ease-out, opacity 0.2s ease-out",
opacity: isExpanded ? 1 : 0,
}}
<CollapsibleContent
keepMounted
className="mt-1 border-l border-border pl-4 text-[13px] leading-[1.7] break-words whitespace-pre-wrap text-foreground"
>
<div
style={{
paddingLeft: 16,
fontSize: 13,
lineHeight: 1.7,
color: "#262626",
borderLeft: "1px solid #f0f0f0",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
{content}
</div>
</div>
</div>
{content}
</CollapsibleContent>
</Collapsible>
);
}

View file

@ -41,4 +41,22 @@ describe("HistoryTree", () => {
expect(screen.getByText("Hello")).toBeInTheDocument();
expect(screen.getByText("Hi there")).toBeInTheDocument();
});
it("should expand with Enter and collapse with Space from the keyboard", async () => {
const user = userEvent.setup();
const messages: ParsedMessage[] = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
];
render(<HistoryTree messages={messages} />);
expect(screen.getByText("Hello")).not.toBeVisible();
await user.tab();
await user.keyboard("{Enter}");
expect(screen.getByText("Hello")).toBeVisible();
await user.keyboard(" ");
expect(screen.getByText("Hello")).not.toBeVisible();
});
});

View file

@ -4,80 +4,46 @@
*/
import { useState } from "react";
import { Typography } from "antd";
import { DownOutlined, RightOutlined } from "@ant-design/icons";
import { ChevronDown, ChevronRight } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { ParsedMessage } from "./prettyMessagesTypes";
import { SimpleMessageBlock } from "./SimpleMessageBlock";
const { Text } = Typography;
interface HistoryTreeProps {
messages: ParsedMessage[];
}
export function HistoryTree({ messages }: HistoryTreeProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [isHovered, setIsHovered] = useState(false);
if (messages.length === 0) {
return null;
}
return (
<div style={{ marginBottom: 8 }}>
{/* Clickable Header with hover state */}
<div
onClick={() => setIsExpanded(!isExpanded)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
display: "flex",
alignItems: "center",
gap: 6,
cursor: "pointer",
padding: "4px 0",
borderRadius: 4,
background: isHovered ? "#f5f5f5" : "transparent",
transition: "background 0.15s ease",
marginBottom: isExpanded ? 4 : 0,
}}
>
<Collapsible open={isExpanded} onOpenChange={setIsExpanded} className="mb-2">
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded py-1 text-left transition-colors hover:bg-muted">
{isExpanded ? (
<DownOutlined style={{ fontSize: 10, color: "#8c8c8c" }} />
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
) : (
<RightOutlined style={{ fontSize: 10, color: "#8c8c8c" }} />
<ChevronRight className="size-3 shrink-0 text-muted-foreground" />
)}
<Text type="secondary" style={{ fontSize: 10, letterSpacing: "0.5px", textTransform: "uppercase" }}>
<span className="text-[10px] uppercase tracking-[0.5px] text-muted-foreground">
HISTORY ({messages.length} message{messages.length !== 1 ? "s" : ""})
</Text>
</div>
</span>
</CollapsibleTrigger>
{/* Expanded Tree Content with smooth animation */}
<div
style={{
maxHeight: isExpanded ? "2000px" : "0px",
overflow: "hidden",
transition: "max-height 0.2s ease-out, opacity 0.2s ease-out",
opacity: isExpanded ? 1 : 0,
}}
>
<div
style={{
paddingLeft: 16,
borderLeft: "1px solid #f0f0f0",
}}
>
{messages.map((msg, index) => (
<SimpleMessageBlock
key={index}
label={msg.role.toUpperCase()}
content={msg.content}
toolCalls={msg.toolCalls}
isCompact={true}
/>
))}
</div>
</div>
</div>
<CollapsibleContent keepMounted className="mt-1 border-l border-border pl-4">
{messages.map((msg, index) => (
<SimpleMessageBlock
key={index}
label={msg.role.toUpperCase()}
content={msg.content}
toolCalls={msg.toolCalls}
isCompact={true}
/>
))}
</CollapsibleContent>
</Collapsible>
);
}