mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): view_logs/LogDetailsDrawer tree blocks to shadcn
- SimpleToolCallBlock: antd Typography \u2192 plain span + semantic bg-muted/border-border/text-muted-foreground tokens. Function badge preserved. - SimpleMessageBlock: antd Typography \u2192 plain span; preserves label/content/toolCalls layout. - CollapsibleMessage / HistoryTree: antd Typography + @ant-design Down/RightOutlined \u2192 plain span + lucide ChevronDown/ChevronRight. Smooth max-height expand animation preserved via Tailwind's transition-[max-height,opacity] utility. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
cf30ba43ef
commit
baefdd512c
4 changed files with 65 additions and 174 deletions
|
|
@ -1,13 +1,5 @@
|
|||
/**
|
||||
* CollapsibleMessage - Collapsible message with arrow and char count
|
||||
* Used for system messages
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
|
||||
interface CollapsibleMessageProps {
|
||||
label: string;
|
||||
|
|
@ -15,13 +7,12 @@ interface CollapsibleMessageProps {
|
|||
defaultExpanded?: boolean;
|
||||
}
|
||||
|
||||
export function CollapsibleMessage({
|
||||
label,
|
||||
content,
|
||||
defaultExpanded = false
|
||||
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) {
|
||||
|
|
@ -29,57 +20,32 @@ export function CollapsibleMessage({
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
{/* Clickable Header with hover state */}
|
||||
<div className="mb-2">
|
||||
<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,
|
||||
}}
|
||||
className="flex items-center gap-1.5 cursor-pointer py-1 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<DownOutlined style={{ fontSize: 10, color: '#8c8c8c' }} />
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<RightOutlined style={{ fontSize: 10, color: '#8c8c8c' }} />
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<Text type="secondary" style={{ fontSize: 10, letterSpacing: '0.5px', textTransform: 'uppercase' }}>
|
||||
<span className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
{label}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 10 }}>
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
({charCount.toLocaleString()} chars)
|
||||
</Text>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content with smooth animation */}
|
||||
<div
|
||||
className="overflow-hidden transition-[max-height,opacity] duration-200 ease-out"
|
||||
style={{
|
||||
maxHeight: isExpanded ? '2000px' : '0px',
|
||||
overflow: 'hidden',
|
||||
transition: 'max-height 0.2s ease-out, opacity 0.2s ease-out',
|
||||
maxHeight: isExpanded ? "2000px" : "0px",
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 16,
|
||||
fontSize: 13,
|
||||
lineHeight: 1.7,
|
||||
color: '#262626',
|
||||
borderLeft: '1px solid #f0f0f0',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
<div className="pl-4 text-[13px] leading-7 text-foreground border-l border-border whitespace-pre-wrap break-words">
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
/**
|
||||
* HistoryTree - Collapsible tree view for message history
|
||||
* Shows arrow indicator and message count
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { ParsedMessage } from './prettyMessagesTypes';
|
||||
import { SimpleMessageBlock } from './SimpleMessageBlock';
|
||||
|
||||
const { Text } = Typography;
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { ParsedMessage } from "./prettyMessagesTypes";
|
||||
import { SimpleMessageBlock } from "./SimpleMessageBlock";
|
||||
|
||||
interface HistoryTreeProps {
|
||||
messages: ParsedMessage[];
|
||||
|
|
@ -17,56 +9,35 @@ interface HistoryTreeProps {
|
|||
|
||||
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 className="mb-2">
|
||||
<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,
|
||||
}}
|
||||
className="flex items-center gap-1.5 cursor-pointer py-1 rounded hover:bg-muted transition-colors"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<DownOutlined style={{ fontSize: 10, color: '#8c8c8c' }} />
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<RightOutlined style={{ fontSize: 10, color: '#8c8c8c' }} />
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
<Text type="secondary" style={{ fontSize: 10, letterSpacing: '0.5px', textTransform: 'uppercase' }}>
|
||||
HISTORY ({messages.length} message{messages.length !== 1 ? 's' : ''})
|
||||
</Text>
|
||||
<span className="text-[10px] tracking-wider uppercase text-muted-foreground">
|
||||
HISTORY ({messages.length} message{messages.length !== 1 ? "s" : ""})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Expanded Tree Content with smooth animation */}
|
||||
<div
|
||||
className="overflow-hidden transition-[max-height,opacity] duration-200 ease-out"
|
||||
style={{
|
||||
maxHeight: isExpanded ? '2000px' : '0px',
|
||||
overflow: 'hidden',
|
||||
transition: 'max-height 0.2s ease-out, opacity 0.2s ease-out',
|
||||
maxHeight: isExpanded ? "2000px" : "0px",
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 16,
|
||||
borderLeft: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<div className="pl-4 border-l border-border">
|
||||
{messages.map((msg, index) => (
|
||||
<SimpleMessageBlock
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
/**
|
||||
* SimpleMessageBlock - Simple message display without collapsing
|
||||
* Used for messages in tree view and last user message
|
||||
*/
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { ToolCall } from './prettyMessagesTypes';
|
||||
import { SimpleToolCallBlock } from './SimpleToolCallBlock';
|
||||
|
||||
const { Text } = Typography;
|
||||
import { ToolCall } from "./prettyMessagesTypes";
|
||||
import { SimpleToolCallBlock } from "./SimpleToolCallBlock";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SimpleMessageBlockProps {
|
||||
label: string;
|
||||
|
|
@ -16,56 +9,45 @@ interface SimpleMessageBlockProps {
|
|||
isCompact?: boolean;
|
||||
}
|
||||
|
||||
export function SimpleMessageBlock({
|
||||
label,
|
||||
content,
|
||||
toolCalls,
|
||||
isCompact = false
|
||||
export function SimpleMessageBlock({
|
||||
label,
|
||||
content,
|
||||
toolCalls,
|
||||
isCompact = false,
|
||||
}: SimpleMessageBlockProps) {
|
||||
// Don't show "null" for empty content
|
||||
const displayContent = content && content !== 'null' && content.length > 0 ? content : null;
|
||||
const displayContent =
|
||||
content && content !== "null" && content.length > 0 ? content : null;
|
||||
const hasToolCalls = toolCalls && toolCalls.length > 0;
|
||||
|
||||
// If no content and no tool calls, don't render
|
||||
if (!displayContent && !hasToolCalls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: isCompact ? 8 : 0 }}>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: 10,
|
||||
letterSpacing: '0.5px',
|
||||
textTransform: 'uppercase',
|
||||
display: 'block',
|
||||
marginBottom: 3
|
||||
}}
|
||||
>
|
||||
<div className={cn(isCompact ? "mb-2" : "")}>
|
||||
<span className="block text-[10px] tracking-wider uppercase text-muted-foreground mb-0.5">
|
||||
{label}
|
||||
</Text>
|
||||
</span>
|
||||
|
||||
{displayContent && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
lineHeight: 1.7,
|
||||
color: '#262626',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
marginBottom: hasToolCalls ? 6 : 0,
|
||||
}}
|
||||
className={cn(
|
||||
"text-[13px] leading-7 text-foreground whitespace-pre-wrap break-words",
|
||||
hasToolCalls ? "mb-1.5" : "",
|
||||
)}
|
||||
>
|
||||
{displayContent}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline tool calls for assistant messages in history */}
|
||||
{hasToolCalls && (
|
||||
<div>
|
||||
{toolCalls.map((tc, index) => (
|
||||
<SimpleToolCallBlock key={tc.id || index} tool={tc} compact={isCompact} />
|
||||
<SimpleToolCallBlock
|
||||
key={tc.id || index}
|
||||
tool={tc}
|
||||
compact={isCompact}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,33 @@
|
|||
/**
|
||||
* SimpleToolCallBlock - Simple tool call display without copy button
|
||||
* Used in compact/tree views
|
||||
*/
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { ToolCall } from './prettyMessagesTypes';
|
||||
|
||||
const { Text } = Typography;
|
||||
import { ToolCall } from "./prettyMessagesTypes";
|
||||
|
||||
interface SimpleToolCallBlockProps {
|
||||
tool: ToolCall;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export function SimpleToolCallBlock({ tool, compact = false }: SimpleToolCallBlockProps) {
|
||||
export function SimpleToolCallBlock({
|
||||
tool,
|
||||
compact = false,
|
||||
}: SimpleToolCallBlockProps) {
|
||||
return (
|
||||
<div
|
||||
className="bg-muted border border-border rounded-md mt-2 font-mono text-xs relative"
|
||||
style={{
|
||||
background: '#f8f9fa',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: 6,
|
||||
padding: compact ? '6px 10px' : '10px 14px',
|
||||
marginTop: 8,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
position: 'relative',
|
||||
padding: compact ? "6px 10px" : "10px 14px",
|
||||
}}
|
||||
>
|
||||
{/* Function badge */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -8,
|
||||
left: 12,
|
||||
background: '#fff',
|
||||
padding: '0 6px',
|
||||
fontSize: 10,
|
||||
color: '#8c8c8c',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: 3,
|
||||
}}
|
||||
>
|
||||
<div className="absolute -top-2 left-3 bg-background px-1.5 text-[10px] text-muted-foreground border border-border rounded">
|
||||
function
|
||||
</div>
|
||||
|
||||
<Text strong style={{ fontSize: 13, display: 'block', marginBottom: 6 }}>
|
||||
{tool.name}
|
||||
</Text>
|
||||
<span className="block font-bold text-[13px] mb-1.5">{tool.name}</span>
|
||||
|
||||
{Object.keys(tool.arguments).length > 0 && (
|
||||
<div>
|
||||
{Object.entries(tool.arguments).map(([key, value]) => (
|
||||
<div key={key} style={{ marginBottom: 2 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{key}:{' '}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 12 }}>{JSON.stringify(value)}</Text>
|
||||
<div key={key} className="mb-0.5">
|
||||
<span className="text-xs text-muted-foreground">{key}: </span>
|
||||
<span className="text-xs">{JSON.stringify(value)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue