fix(ui): restore safe model tooltips

This commit is contained in:
777genius 2026-05-17 01:07:38 +03:00
parent a3a286c652
commit c1a2ccc0a7
3 changed files with 167 additions and 25 deletions

View file

@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
import { Checkbox } from '@renderer/components/ui/checkbox';
import { HoverTooltip } from '@renderer/components/ui/hover-tooltip';
import { Input } from '@renderer/components/ui/input';
import { Label } from '@renderer/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
@ -47,6 +48,7 @@ import {
CheckCircle2,
ChevronDown,
Filter,
Info,
Search,
Star,
} from 'lucide-react';
@ -819,6 +821,18 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
<span>{modelRecommendation.label}</span>
</span>
) : null}
{opt.value === '' ? (
<span className="flex items-center justify-center gap-1">
<HoverTooltip
content={defaultModelTooltip}
title={defaultModelTooltip}
stopClickPropagation
contentClassName="max-w-[240px]"
>
<Info className="size-3 shrink-0 opacity-45 transition-opacity hover:opacity-75" />
</HoverTooltip>
</span>
) : null}
{hasModelIssue && (
<span
className="flex items-center justify-center gap-1 text-[10px] font-normal text-red-300"
@ -826,6 +840,16 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
>
<AlertTriangle className="size-3 shrink-0" />
<span>{modelUnavailableReason ? 'Unavailable' : 'Issue'}</span>
{modelStatusMessage ? (
<HoverTooltip
content={modelStatusMessage}
title={modelStatusMessage}
stopClickPropagation
contentClassName="max-w-[240px]"
>
<Info className="size-3 shrink-0 opacity-55 transition-opacity hover:opacity-85" />
</HoverTooltip>
) : null}
</span>
)}
{!hasModelIssue && modelDisabledReason && (
@ -834,6 +858,14 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
title={modelDisabledReason}
>
<span>{TEAM_MODEL_UI_DISABLED_BADGE_LABEL}</span>
<HoverTooltip
content={modelDisabledReason}
title={modelDisabledReason}
stopClickPropagation
contentClassName="max-w-[240px]"
>
<Info className="size-3 shrink-0 opacity-45 transition-opacity hover:opacity-75" />
</HoverTooltip>
</span>
)}
</span>

View file

@ -12,6 +12,7 @@ import {
import { RoleSelect } from '@renderer/components/team/RoleSelect';
import { Button } from '@renderer/components/ui/button';
import { Checkbox } from '@renderer/components/ui/checkbox';
import { HoverTooltip } from '@renderer/components/ui/hover-tooltip';
import { Input } from '@renderer/components/ui/input';
import { Label } from '@renderer/components/ui/label';
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
@ -262,6 +263,21 @@ export const MemberDraftRow = ({
const modelHelpDescriptionId = modelTooltipText ? `member-${member.id}-model-help` : undefined;
const modelButtonDescribedBy =
[modelIssueDescriptionId, modelHelpDescriptionId].filter(Boolean).join(' ') || undefined;
const modelButtonTooltipContent =
currentModelIssueText || modelTooltipText ? (
<>
{currentModelIssueText ? (
<span className="block text-red-300">{currentModelIssueText}</span>
) : null}
{modelTooltipText ? (
<span
className={cn('block', currentModelIssueText && 'mt-1 border-t border-white/10 pt-1')}
>
{modelTooltipText}
</span>
) : null}
</>
) : null;
const hasCustomProviderOrModel =
!forceInheritedModelSettings && Boolean(member.providerId || member.model?.trim());
const showSonnetExtraUsageWarning =
@ -357,7 +373,13 @@ export const MemberDraftRow = ({
</Button>
) : null}
<div className="w-full min-w-0 space-y-1 sm:w-[150px] sm:min-w-[150px]">
<span className="inline-flex w-full" title={modelButtonTitle}>
<HoverTooltip
content={modelButtonTooltipContent}
title={modelButtonTitle}
disabled={!modelButtonTooltipContent}
className="w-full"
contentClassName="max-w-64"
>
<Button
variant="outline"
size="sm"
@ -382,7 +404,7 @@ export const MemberDraftRow = ({
<AlertTriangle className="size-3.5 shrink-0 text-red-300" />
) : null}
</Button>
</span>
</HoverTooltip>
{modelTooltipText ? (
<span id={modelHelpDescriptionId} className="sr-only">
{modelTooltipText}
@ -400,34 +422,41 @@ export const MemberDraftRow = ({
</div>
{showWorktreeIsolationControls ? (
<div className="space-y-0.5">
<div
className={cn(
'flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded-md border border-[var(--color-border)] px-2 text-xs text-[var(--color-text-secondary)]',
worktreeIsolationDisabled && 'cursor-not-allowed opacity-50'
)}
<HoverTooltip
as="div"
content={worktreeIsolationDescription}
title={worktreeIsolationDescription}
aria-describedby={worktreeIsolationDescriptionId}
className="shrink-0"
contentClassName="max-w-64"
>
<Checkbox
id={`member-${member.id}-worktree-isolation`}
checked={member.isolation === 'worktree'}
disabled={worktreeIsolationDisabled}
aria-describedby={worktreeIsolationDescriptionId}
onCheckedChange={(checked) =>
onWorktreeIsolationChange?.(member.id, checked === true)
}
/>
<Label
htmlFor={`member-${member.id}-worktree-isolation`}
<div
className={cn(
'flex cursor-pointer items-center gap-1.5 text-xs font-normal',
worktreeIsolationDisabled && 'cursor-not-allowed'
'flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-[var(--color-border)] px-2 text-xs text-[var(--color-text-secondary)]',
worktreeIsolationDisabled && 'cursor-not-allowed opacity-50'
)}
aria-describedby={worktreeIsolationDescriptionId}
>
<GitBranch className="size-3.5 shrink-0" />
<span>Worktree</span>
</Label>
</div>
<Checkbox
id={`member-${member.id}-worktree-isolation`}
checked={member.isolation === 'worktree'}
disabled={worktreeIsolationDisabled}
aria-describedby={worktreeIsolationDescriptionId}
onCheckedChange={(checked) =>
onWorktreeIsolationChange?.(member.id, checked === true)
}
/>
<Label
htmlFor={`member-${member.id}-worktree-isolation`}
className={cn(
'flex cursor-pointer items-center gap-1.5 text-xs font-normal',
worktreeIsolationDisabled && 'cursor-not-allowed'
)}
>
<GitBranch className="size-3.5 shrink-0" />
<span>Worktree</span>
</Label>
</div>
</HoverTooltip>
<span id={worktreeIsolationDescriptionId} className="sr-only">
{worktreeIsolationDescription}
</span>

View file

@ -0,0 +1,81 @@
import React from 'react';
import { cn } from '@renderer/lib/utils';
type HoverTooltipSide = 'top' | 'bottom';
type HoverTooltipAlign = 'start' | 'center' | 'end';
interface HoverTooltipProps {
children: React.ReactNode;
content: React.ReactNode;
align?: HoverTooltipAlign;
as?: 'span' | 'div';
className?: string;
contentClassName?: string;
disabled?: boolean;
side?: HoverTooltipSide;
stopClickPropagation?: boolean;
title?: string;
}
const sideClassBySide: Record<HoverTooltipSide, string> = {
top: 'bottom-full mb-2',
bottom: 'top-full mt-2',
};
const alignClassByAlign: Record<HoverTooltipAlign, string> = {
start: 'left-0',
center: 'left-1/2 -translate-x-1/2',
end: 'right-0',
};
const renderTooltipContent = (content: React.ReactNode): React.JSX.Element => {
return typeof content === 'string' ? (
<span className="whitespace-pre-line">{content}</span>
) : (
<span>{content}</span>
);
};
export const HoverTooltip = ({
children,
content,
align = 'center',
as = 'span',
className,
contentClassName,
disabled = false,
side = 'top',
stopClickPropagation = false,
title,
}: Readonly<HoverTooltipProps>): React.JSX.Element => {
const TooltipWrapper = as;
if (disabled || !content) {
return <TooltipWrapper className={className}>{children}</TooltipWrapper>;
}
return (
<TooltipWrapper
className={cn('group/hover-tooltip relative inline-flex min-w-0', className)}
title={title}
onClick={
stopClickPropagation ? (event: React.MouseEvent) => event.stopPropagation() : undefined
}
>
{children}
<span
aria-hidden="true"
className={cn(
'pointer-events-none absolute z-[80] w-max max-w-72 rounded-md border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-2.5 py-1.5 text-left text-xs font-normal leading-relaxed text-[var(--color-text-secondary)] opacity-0 shadow-lg ring-1 ring-black/5 transition-opacity duration-100',
'group-focus-within/hover-tooltip:opacity-100 group-hover/hover-tooltip:opacity-100',
sideClassBySide[side],
alignClassByAlign[align],
contentClassName
)}
>
{renderTooltipContent(content)}
</span>
</TooltipWrapper>
);
};