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 new file mode 100644 index 00000000000..791c2192c1d --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx @@ -0,0 +1,184 @@ +import React, { useEffect, useRef } from "react"; +import { ArrowUp, Code2, Square } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@/components/ui/input-group"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/cva.config"; + +interface ChatComposerProps { + value: string; + onChange: (value: string) => void; + onSubmit: () => void; + onCancel?: () => void; + placeholder: string; + disabled?: boolean; + isLoading?: boolean; + submitDisabled?: boolean; + tools?: React.ReactNode; + body?: React.ReactNode; + suggestions?: string[]; + showSuggestions?: boolean; + onSuggestionSelect?: (suggestion: string) => void; + className?: string; +} + +export function ChatComposer({ + value, + onChange, + onSubmit, + onCancel, + placeholder, + disabled = false, + isLoading = false, + submitDisabled = false, + tools, + body, + suggestions = [], + showSuggestions = false, + onSuggestionSelect, + className, +}: ChatComposerProps) { + const textareaRef = useRef(null); + + useEffect(() => { + const el = textareaRef.current; + if (!el || body) { + return; + } + el.style.height = "0px"; + el.style.height = `${Math.min(el.scrollHeight, 192)}px`; + }, [value, body]); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) { + event.preventDefault(); + if (!submitDisabled && !isLoading) { + onSubmit(); + } + } + }; + + return ( +
+ {showSuggestions && suggestions.length > 0 && ( +
+ {suggestions.map((suggestion) => ( + + ))} +
+ )} + +
{ + event.preventDefault(); + if (!submitDisabled && !isLoading) { + onSubmit(); + } + }} + > + + {body ? ( +
{body}
+ ) : ( + onChange(event.target.value)} + onKeyDown={handleKeyDown} + /> + )} + + +
{tools}
+ + {isLoading && onCancel ? ( + + + + ) : ( + + + + )} +
+
+
+
+ ); +} + +interface CodeInterpreterToggleProps { + enabled: boolean; + onToggle: () => void; +} + +export function CodeInterpreterToggle({ enabled, onToggle }: CodeInterpreterToggleProps) { + return ( + + + } + > + + + + {enabled ? "Code Interpreter enabled (click to disable)" : "Enable Code Interpreter"} + + + ); +} + +export default ChatComposer; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx index 86bc638a421..4a205a60732 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx @@ -1,7 +1,6 @@ "use client"; import { - ArrowUp, Bot, Code2, Database, @@ -47,6 +46,7 @@ import { makeOpenAIResponsesRequest } from "@/components/llm_calls/responses_api import { makeInteractionsRequest } from "../../llm_calls/interactions_api"; import AdditionalModelSettings from "./AdditionalModelSettings"; import { OPEN_AI_VOICE_SELECT_OPTIONS, OpenAIVoice } from "./chatConstants"; +import ChatComposer, { CodeInterpreterToggle } from "./ChatComposer"; import ChatImageUpload from "./ChatImageUpload"; import { createChatDisplayMessage, createChatMultimodalMessage } from "./ChatImageUtils"; import CodeInterpreterTool from "./CodeInterpreterTool"; @@ -72,7 +72,6 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from " import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select as ShadcnSelect, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer"; import { @@ -505,14 +504,6 @@ const ChatUI: React.FC = ({ } }, [chatHistory]); - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); // Prevent default to avoid newline - handleSendMessage(); - } - // If Shift+Enter is pressed, the default behavior (inserting a newline) will occur - }; - const handleCancelRequest = () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); @@ -1975,27 +1966,24 @@ const ChatUI: React.FC = ({ )} - {chatHistory.length === 0 && !isLoading && endpointType !== EndpointType.MCP && ( -
- {(endpointType === EndpointType.A2A_AGENTS + ( - - ))} -
- )} - -
-
-
+ } + onSuggestionSelect={setInputMessage} + tools={ + <> {endpointType === EndpointType.RESPONSES && !responsesUploadedImage && ( = ({ /> )} {endpointType === EndpointType.RESPONSES && ( - - { - codeInterpreter.toggle(); - if (!codeInterpreter.enabled) { - NotificationsManager.success("Code Interpreter enabled!"); - } - }} - /> + { + codeInterpreter.toggle(); + if (!codeInterpreter.enabled) { + NotificationsManager.success("Code Interpreter enabled!"); } - > - - - - {codeInterpreter.enabled - ? "Code Interpreter enabled (click to disable)" - : "Enable Code Interpreter"} - - + }} + /> )} -
- - {endpointType === EndpointType.MCP && + + } + body={ + endpointType === EndpointType.MCP && selectedMCPServers.length === 1 && selectedMCPServers[0] !== "__all__" && - selectedMCPDirectTool ? ( -
- {(() => { + selectedMCPDirectTool + ? (() => { const rawSel = selectedMCPServers[0]; let toolPool: MCPTool[] = []; if (rawSel.startsWith("toolset:")) { @@ -2076,45 +2041,10 @@ const ChatUI: React.FC = ({ Loading tool schema...
); - })()} -
- ) : ( -