Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
588 lines
22 KiB
TypeScript
588 lines
22 KiB
TypeScript
// Model + reasoning/speed composer menus, including per-target recents and
|
|
// favorites chrome state. Extracted from AgentComposerSettingsMenus.tsx to
|
|
// keep both files under the 800-line budget.
|
|
import { CheckIcon, ChevronDown, Star, ZapIcon } from "lucide-react";
|
|
import {
|
|
Fragment,
|
|
cloneElement,
|
|
useState,
|
|
type HTMLAttributes,
|
|
type ReactElement
|
|
} from "react";
|
|
import { translate } from "../../i18n/index";
|
|
import styles from "./AgentGUINode.styles";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuTrigger,
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
cn
|
|
} from "@tutti-os/ui-system";
|
|
import type { AgentGUIComposerSettingsVM } from "./model/agentGuiNodeTypes";
|
|
import type { AgentComposerSettingsMenuLabels } from "./model/composerSettingsMenuModel";
|
|
import { ComposerOptionInfoTooltip } from "./AgentComposerModelReasoningDropdown";
|
|
import {
|
|
buildComposerModelMenuModel,
|
|
type ComposerMenuOption
|
|
} from "./model/composerSettingsMenuModel";
|
|
import { useComposerModelChoiceHistory } from "./controller/useComposerModelChoiceHistory";
|
|
|
|
export function AgentModelReasoningDropdown({
|
|
composerSettings,
|
|
disabled = false,
|
|
labels,
|
|
modelHistoryTargetId,
|
|
onSettingsChange
|
|
}: {
|
|
composerSettings: AgentGUIComposerSettingsVM;
|
|
disabled?: boolean;
|
|
labels: AgentComposerSettingsMenuLabels;
|
|
/**
|
|
* Optional test/embedding override. Omission uses the controller-projected
|
|
* exact target; explicit null disables model history.
|
|
*/
|
|
modelHistoryTargetId?: string | null;
|
|
onSettingsChange: (patch: {
|
|
model?: string;
|
|
reasoningEffort?: string;
|
|
speed?: string;
|
|
}) => void;
|
|
}): React.JSX.Element {
|
|
"use memo";
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const [modelSearchQuery, setModelSearchQuery] = useState("");
|
|
const projectedModelHistory = composerSettings.modelChoiceHistory;
|
|
const modelHistory = useComposerModelChoiceHistory({
|
|
targetId:
|
|
modelHistoryTargetId === undefined
|
|
? (projectedModelHistory?.targetId ?? null)
|
|
: modelHistoryTargetId,
|
|
catalog: projectedModelHistory?.catalog ?? null
|
|
});
|
|
const {
|
|
enabled: modelHistoryEnabled,
|
|
favoriteModelIds,
|
|
recentModelIds
|
|
} = modelHistory;
|
|
const handleMenuOpenChange = (open: boolean): void => {
|
|
if (open) {
|
|
// Pick up writes from other windows and clear the previous filter.
|
|
modelHistory.refreshFromStorage();
|
|
setModelSearchQuery("");
|
|
}
|
|
setMenuOpen(open);
|
|
};
|
|
const menu = buildComposerModelMenuModel(composerSettings, labels, {
|
|
favoriteModelIds,
|
|
recentModelIds,
|
|
searchQuery: modelSearchQuery
|
|
});
|
|
const menuDisabled = disabled || menu.disabled;
|
|
// While the model list is still loading the trigger shows a placeholder
|
|
// ("Default") that reads like a real selection. Surface a hover hint so the
|
|
// user knows the list is still loading rather than already resolved.
|
|
const isModelLoading =
|
|
composerSettings.isModelOptionsLoading ||
|
|
composerSettings.isSettingsLoading;
|
|
const applySettingsChange = (patch: {
|
|
model?: string;
|
|
reasoningEffort?: string;
|
|
speed?: string;
|
|
}): void => {
|
|
onSettingsChange(patch);
|
|
setMenuOpen(false);
|
|
};
|
|
const applyModelSelection = (value: string): void => {
|
|
modelHistory.recordRecentModel(value);
|
|
applySettingsChange({ model: value });
|
|
};
|
|
const handleToggleFavoriteModel = (value: string): void => {
|
|
modelHistory.toggleFavoriteModel(value);
|
|
};
|
|
const toggleFavoriteModel = modelHistoryEnabled
|
|
? handleToggleFavoriteModel
|
|
: undefined;
|
|
const favoriteValueSet = new Set(menu.model.favoriteValues);
|
|
const modelDescriptionPresentation = menu.model.optionDescriptionInline
|
|
? ("inline" as const)
|
|
: ("model-tooltip" as const);
|
|
const modelListEmpty =
|
|
menu.model.options.length === 0 &&
|
|
menu.model.favoriteOptions.length === 0 &&
|
|
menu.model.recentOptions.length === 0;
|
|
const trigger = (
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
"w-auto",
|
|
styles.composerMenuTrigger,
|
|
menuDisabled &&
|
|
"cursor-not-allowed text-[var(--agent-gui-text-tertiary)] opacity-60 hover:text-[var(--agent-gui-text-tertiary)]",
|
|
(composerSettings.isSettingsLoading ||
|
|
composerSettings.isModelOptionsLoading) &&
|
|
"animate-pulse"
|
|
)}
|
|
aria-label={`${labels.modelLabel} / ${labels.reasoningLabel}`}
|
|
data-agent-model-reasoning-trigger="true"
|
|
>
|
|
<span className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
|
{menu.speed.show && menu.trigger.isFast ? (
|
|
<ZapIcon
|
|
aria-hidden
|
|
className="size-3.5 shrink-0"
|
|
data-agent-speed-indicator="fast"
|
|
strokeWidth={2.5}
|
|
/>
|
|
) : null}
|
|
{menu.trigger.showCombined ? (
|
|
<span className="min-w-0 truncate">{menu.trigger.combinedLabel}</span>
|
|
) : (
|
|
<>
|
|
<span className="min-w-0 truncate">{menu.trigger.modelLabel}</span>
|
|
<span className="shrink-0">{menu.trigger.reasoningLabel}</span>
|
|
</>
|
|
)}
|
|
</span>
|
|
<ChevronDown aria-hidden="true" className="shrink-0" size={16} />
|
|
</button>
|
|
);
|
|
|
|
return (
|
|
<DropdownMenu open={menuOpen} onOpenChange={handleMenuOpenChange}>
|
|
{isModelLoading ? (
|
|
// The trigger is disabled while loading, so pointer events never reach
|
|
// it. Target the tooltip at a focusable wrapper span (Radix's pattern
|
|
// for disabled triggers) so hover/focus reliably surfaces the hint.
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span className="inline-flex" tabIndex={0}>
|
|
<DropdownMenuTrigger asChild disabled={menuDisabled}>
|
|
{trigger}
|
|
</DropdownMenuTrigger>
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top">{labels.loadingOptions}</TooltipContent>
|
|
</Tooltip>
|
|
) : (
|
|
<DropdownMenuTrigger asChild disabled={menuDisabled}>
|
|
{trigger}
|
|
</DropdownMenuTrigger>
|
|
)}
|
|
<DropdownMenuContent
|
|
align="end"
|
|
side="top"
|
|
sideOffset={4}
|
|
collisionPadding={16}
|
|
className={cn(
|
|
styles.composerMenuContent,
|
|
"w-max min-w-[360px] max-w-[calc(100vw-32px)] data-[side=top]:!translate-y-0"
|
|
)}
|
|
data-agent-composer-settings-layout="model-primary"
|
|
>
|
|
{menu.model.show ? (
|
|
<>
|
|
<DropdownMenuLabel className="flex min-w-0 items-center gap-2">
|
|
<span className="min-w-0 truncate">
|
|
{labels.modelSelectionLabel}
|
|
</span>
|
|
{menu.model.plan ? (
|
|
<span
|
|
data-agent-model-plan-badge="true"
|
|
className="min-w-0 shrink truncate rounded-full border border-[var(--line-2)] px-1.5 text-[10px] font-normal leading-4 text-[var(--agent-gui-text-tertiary)]"
|
|
>
|
|
{translate("agentHost.agentGui.composerModelPlanBadge", {
|
|
name: menu.model.plan.name
|
|
})}
|
|
</span>
|
|
) : null}
|
|
</DropdownMenuLabel>
|
|
{menu.model.searchEnabled ? (
|
|
<div
|
|
className="px-2 pb-1.5"
|
|
// Keep typing local to the input: Radix menu typeahead and
|
|
// arrow-key focus handling must not steal these key events.
|
|
onKeyDown={(event) => event.stopPropagation()}
|
|
>
|
|
<input
|
|
type="text"
|
|
value={modelSearchQuery}
|
|
data-agent-model-search-input="true"
|
|
placeholder={translate(
|
|
"agentHost.agentGui.composerModelSearchPlaceholder"
|
|
)}
|
|
className="box-border h-7 w-full min-w-0 rounded-[6px] border border-[var(--line-2)] bg-transparent px-2 text-[12px] text-[var(--text-primary)] outline-none placeholder:text-[var(--agent-gui-text-tertiary)] focus:border-[var(--tutti-purple)]"
|
|
onChange={(event) => setModelSearchQuery(event.target.value)}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
{menu.model.favoriteOptions.length > 0 ? (
|
|
<>
|
|
<DropdownMenuLabel
|
|
data-agent-model-favorites-group="true"
|
|
className="text-xs text-[var(--agent-gui-text-tertiary)]"
|
|
>
|
|
{translate("agentHost.agentGui.composerModelFavoritesGroup")}
|
|
</DropdownMenuLabel>
|
|
<ComposerMenuOptionItems
|
|
options={menu.model.favoriteOptions}
|
|
selectedValue={menu.model.selectedValue}
|
|
descriptionPresentation={modelDescriptionPresentation}
|
|
tooltipsEnabled
|
|
favoriteValues={favoriteValueSet}
|
|
onToggleFavorite={toggleFavoriteModel}
|
|
onSelect={applyModelSelection}
|
|
/>
|
|
</>
|
|
) : null}
|
|
{menu.model.recentOptions.length > 0 ? (
|
|
<>
|
|
<DropdownMenuLabel
|
|
data-agent-model-recents-group="true"
|
|
className="text-xs text-[var(--agent-gui-text-tertiary)]"
|
|
>
|
|
{translate("agentHost.agentGui.composerModelRecentsGroup")}
|
|
</DropdownMenuLabel>
|
|
<ComposerMenuOptionItems
|
|
options={menu.model.recentOptions}
|
|
selectedValue={menu.model.selectedValue}
|
|
descriptionPresentation={modelDescriptionPresentation}
|
|
tooltipsEnabled
|
|
favoriteValues={favoriteValueSet}
|
|
onToggleFavorite={toggleFavoriteModel}
|
|
onSelect={applyModelSelection}
|
|
/>
|
|
</>
|
|
) : null}
|
|
{(menu.model.favoriteOptions.length > 0 ||
|
|
menu.model.recentOptions.length > 0) &&
|
|
menu.model.options.length > 0 ? (
|
|
<DropdownMenuSeparator />
|
|
) : null}
|
|
{menu.model.groups.length > 0 ? (
|
|
menu.model.groups.map((group, index) => (
|
|
<Fragment key={group.label ?? `ungrouped-${index}`}>
|
|
{group.label !== null ? (
|
|
<DropdownMenuLabel className="text-xs text-[var(--agent-gui-text-tertiary)]">
|
|
{group.label}
|
|
</DropdownMenuLabel>
|
|
) : null}
|
|
<ComposerMenuOptionItems
|
|
options={group.options}
|
|
selectedValue={menu.model.selectedValue}
|
|
descriptionPresentation={modelDescriptionPresentation}
|
|
tooltipsEnabled
|
|
favoriteValues={favoriteValueSet}
|
|
onToggleFavorite={toggleFavoriteModel}
|
|
onSelect={applyModelSelection}
|
|
/>
|
|
</Fragment>
|
|
))
|
|
) : (
|
|
<ComposerMenuOptionItems
|
|
options={menu.model.options}
|
|
selectedValue={menu.model.selectedValue}
|
|
descriptionPresentation={modelDescriptionPresentation}
|
|
tooltipsEnabled
|
|
favoriteValues={favoriteValueSet}
|
|
onToggleFavorite={toggleFavoriteModel}
|
|
onSelect={applyModelSelection}
|
|
/>
|
|
)}
|
|
{menu.model.searchEnabled &&
|
|
menu.model.searchQuery &&
|
|
modelListEmpty ? (
|
|
<div
|
|
data-agent-model-search-empty="true"
|
|
className="px-2 py-1.5 text-[12px] text-[var(--agent-gui-text-tertiary)]"
|
|
>
|
|
{translate("agentHost.agentGui.composerModelSearchEmpty")}
|
|
</div>
|
|
) : null}
|
|
</>
|
|
) : null}
|
|
{menu.model.show && (menu.reasoning.show || menu.speed.show) ? (
|
|
<DropdownMenuSeparator />
|
|
) : null}
|
|
{menu.reasoning.show ? (
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger
|
|
className={cn(styles.composerMenuItem, "[&>svg]:!ml-0.5")}
|
|
data-agent-reasoning-submenu-trigger="true"
|
|
>
|
|
<span className="min-w-0 flex-1 truncate">
|
|
{labels.reasoningLabel}
|
|
</span>
|
|
<span className="text-[var(--text-tertiary)]">
|
|
{menu.reasoning.selectedLabel}
|
|
</span>
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent
|
|
className={cn(styles.composerMenuContent, "min-w-[132px]")}
|
|
data-agent-composer-settings-layout="model-submenu"
|
|
>
|
|
<ComposerMenuOptionItems
|
|
options={menu.reasoning.options}
|
|
selectedValue={menu.reasoning.selectedValue}
|
|
tooltipsEnabled
|
|
onSelect={(value) =>
|
|
applySettingsChange({ reasoningEffort: value })
|
|
}
|
|
/>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
) : null}
|
|
{menu.speed.show ? (
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger
|
|
className={cn(styles.composerMenuItem, "[&>svg]:!ml-0.5")}
|
|
data-agent-speed-submenu-trigger="true"
|
|
>
|
|
<span className="min-w-0 flex-1 truncate">
|
|
{labels.speedLabel}
|
|
</span>
|
|
<span className="text-[var(--text-tertiary)]">
|
|
{menu.speed.selectedLabel}
|
|
</span>
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent
|
|
className={cn(styles.composerMenuContent, "w-[200px]")}
|
|
data-agent-composer-settings-layout="model-submenu"
|
|
>
|
|
<ComposerMenuOptionItems
|
|
options={menu.speed.options}
|
|
selectedValue={menu.speed.selectedValue}
|
|
descriptionPresentation="inline"
|
|
tooltipsEnabled
|
|
onSelect={(value) => applySettingsChange({ speed: value })}
|
|
/>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
) : null}
|
|
{menu.switchEffectHint ? (
|
|
<>
|
|
<DropdownMenuSeparator />
|
|
<div
|
|
data-agent-model-switch-hint="true"
|
|
className="px-2 py-1.5 text-[11px] text-[var(--agent-gui-text-tertiary)]"
|
|
>
|
|
{translate("agentHost.agentGui.composerModelSwitchNextTurnHint")}
|
|
</div>
|
|
</>
|
|
) : null}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|
|
|
|
// Renders a list of pick-to-apply menu items. Pointer activation applies
|
|
// directly because runtime evidence showed submenu items can receive
|
|
// pointerdown while Radix onSelect never fires in this embedded menu. onSelect
|
|
// remains for keyboard activation and normal Radix paths.
|
|
function ComposerMenuOptionItems({
|
|
options,
|
|
selectedValue,
|
|
descriptionPresentation = "none",
|
|
tooltipsEnabled = true,
|
|
favoriteValues,
|
|
onToggleFavorite,
|
|
onSelect
|
|
}: {
|
|
options: ComposerMenuOption[];
|
|
selectedValue: string;
|
|
descriptionPresentation?: "inline" | "model-tooltip" | "none" | "tooltip";
|
|
tooltipsEnabled?: boolean;
|
|
/** Values currently favorited; enables the per-option star toggle. */
|
|
favoriteValues?: ReadonlySet<string>;
|
|
onToggleFavorite?: (value: string) => void;
|
|
onSelect: (value: string) => void;
|
|
}): React.JSX.Element {
|
|
return (
|
|
<>
|
|
{options.map((option) => {
|
|
const hasDescription = Boolean(option.description);
|
|
const showInlineDescription =
|
|
descriptionPresentation === "inline" && hasDescription;
|
|
const showModelTooltip = descriptionPresentation === "model-tooltip";
|
|
const showTooltipDescription =
|
|
descriptionPresentation === "tooltip" && hasDescription;
|
|
const isFavorite = favoriteValues?.has(option.value) ?? false;
|
|
const favoriteToggle = onToggleFavorite ? (
|
|
<button
|
|
type="button"
|
|
data-agent-model-favorite-toggle="true"
|
|
data-favorited={isFavorite ? "true" : "false"}
|
|
aria-label={translate(
|
|
isFavorite
|
|
? "agentHost.agentGui.composerModelFavoriteRemove"
|
|
: "agentHost.agentGui.composerModelFavoriteAdd"
|
|
)}
|
|
className={cn(
|
|
"ml-1 inline-flex size-5 shrink-0 items-center justify-center rounded-[4px] text-[var(--agent-gui-text-tertiary)] transition-opacity hover:text-[var(--text-secondary)]",
|
|
isFavorite
|
|
? "text-[var(--tutti-purple)] opacity-100 hover:text-[var(--tutti-purple)]"
|
|
: "opacity-0 group-hover/composer-option:opacity-100 group-data-[highlighted]/composer-option:opacity-100"
|
|
)}
|
|
// Stop before the DropdownMenuItem's pointerdown-apply handler:
|
|
// starring an option must not also select it or close the menu.
|
|
onPointerDown={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}}
|
|
onClick={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
onToggleFavorite(option.value);
|
|
}}
|
|
>
|
|
<Star
|
|
aria-hidden
|
|
className="size-3.5"
|
|
fill={isFavorite ? "currentColor" : "none"}
|
|
strokeWidth={2}
|
|
/>
|
|
</button>
|
|
) : null;
|
|
const item = (
|
|
<DropdownMenuItem
|
|
key={option.value}
|
|
className={cn(
|
|
styles.composerMenuItem,
|
|
"group/composer-option",
|
|
showModelTooltip &&
|
|
"min-h-[40px] max-w-full items-center px-3 py-2",
|
|
showInlineDescription && "items-start"
|
|
)}
|
|
data-agent-model-option={showModelTooltip ? "true" : undefined}
|
|
onPointerDown={(event) => {
|
|
if (event.button === 0 && !event.ctrlKey) {
|
|
event.preventDefault();
|
|
onSelect(option.value);
|
|
}
|
|
}}
|
|
onSelect={() => {
|
|
onSelect(option.value);
|
|
}}
|
|
>
|
|
{showModelTooltip ? (
|
|
<span className="flex min-w-0 flex-1 items-baseline gap-2 overflow-hidden">
|
|
<span className="min-w-0 truncate leading-[1.15]">
|
|
{option.label}
|
|
</span>
|
|
{option.summary && option.summary.length > 0 ? (
|
|
<span className="flex min-w-0 shrink-0 items-baseline gap-1.5 overflow-hidden text-[var(--agent-gui-text-tertiary)]">
|
|
{option.summary.map((summary) => (
|
|
<span
|
|
key={summary}
|
|
className="max-w-[64px] truncate leading-[1.15]"
|
|
>
|
|
{summary}
|
|
</span>
|
|
))}
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
) : (
|
|
<span
|
|
className={cn(
|
|
"flex min-w-0 flex-1 flex-col",
|
|
showInlineDescription ? "gap-0.5" : "gap-0"
|
|
)}
|
|
>
|
|
<span className="flex min-w-0 items-center gap-1.5">
|
|
<span className="min-w-0 truncate leading-[1.15]">
|
|
{option.label}
|
|
</span>
|
|
{showTooltipDescription && option.description ? (
|
|
<ComposerOptionInfoTooltip
|
|
description={option.description}
|
|
tooltipsEnabled={tooltipsEnabled}
|
|
/>
|
|
) : null}
|
|
</span>
|
|
{showInlineDescription && option.description ? (
|
|
<span className="whitespace-normal text-[11px] leading-[1.2] text-[var(--text-tertiary)]">
|
|
{option.description}
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
)}
|
|
{favoriteToggle}
|
|
<CheckIcon
|
|
aria-hidden
|
|
className={cn(
|
|
"ml-2 size-3.5 shrink-0 text-[var(--tutti-purple)]",
|
|
option.value !== selectedValue && "invisible"
|
|
)}
|
|
/>
|
|
</DropdownMenuItem>
|
|
);
|
|
return showModelTooltip ? (
|
|
<ComposerModelOptionTooltip
|
|
key={option.value}
|
|
option={option}
|
|
tooltipsEnabled={tooltipsEnabled}
|
|
>
|
|
{item}
|
|
</ComposerModelOptionTooltip>
|
|
) : (
|
|
item
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function ComposerModelOptionTooltip({
|
|
children,
|
|
option,
|
|
tooltipsEnabled = true
|
|
}: {
|
|
children: ReactElement<HTMLAttributes<HTMLElement>>;
|
|
option: ComposerMenuOption;
|
|
tooltipsEnabled?: boolean;
|
|
}): React.JSX.Element {
|
|
if (!tooltipsEnabled || !option.tooltip) {
|
|
return children;
|
|
}
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
{cloneElement(children, {
|
|
"data-agent-model-option-tooltip-trigger": "true"
|
|
} as Partial<HTMLAttributes<HTMLElement>> &
|
|
Record<"data-agent-model-option-tooltip-trigger", string>)}
|
|
</TooltipTrigger>
|
|
<TooltipContent
|
|
side="right"
|
|
align="start"
|
|
sideOffset={8}
|
|
className="flex w-[320px] max-w-[calc(100vw-32px)] flex-col items-start gap-0 whitespace-normal rounded-lg border border-[var(--line-2)] bg-[var(--background-fronted)] px-4 py-3 text-[13px] leading-[1.3] text-[var(--text-primary)] shadow-lg"
|
|
data-agent-model-option-tooltip="true"
|
|
>
|
|
<span className="block text-[15px] font-semibold leading-[1.2]">
|
|
{option.tooltip.title}
|
|
</span>
|
|
{option.tooltip.description ? (
|
|
<span className="mt-1.5 block text-[13px] leading-[1.35] text-[var(--text-tertiary)]">
|
|
{option.tooltip.description}
|
|
</span>
|
|
) : null}
|
|
{option.tooltip.contextWindow ? (
|
|
<span className="mt-4 block">{option.tooltip.contextWindow}</span>
|
|
) : null}
|
|
{option.tooltip.version ? (
|
|
<span className="mt-4 block italic">{option.tooltip.version}</span>
|
|
) : null}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|