mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge pull request #36738 from BerriAI/litellm_/eloquent-wu-3ab1d5
refactor(ui): migrate HistoryTree and CollapsibleMessage to shadcn
This commit is contained in:
commit
faea98ee7e
5 changed files with 68 additions and 120 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue