mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): MessageBubble + SessionManagement to shadcn
- conversation_panel/MessageBubble: @ant-design UserOutlined/RobotOutlined \u2192 lucide User/Bot. Inline style hex colors replaced with categorical blue (user) / muted (assistant) Tailwind classes. File added to overrides. - SessionManagement: antd Switch/Tooltip + @ant-design InfoCircle/ CopyOutlined \u2192 shadcn Switch/Tooltip + lucide Info/Copy. UI/API label pair flanks the Switch since shadcn Switch has no checkedChildren/ unCheckedChildren slots. Session-active banner uses categorical emerald/blue palette. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
381cb8a614
commit
c1d0451c9b
3 changed files with 127 additions and 61 deletions
|
|
@ -60,6 +60,8 @@
|
|||
"src/components/guardrails/content_filter/ContentFilterDisplay.tsx",
|
||||
"src/components/prompts/prompt_editor_view/PromptEditorHeader.tsx",
|
||||
"src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx",
|
||||
"src/components/prompts/prompt_editor_view/conversation_panel/MessageBubble.tsx",
|
||||
"src/components/playground/chat_ui/SessionManagement.tsx",
|
||||
"src/components/policies/impact_preview_alert.tsx",
|
||||
"src/components/policies/impact_popover.tsx",
|
||||
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import React from "react";
|
||||
import { Switch, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined, CopyOutlined } from "@ant-design/icons";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Copy, Info } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EndpointType } from "./mode_endpoint_mapping";
|
||||
import NotificationsManager from "../../molecules/notifications_manager";
|
||||
|
||||
|
|
@ -30,10 +37,14 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
|
|||
|
||||
const getSessionDisplay = () => {
|
||||
if (!responsesSessionId) {
|
||||
return useApiSessionManagement ? "API Session: Ready" : "UI Session: Ready";
|
||||
return useApiSessionManagement
|
||||
? "API Session: Ready"
|
||||
: "UI Session: Ready";
|
||||
}
|
||||
|
||||
const sessionPrefix = useApiSessionManagement ? "Response ID" : "UI Session";
|
||||
const sessionPrefix = useApiSessionManagement
|
||||
? "Response ID"
|
||||
: "UI Session";
|
||||
const truncatedId = responsesSessionId.slice(0, 10);
|
||||
return `${sessionPrefix}: ${truncatedId}...`;
|
||||
};
|
||||
|
|
@ -52,43 +63,83 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
|
|||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
{/* Session Management Toggle */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-700">Session Management</span>
|
||||
<Tooltip title="Choose between LiteLLM API session management (using previous_response_id) or UI-based session management (using chat history)">
|
||||
<InfoCircleOutlined className="text-gray-400" style={{ fontSize: "12px" }} />
|
||||
</Tooltip>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
Session Management
|
||||
</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3 w-3 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
Choose between LiteLLM API session management (using
|
||||
previous_response_id) or UI-based session management (using
|
||||
chat history)
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<span
|
||||
className={cn(
|
||||
useApiSessionManagement
|
||||
? "text-muted-foreground"
|
||||
: "text-foreground",
|
||||
)}
|
||||
>
|
||||
UI
|
||||
</span>
|
||||
<Switch
|
||||
checked={useApiSessionManagement}
|
||||
onCheckedChange={onToggleSessionManagement}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
useApiSessionManagement
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
API
|
||||
</span>
|
||||
</div>
|
||||
<Switch
|
||||
checked={useApiSessionManagement}
|
||||
onChange={onToggleSessionManagement}
|
||||
checkedChildren="API"
|
||||
unCheckedChildren="UI"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Session Status Indicator */}
|
||||
<div
|
||||
className={`text-xs p-2 rounded-md ${
|
||||
className={cn(
|
||||
"text-xs p-2 rounded-md border",
|
||||
responsesSessionId
|
||||
? "bg-green-50 text-green-700 border border-green-200"
|
||||
: "bg-blue-50 text-blue-700 border border-blue-200"
|
||||
}`}
|
||||
? "bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900 dark:text-emerald-300"
|
||||
: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900 dark:text-blue-300",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<InfoCircleOutlined style={{ fontSize: "12px" }} />
|
||||
<Info className="h-3 w-3" />
|
||||
{getSessionDisplay()}
|
||||
</div>
|
||||
{responsesSessionId && (
|
||||
<Tooltip
|
||||
title={
|
||||
<div className="text-xs">
|
||||
<div className="mb-1">Copy response ID to continue session:</div>
|
||||
<div className="bg-gray-800 text-gray-100 p-2 rounded font-mono text-xs whitespace-pre-wrap">
|
||||
{`curl -X POST "your-proxy-url/v1/responses" \\
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopySessionId}
|
||||
className="ml-2 p-1 hover:bg-emerald-100 dark:hover:bg-emerald-900 rounded transition-colors"
|
||||
aria-label="Copy response ID"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[500px]">
|
||||
<div className="text-xs">
|
||||
<div className="mb-1">
|
||||
Copy response ID to continue session:
|
||||
</div>
|
||||
<div className="bg-gray-800 text-gray-100 p-2 rounded font-mono text-xs whitespace-pre-wrap">
|
||||
{`curl -X POST "your-proxy-url/v1/responses" \\
|
||||
-H "Authorization: Bearer your-api-key" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
|
|
@ -97,18 +148,16 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
|
|||
"previous_response_id": "${responsesSessionId}",
|
||||
"stream": true
|
||||
}'`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
overlayStyle={{ maxWidth: "500px" }}
|
||||
>
|
||||
<button onClick={handleCopySessionId} className="ml-2 p-1 hover:bg-green-100 rounded transition-colors">
|
||||
<CopyOutlined style={{ fontSize: "12px" }} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs opacity-75 mt-1">{getSessionDescription()}</div>
|
||||
<div className="text-xs opacity-75 mt-1">
|
||||
{getSessionDescription()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,41 +1,51 @@
|
|||
import React from "react";
|
||||
import { RobotOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { Bot, User } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import ResponseMetrics from "../../../playground/chat_ui/ResponseMetrics";
|
||||
import { Message } from "./types";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MessageBubbleProps {
|
||||
message: Message;
|
||||
}
|
||||
|
||||
const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
||||
const isUser = message.role === "user";
|
||||
return (
|
||||
<div className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"mb-4 flex",
|
||||
isUser ? "justify-end" : "justify-start",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="max-w-[85%] rounded-lg shadow-sm p-3.5 px-4"
|
||||
style={{
|
||||
backgroundColor: message.role === "user" ? "#f0f8ff" : "#ffffff",
|
||||
border: message.role === "user" ? "1px solid #e6f0fa" : "1px solid #f0f0f0",
|
||||
}}
|
||||
className={cn(
|
||||
"max-w-[85%] rounded-lg shadow-sm p-3.5 px-4 border",
|
||||
isUser
|
||||
? "bg-blue-50 border-blue-100 dark:bg-blue-950/40 dark:border-blue-900"
|
||||
: "bg-background border-border",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<div
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full mr-1"
|
||||
style={{
|
||||
backgroundColor: message.role === "user" ? "#e6f0fa" : "#f5f5f5",
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center justify-center w-6 h-6 rounded-full mr-1",
|
||||
isUser
|
||||
? "bg-blue-100 dark:bg-blue-950"
|
||||
: "bg-muted",
|
||||
)}
|
||||
>
|
||||
{message.role === "user" ? (
|
||||
<UserOutlined style={{ fontSize: "12px", color: "#2563eb" }} />
|
||||
{isUser ? (
|
||||
<User className="h-3 w-3 text-blue-600 dark:text-blue-400" />
|
||||
) : (
|
||||
<RobotOutlined style={{ fontSize: "12px", color: "#4b5563" }} />
|
||||
<Bot className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<strong className="text-sm capitalize">{message.role}</strong>
|
||||
{message.role === "assistant" && message.model && (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600 font-normal">
|
||||
{!isUser && message.model && (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground font-normal">
|
||||
{message.model}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -50,22 +60,23 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
|||
hyphens: "auto",
|
||||
}}
|
||||
>
|
||||
{message.role === "assistant" ? (
|
||||
{!isUser ? (
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
code({
|
||||
node,
|
||||
inline,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<"code"> & {
|
||||
inline?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
node?: any;
|
||||
}) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
style={coy as any}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
|
|
@ -78,7 +89,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
|||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code
|
||||
className={`${className} px-1.5 py-0.5 rounded bg-gray-100 text-sm font-mono`}
|
||||
className={`${className} px-1.5 py-0.5 rounded bg-muted text-sm font-mono`}
|
||||
style={{ wordBreak: "break-word" }}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -86,8 +97,11 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
|||
</code>
|
||||
);
|
||||
},
|
||||
pre: ({ node, ...props }) => (
|
||||
<pre style={{ overflowX: "auto", maxWidth: "100%" }} {...props} />
|
||||
pre: ({ ...props }) => (
|
||||
<pre
|
||||
style={{ overflowX: "auto", maxWidth: "100%" }}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
|
|
@ -97,8 +111,10 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
|||
<div className="whitespace-pre-wrap">{message.content}</div>
|
||||
)}
|
||||
|
||||
{message.role === "assistant" &&
|
||||
(message.timeToFirstToken || message.totalLatency || message.usage) && (
|
||||
{!isUser &&
|
||||
(message.timeToFirstToken ||
|
||||
message.totalLatency ||
|
||||
message.usage) && (
|
||||
<ResponseMetrics
|
||||
timeToFirstToken={message.timeToFirstToken}
|
||||
totalLatency={message.totalLatency}
|
||||
|
|
@ -112,4 +128,3 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
|||
};
|
||||
|
||||
export default MessageBubble;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue