From 5b287f66d7847e90ba8b6ad4071fe33217a152da Mon Sep 17 00:00:00 2001 From: tin-berri Date: Tue, 22 Sep 2026 18:06:40 -0700 Subject: [PATCH] feat(ui): add native LiteAdmin assistant (#42443) --- ui/litellm-dashboard/package-lock.json | 19 + ui/litellm-dashboard/package.json | 1 + .../hooks/proxySettings/useProxySettings.ts | 18 +- .../src/app/(dashboard)/layout.tsx | 2 + .../components/chat_ui/ChatComposer.tsx | 10 +- .../src/components/chat/ChatMessages.tsx | 132 ++-- .../src/components/chat/design.md | 68 +- .../liteadmin/LiteAdmin.integration.test.tsx | 509 +++++++++++++++ .../src/components/liteadmin/LiteAdmin.tsx | 247 +++++++ .../liteadmin/LiteAdminConversation.tsx | 180 ++++++ .../src/components/liteadmin/agent.test.ts | 607 ++++++++++++++++++ .../src/components/liteadmin/agent.ts | 125 ++++ .../src/components/liteadmin/operations.ts | 328 ++++++++++ .../components/liteadmin/toolResults.test.ts | 178 +++++ .../src/components/liteadmin/toolResults.ts | 161 +++++ .../src/components/liteadmin/useLiteAdmin.ts | 186 ++++++ .../components/llm_calls/chat_completion.tsx | 11 +- .../llm_calls/gateway_client.test.ts | 51 ++ .../components/llm_calls/gateway_client.ts | 23 + .../src/components/navbar.test.tsx | 11 +- .../src/components/ui/Bubble.tsx | 74 +++ 21 files changed, 2850 insertions(+), 91 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.integration.test.tsx create mode 100644 ui/litellm-dashboard/src/components/liteadmin/LiteAdmin.tsx create mode 100644 ui/litellm-dashboard/src/components/liteadmin/LiteAdminConversation.tsx create mode 100644 ui/litellm-dashboard/src/components/liteadmin/agent.test.ts create mode 100644 ui/litellm-dashboard/src/components/liteadmin/agent.ts create mode 100644 ui/litellm-dashboard/src/components/liteadmin/operations.ts create mode 100644 ui/litellm-dashboard/src/components/liteadmin/toolResults.test.ts create mode 100644 ui/litellm-dashboard/src/components/liteadmin/toolResults.ts create mode 100644 ui/litellm-dashboard/src/components/liteadmin/useLiteAdmin.ts create mode 100644 ui/litellm-dashboard/src/components/llm_calls/gateway_client.test.ts create mode 100644 ui/litellm-dashboard/src/components/llm_calls/gateway_client.ts create mode 100644 ui/litellm-dashboard/src/components/ui/Bubble.tsx diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index 1f459bc50ea..0b71b51dc3f 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -13,6 +13,7 @@ "@headlessui/tailwindcss": "0.2.2", "@heroicons/react": "1.0.6", "@hookform/resolvers": "5.4.0", + "@shadcn/react": "0.3.1", "@tanstack/react-pacer": "0.22.1", "@tanstack/react-query": "5.100.7", "@tanstack/react-table": "8.21.3", @@ -2990,6 +2991,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@shadcn/react": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@shadcn/react/-/react-0.3.1.tgz", + "integrity": "sha512-2gOR0HDMtWeRsCZfNDaU0YDFdgH3zsDQ6lz67Fv/y/qjY9y+R8kJqAn6q56phqv7/zHi0wqURjntrNO9zL7vnQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">=19", + "react": ">=19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index 9786d5c1d6e..233a0e63881 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -29,6 +29,7 @@ "@headlessui/tailwindcss": "0.2.2", "@heroicons/react": "1.0.6", "@hookform/resolvers": "5.4.0", + "@shadcn/react": "0.3.1", "@tanstack/react-pacer": "0.22.1", "@tanstack/react-query": "5.100.7", "@tanstack/react-table": "8.21.3", diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxySettings/useProxySettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxySettings/useProxySettings.ts index 7925af223ae..b7b28c47f9c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxySettings/useProxySettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxySettings/useProxySettings.ts @@ -1,4 +1,4 @@ -import { fetchProxySettings } from "@/utils/proxyUtils"; +import { getProxyBaseUrl, getProxyUISettings } from "@/components/networking"; import { useQuery } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; @@ -18,11 +18,19 @@ const EMPTY_PROXY_SETTINGS: ProxySettings = { LITELLM_UI_API_DOC_BASE_URL: null, }; -export default function useProxySettings(accessToken: string | null): ProxySettings { - const { data } = useQuery({ - queryKey: [...proxySettingsKeys.all, accessToken], - queryFn: () => fetchProxySettings(accessToken), +export function useProxySettingsQuery(accessToken: string | null) { + const managementBaseUrl = getProxyBaseUrl(); + return useQuery({ + queryKey: [...proxySettingsKeys.all, managementBaseUrl, accessToken], + queryFn: () => { + if (getProxyBaseUrl() !== managementBaseUrl) throw new Error("Gateway changed while loading settings."); + return accessToken ? getProxyUISettings(accessToken) : null; + }, enabled: Boolean(accessToken), }); +} + +export default function useProxySettings(accessToken: string | null): ProxySettings { + const { data } = useProxySettingsQuery(accessToken); return data ?? EMPTY_PROXY_SETTINGS; } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 2e903c7b150..03612cee3c6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -13,6 +13,7 @@ import { NoRedisWarningBanner } from "@/components/NoRedisWarningBanner"; import { EnvCredentialLoginWarningBanner } from "@/components/EnvCredentialLoginWarningBanner"; import { LicenseExpiryBanner } from "@/components/LicenseExpiryBanner"; import { UserBanner } from "@/components/UserBanner"; +import LiteAdmin from "@/components/liteadmin/LiteAdmin"; import { UpgradeBanner } from "@/components/UpgradeBanner"; import { uiHref } from "@/utils/uiHref"; import { PluginModeProvider, usePluginMode } from "@/contexts/PluginModeContext"; @@ -141,6 +142,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
{children}
+ ); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx index 4925e775ac7..5d2edbdfc94 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx @@ -56,14 +56,16 @@ export function ChatComposer({ {showSuggestions && suggestions.length > 0 && (
{suggestions.map((suggestion) => ( - + {suggestion} + ))}
)} diff --git a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx index 099105586c8..5b331cc81bc 100644 --- a/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx +++ b/ui/litellm-dashboard/src/components/chat/ChatMessages.tsx @@ -4,8 +4,11 @@ import { Wrench, Copy, Check, Pencil } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Button } from "@/components/ui/button"; +import { Bubble, BubbleContent } from "@/components/ui/Bubble"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Textarea } from "@/components/ui/textarea"; import React, { useEffect, useRef, useState } from "react"; -import ReactMarkdown from "react-markdown"; +import ReactMarkdown, { type Components } from "react-markdown"; import remarkGfm from "remark-gfm"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; @@ -64,6 +67,25 @@ function MarkdownCodeRenderer({ ); } +const markdownComponents: Components = { + code: MarkdownCodeRenderer, + pre: ({ node, ...props }) =>
,
+  p: ({ node, ...props }) => 

, + ul: ({ node, ...props }) =>

    , + ol: ({ node, ...props }) =>
      , + table: ({ node, ...props }) => , + thead: ({ node, ...props }) => , + tbody: ({ node, ...props }) => , + tr: ({ node, ...props }) => , + th: ({ node, ...props }) => , + td: ({ node, ...props }) => , +}; + +const markdownWithoutImages: Components = { + ...markdownComponents, + img: ({ alt }) => {alt || "Image omitted"}, +}; + interface UserBubbleProps { message: ChatMessage; onEdit?: (messageId: string, newContent: string) => void; @@ -75,6 +97,7 @@ function UserBubble({ message, onEdit, isStreaming }: UserBubbleProps) { const [editing, setEditing] = useState(false); const [editValue, setEditValue] = useState(message.content); const textareaRef = useRef(null); + const showEditAction = hovered && !isStreaming && Boolean(onEdit); useEffect(() => { if (editing && textareaRef.current) { @@ -113,8 +136,9 @@ function UserBubble({ message, onEdit, isStreaming }: UserBubbleProps) { return (
      -