diff --git a/web/src/app/chat/input/LLMPopover.tsx b/web/src/app/chat/input/LLMPopover.tsx index 1a4b6ab08..6b67392be 100644 --- a/web/src/app/chat/input/LLMPopover.tsx +++ b/web/src/app/chat/input/LLMPopover.tsx @@ -1,4 +1,9 @@ -import React, { useState, useEffect } from "react"; +import React, { + useState, + useEffect, + useCallback, + useLayoutEffect, +} from "react"; import { Popover, PopoverContent, @@ -28,6 +33,7 @@ import { FiAlertTriangle } from "react-icons/fi"; import { Slider } from "@/components/ui/slider"; import { useUser } from "@/components/user/UserProvider"; +import { TruncatedText } from "@/components/ui/truncatedText"; interface LLMPopoverProps { llmProviders: LLMProviderDescriptor[]; @@ -158,9 +164,7 @@ export default function LLMPopover({ size: 16, className: "flex-none my-auto text-black", })} - - {getDisplayNameForModel(name)} - + {(() => { if (currentAssistant?.llm_model_version_override === name) { return ( diff --git a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx index 819d5f0b6..0b3c1d5d1 100644 --- a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx +++ b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx @@ -4,10 +4,7 @@ import React, { ForwardedRef, forwardRef, useContext, - useState, useCallback, - useLayoutEffect, - useRef, } from "react"; import Link from "next/link"; import { @@ -50,9 +47,9 @@ import { } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { CirclePlus, CircleX, PinIcon } from "lucide-react"; +import { CircleX, PinIcon } from "lucide-react"; import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; -import { turborepoTraceAccess } from "next/dist/build/turborepo-access-trace"; +import { TruncatedText } from "@/components/ui/truncatedText"; interface HistorySidebarProps { liveAssistant?: Persona | null; @@ -101,24 +98,6 @@ const SortableAssistant: React.FC = ({ ...(isDragging ? { zIndex: 1000, position: "relative" as const } : {}), }; - const nameRef = useRef(null); - const hiddenNameRef = useRef(null); - const [isNameTruncated, setIsNameTruncated] = useState(false); - - useLayoutEffect(() => { - const checkTruncation = () => { - if (nameRef.current && hiddenNameRef.current) { - const visibleWidth = nameRef.current.offsetWidth; - const fullTextWidth = hiddenNameRef.current.offsetWidth; - setIsNameTruncated(fullTextWidth > visibleWidth); - } - }; - - checkTruncation(); - window.addEventListener("resize", checkTruncation); - return () => window.removeEventListener("resize", checkTruncation); - }, [assistant.name]); - return (
= ({ } relative flex items-center gap-x-2 py-1 px-2 rounded-md`} > - - - -

- {assistant.name} -

-
- {isNameTruncated && ( - {assistant.name} - )} -
-
- - {assistant.name} - + + diff --git a/web/src/components/ui/truncatedText.tsx b/web/src/components/ui/truncatedText.tsx new file mode 100644 index 000000000..cd339600b --- /dev/null +++ b/web/src/components/ui/truncatedText.tsx @@ -0,0 +1,86 @@ +import React, { + useState, + useRef, + useLayoutEffect, + HTMLAttributes, +} from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +interface TruncatedTextProps extends HTMLAttributes { + text: string; + tooltipClassName?: string; + tooltipSide?: "top" | "right" | "bottom" | "left"; + tooltipSideOffset?: number; +} + +/** + * Renders passed in text on a single line. If text is truncated, + * shows a tooltip on hover with the full text. + */ +export function TruncatedText({ + text, + tooltipClassName, + tooltipSide = "right", + tooltipSideOffset = 5, + className = "", + ...rest +}: TruncatedTextProps) { + const [isTruncated, setIsTruncated] = useState(false); + const visibleRef = useRef(null); + const hiddenRef = useRef(null); + + useLayoutEffect(() => { + function checkTruncation() { + if (visibleRef.current && hiddenRef.current) { + const visibleWidth = visibleRef.current.offsetWidth; + const fullTextWidth = hiddenRef.current.offsetWidth; + setIsTruncated(fullTextWidth > visibleWidth); + } + } + + checkTruncation(); + window.addEventListener("resize", checkTruncation); + return () => window.removeEventListener("resize", checkTruncation); + }, [text]); + + return ( + + + + + {text} + + + {/* Hide offscreen to measure full text width */} + + {isTruncated && ( + +

+ {text} +

+
+ )} +
+
+ ); +}