Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/web/src/components/chat/ComposerModelEffort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getProviderOptionCurrentLabel } from "@t3tools/shared/model";
import { CheckIcon, ChevronRightIcon, ChevronUpIcon, WorkflowIcon } from "lucide-react";
import { type KeyboardEvent, type ReactNode, use, useRef, useState } from "react";

import { cn } from "~/lib/utils";
import { cn, titleWhenTruncated } from "~/lib/utils";
import {
Menu,
MenuPopup,
Expand Down Expand Up @@ -515,7 +515,12 @@ export function ComposerEffortColumn(props: { traits: TraitsSelectionInput | nul
<span className="text-muted-foreground">
{controls?.effort?.descriptor.label ?? (controls?.others.length ? "Options" : "Effort")}
</span>
<span className="ms-auto min-w-0 truncate text-muted-foreground text-xs">
<span
// A new label must not keep the last one's title while the pointer stays.
key={picker.modelLabel}
className="ms-auto min-w-0 truncate text-muted-foreground text-xs"
onPointerEnter={titleWhenTruncated(picker.modelLabel)}
>
{picker.modelLabel}
</span>
</div>
Expand Down
13 changes: 6 additions & 7 deletions apps/web/src/components/chat/ModelListRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
import { Kbd } from "../ui/kbd";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { cn } from "~/lib/utils";
import { cn, titleWhenTruncated } from "~/lib/utils";
import { modelPickerModelKey } from "./modelPickerKeys";
import { ProviderInstanceIcon } from "./ProviderInstanceIcon";

Expand Down Expand Up @@ -69,19 +69,18 @@ export const ModelListRow = memo(function ModelListRow(props: {
<div className="flex min-w-0 flex-1 items-center gap-2 text-left">
<div
className="min-w-0 truncate text-sm font-normal leading-snug"
// Names the row in full on hover only when it is cut off.
onPointerEnter={(event) => {
const name = event.currentTarget;
name.title = name.scrollWidth > name.clientWidth ? getDisplayModelName(model) : "";
}}
onPointerEnter={titleWhenTruncated(getDisplayModelName(model))}
>
{getDisplayModelName(model)}
{/* The logo is decorative, so name the provider for screen readers. */}
<span className="sr-only">, {props.providerDisplayName}</span>
</div>
{props.isSelected ? <CheckIcon className="size-3.5 shrink-0" aria-hidden="true" /> : null}
{props.subProvider ? (
<span className="min-w-0 shrink truncate text-xs text-muted-foreground/70">
<span
className="min-w-0 shrink truncate text-xs text-muted-foreground/70"
onPointerEnter={titleWhenTruncated(props.subProvider)}
>
{props.subProvider}
</span>
) : null}
Expand Down
14 changes: 12 additions & 2 deletions apps/web/src/components/chat/ProviderModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { createContext, memo, type ReactNode, useRef, useState } from "react";
import { Badge } from "../ui/badge";
import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { cn } from "~/lib/utils";
import { cn, titleWhenTruncated } from "~/lib/utils";
import { ModelPickerContent, resolveModelPickerSelectedModel } from "./ModelPickerContent";
import { ChatGptSharingControl } from "./ChatGptSharingControl";
import { ProviderInstanceIcon } from "./ProviderInstanceIcon";
Expand Down Expand Up @@ -388,7 +388,17 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
</span>
) : null}
{props.isComposerOwned ? (
<span {...triggerTextProps}>{triggerText}</span>
// The composer trigger has no hover tooltip; a cut-off name still shows in full.
<span
// A new label must not keep the last one's title while the pointer stays.
key={props.triggerLabel ?? allModelNames ?? triggerLabel}
{...triggerTextProps}
onPointerEnter={titleWhenTruncated(
props.triggerLabel ?? allModelNames ?? triggerLabel,
)}
>
{triggerText}
</span>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
) : (
<Tooltip>
<TooltipTrigger render={<span {...triggerTextProps} />}>{triggerText}</TooltipTrigger>
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ export function cn(...inputs: CxOptions) {

export { isMacPlatform };

/** `onPointerEnter` for a truncating element: shows `text` as its title only when it is cut off. */
export function titleWhenTruncated(text: string) {
return (event: { currentTarget: HTMLElement }) => {
const element = event.currentTarget;
element.title = element.scrollWidth > element.clientWidth ? text : "";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
};
}

export function isWindowsPlatform(platform: string): boolean {
return /^win(dows)?/i.test(platform);
}
Expand Down
Loading