From 414ed03bd7f2301a68d88b402fb887e086d5fa40 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Tue, 10 Mar 2026 16:16:25 -0700 Subject: [PATCH] feat(chat-ui): responses API + MCP tool execution display - Switch /chat from chat completions to responses API (previous_response_id session chaining) - Add MCP server picker with search filter in chat input bar - Show MCP tool call events (list_tools + call_tool) inline in chat via MCPEventsDisplay - Add tool chip strip showing available tools when MCP servers are selected - Non-blocking MCP toggle: server added immediately, verification in background (works for no-auth MCPs like deepwiki) - Add truncateAfterMessage to useChatHistory for edit/retry - Sync activeConversationId on URL change (fixes stale conversation on new chat) - Add "Open Chat" shortcut button to sidebar --- .../app/(dashboard)/components/Sidebar2.tsx | 36 ++ .../src/components/chat/ChatMessages.tsx | 229 ++++++-- .../src/components/chat/ChatPage.tsx | 489 ++++++++++++------ .../src/components/chat/MCPAppsPanel.tsx | 296 +++++++++++ .../src/components/chat/useChatHistory.ts | 26 + 5 files changed, 885 insertions(+), 191 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx index b2926e7f608..9c2b47946a5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx @@ -462,6 +462,42 @@ const Sidebar2: React.FC = ({ accessToken, userRole, defaultSelect /> {isAdminRole(userRole) && !collapsed && } + + {/* Pinned "Open Chat" button at bottom */} +
+ { + (e.currentTarget as HTMLAnchorElement).style.background = "#0958d9"; + }} + onMouseLeave={(e) => { + (e.currentTarget as HTMLAnchorElement).style.background = "#1677ff"; + }} + > + + {!collapsed && Open Chat} + +
); diff --git a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx index b3114564000..b0330d5e6da 100644 --- a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx +++ b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx @@ -1,8 +1,8 @@ "use client"; -import { ToolOutlined } from "@ant-design/icons"; -import { Collapse } from "antd"; -import React, { useEffect, useRef } from "react"; +import { ToolOutlined, CopyOutlined, CheckOutlined, EditOutlined } from "@ant-design/icons"; +import { Collapse, Tooltip } from "antd"; +import React, { useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; @@ -68,33 +68,157 @@ function MarkdownCodeRenderer({ interface UserBubbleProps { message: ChatMessage; + onEdit?: (messageId: string, newContent: string) => void; + isStreaming?: boolean; } -function UserBubble({ message }: UserBubbleProps) { - return ( -
-
- {message.content} +function UserBubble({ message, onEdit, isStreaming }: UserBubbleProps) { + const [hovered, setHovered] = useState(false); + const [editing, setEditing] = useState(false); + const [editValue, setEditValue] = useState(message.content); + const textareaRef = useRef(null); + + useEffect(() => { + if (editing && textareaRef.current) { + textareaRef.current.focus(); + textareaRef.current.selectionStart = textareaRef.current.value.length; + } + }, [editing]); + + // Auto-resize textarea + useEffect(() => { + const ta = textareaRef.current; + if (!ta) return; + ta.style.height = "auto"; + ta.style.height = `${ta.scrollHeight}px`; + }, [editValue, editing]); + + const handleSave = () => { + const trimmed = editValue.trim(); + if (trimmed && trimmed !== message.content && onEdit) { + onEdit(message.id, trimmed); + } + setEditing(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSave(); + } + if (e.key === "Escape") { + setEditValue(message.content); + setEditing(false); + } + }; + + if (editing) { + return ( +
+
+