fix: show disabled action reasons via tooltips

This commit is contained in:
SiYue-ZO 2026-04-12 22:22:36 +08:00
parent e6f91acd4f
commit 7fe727fb68
2 changed files with 93 additions and 46 deletions

View file

@ -18,6 +18,11 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card" } from "@/components/ui/card"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
export function MarketSkillCard({ export function MarketSkillCard({
result, result,
@ -37,11 +42,15 @@ export function MarketSkillCard({
const { t } = useTranslation() const { t } = useTranslation()
const installDisabledReason = (() => { const installDisabledReason = (() => {
if (installPending) return t("pages.agent.skills.marketplace_installDisabled.installing") if (installPending)
if (result.installed) return t("pages.agent.skills.marketplace_installDisabled.installed") return t("pages.agent.skills.marketplace_installDisabled.installing")
if (!canInstall) return t("pages.agent.skills.marketplace_installDisabled.cannotInstall") if (result.installed)
return t("pages.agent.skills.marketplace_installDisabled.installed")
if (!canInstall)
return t("pages.agent.skills.marketplace_installDisabled.cannotInstall")
return t("pages.agent.skills.marketplace_install_action") return t("pages.agent.skills.marketplace_install_action")
})() })()
const installDisabled = !canInstall || result.installed || installPending
return ( return (
<Card <Card
@ -93,25 +102,34 @@ export function MarketSkillCard({
) : null} ) : null}
</div> </div>
<div className="flex shrink-0 flex-col items-end gap-2"> <div className="flex shrink-0 flex-col items-end gap-2">
<Button <Tooltip delayDuration={installDisabled ? 0 : 700}>
size="sm" <TooltipTrigger asChild>
variant={result.installed ? "secondary" : "default"} <span
className="shadow-sm transition-all" className={installDisabled ? "cursor-not-allowed" : undefined}
disabled={!canInstall || result.installed || installPending} tabIndex={installDisabled ? 0 : undefined}
onClick={onInstall} >
title={installDisabledReason} <Button
> size="sm"
{installPending ? ( variant={result.installed ? "secondary" : "default"}
<IconLoader2 className="size-4 animate-spin" /> className="shadow-sm transition-all"
) : result.installed ? ( disabled={installDisabled}
<IconCheck className="size-4" /> onClick={onInstall}
) : ( >
<IconPlus className="size-4" /> {installPending ? (
)} <IconLoader2 className="size-4 animate-spin" />
{result.installed ) : result.installed ? (
? t("pages.agent.skills.marketplace_installed") <IconCheck className="size-4" />
: t("pages.agent.skills.marketplace_install_action")} ) : (
</Button> <IconPlus className="size-4" />
)}
{result.installed
? t("pages.agent.skills.marketplace_installed")
: t("pages.agent.skills.marketplace_install_action")}
</Button>
</span>
</TooltipTrigger>
<TooltipContent>{installDisabledReason}</TooltipContent>
</Tooltip>
{result.installed && installedSkill ? ( {result.installed && installedSkill ? (
<Button <Button
variant="outline" variant="outline"

View file

@ -10,6 +10,11 @@ import { useTranslation } from "react-i18next"
import type { ModelInfo } from "@/api/models" import type { ModelInfo } from "@/api/models"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
interface ModelCardProps { interface ModelCardProps {
model: ModelInfo model: ModelInfo
@ -35,7 +40,8 @@ export function ModelCard({
const setDefaultDisabledReason = (() => { const setDefaultDisabledReason = (() => {
if (settingDefault) return t("models.action.setDefaultDisabled.setting") if (settingDefault) return t("models.action.setDefaultDisabled.setting")
if (!model.available) return t("models.action.setDefaultDisabled.unavailable") if (!model.available)
return t("models.action.setDefaultDisabled.unavailable")
if (model.is_default) return t("models.action.setDefaultDisabled.isDefault") if (model.is_default) return t("models.action.setDefaultDisabled.isDefault")
if (model.is_virtual) return t("models.action.setDefaultDisabled.isVirtual") if (model.is_virtual) return t("models.action.setDefaultDisabled.isVirtual")
return t("models.action.setDefault") return t("models.action.setDefault")
@ -44,6 +50,7 @@ export function ModelCard({
const deleteDisabledReason = model.is_default const deleteDisabledReason = model.is_default
? t("models.action.deleteDisabled.isDefault") ? t("models.action.deleteDisabled.isDefault")
: t("models.action.delete") : t("models.action.delete")
const deleteDisabled = model.is_default
return ( return (
<div <div
@ -93,19 +100,32 @@ export function ModelCard({
<IconStarFilled className="size-3.5" /> <IconStarFilled className="size-3.5" />
</span> </span>
) : ( ) : (
<Button <Tooltip delayDuration={!canSetDefault || settingDefault ? 0 : 700}>
variant="ghost" <TooltipTrigger asChild>
size="icon-sm" <span
onClick={() => onSetDefault(model)} className={
disabled={settingDefault || !canSetDefault} !canSetDefault || settingDefault
title={setDefaultDisabledReason} ? "cursor-not-allowed"
> : undefined
{settingDefault ? ( }
<IconLoader2 className="size-3.5 animate-spin" /> tabIndex={!canSetDefault || settingDefault ? 0 : undefined}
) : ( >
<IconStar className="size-3.5" /> <Button
)} variant="ghost"
</Button> size="icon-sm"
onClick={() => onSetDefault(model)}
disabled={settingDefault || !canSetDefault}
>
{settingDefault ? (
<IconLoader2 className="size-3.5 animate-spin" />
) : (
<IconStar className="size-3.5" />
)}
</Button>
</span>
</TooltipTrigger>
<TooltipContent>{setDefaultDisabledReason}</TooltipContent>
</Tooltip>
)} )}
<Button <Button
@ -117,16 +137,25 @@ export function ModelCard({
<IconEdit className="size-3.5" /> <IconEdit className="size-3.5" />
</Button> </Button>
<Button <Tooltip delayDuration={deleteDisabled ? 0 : 700}>
variant="ghost" <TooltipTrigger asChild>
size="icon-sm" <span
onClick={() => onDelete(model)} className={deleteDisabled ? "cursor-not-allowed" : undefined}
disabled={model.is_default} tabIndex={deleteDisabled ? 0 : undefined}
title={deleteDisabledReason} >
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10" <Button
> variant="ghost"
<IconTrash className="size-3.5" /> size="icon-sm"
</Button> onClick={() => onDelete(model)}
disabled={deleteDisabled}
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
>
<IconTrash className="size-3.5" />
</Button>
</span>
</TooltipTrigger>
<TooltipContent>{deleteDisabledReason}</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>