diff --git a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIModelIndicator.tsx b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIModelIndicator.tsx index 0b3212429..6306f37b7 100644 --- a/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIModelIndicator.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/AIModelIndicator.tsx @@ -1,3 +1,6 @@ +import type { UserRole } from "@follow/constants" +import { UserRolePriority } from "@follow/constants" +import { useUserRole } from "@follow/store/user/hooks" import { cn } from "@follow/utils" import { Fragment, memo, useMemo } from "react" @@ -9,6 +12,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu/dropdown-menu" +import { useSettingModal } from "~/modules/settings/modal/use-setting-modal-hack" import { useAIModel } from "../../hooks/useAIModel" @@ -17,7 +21,7 @@ interface AIModelIndicatorProps { onModelChange?: (model: string) => void } -type ProviderType = "openai" | "google" | "auto" | "deepseek" | "anthropic" +type ProviderType = "openai" | "google" | "auto" | "deepseek" | "anthropic" | "moonshotai" const providerIcons: Record = { auto: "i-mgc-folo-bot-original size-4 -ml-0.5", @@ -25,6 +29,38 @@ const providerIcons: Record = { google: "i-simple-icons-googlegemini", anthropic: "i-simple-icons-claude", deepseek: "i-mgc-deepseek-original", + moonshotai: "i-mgc-moonshotai-original", +} + +const MODEL_PAID_LEVELS = ["basic", "plus", "pro"] as const +type ModelPaidLevel = (typeof MODEL_PAID_LEVELS)[number] + +const paidLevelPriority: Record = { + basic: 1, + plus: 2, + pro: 3, +} + +const paidLevelBadgeStyles: Record = { + basic: "border-green/30 bg-green/10 text-green", + plus: "border-blue/30 bg-blue/10 text-blue", + pro: "border-purple/40 bg-purple/10 text-purple", +} + +const paidLevelLabels: Record = { + basic: "Basic", + plus: "Plus", + pro: "Pro", +} + +const isModelPaidLevel = (value: unknown): value is ModelPaidLevel => { + return typeof value === "string" && MODEL_PAID_LEVELS.includes(value as ModelPaidLevel) +} + +const hasAccessToPaidLevel = (role: UserRole | null | undefined, level?: ModelPaidLevel) => { + if (!level) return true + const roleScore = role ? (UserRolePriority[role] ?? 0) : 0 + return roleScore >= paidLevelPriority[level] } const parseModelString = (modelString: string) => { @@ -44,11 +80,17 @@ const parseModelString = (modelString: string) => { export const AIModelIndicator = memo(({ className, onModelChange }: AIModelIndicatorProps) => { const { data, changeModel } = useAIModel() const { defaultModel, availableModels = [], currentModel, availableModelsMenu = [] } = data || {} + const role = useUserRole() + const settingModalPresent = useSettingModal() const { provider, modelName } = useMemo(() => { return parseModelString(currentModel || defaultModel || "") }, [currentModel, defaultModel]) + const selectedMenuItem = useMemo(() => { + return availableModelsMenu.find((item) => item.value === currentModel) + }, [availableModelsMenu, currentModel]) + const iconClass = providerIcons[provider] || providerIcons.auto const hasMultipleModels = availableModels && availableModels.length > 1 @@ -70,7 +112,7 @@ export const AIModelIndicator = memo(({ className, onModelChange }: AIModelIndic > - {availableModelsMenu.find((item) => item.value === currentModel)?.label || modelName} + {selectedMenuItem?.label || modelName} {hasMultipleModels && } @@ -84,13 +126,19 @@ export const AIModelIndicator = memo(({ className, onModelChange }: AIModelIndic {modelContent} - {availableModelsMenu.map(({ label, value }, index) => { + {availableModelsMenu.map(({ label, value, paidLevel }, index) => { if (value) { const { provider: itemProvider, modelName: itemModelName } = parseModelString(value) const itemIconClass = providerIcons[itemProvider] || providerIcons.auto const isSelected = value === (currentModel || defaultModel) + const normalizedPaidLevel = isModelPaidLevel(paidLevel) ? paidLevel : undefined + const requiresUpgrade = !hasAccessToPaidLevel(role, normalizedPaidLevel) const handleModelSelect = () => { + if (requiresUpgrade) { + settingModalPresent("plan") + return + } changeModel(value) onModelChange?.(value) } @@ -98,12 +146,22 @@ export const AIModelIndicator = memo(({ className, onModelChange }: AIModelIndic return ( {label || itemModelName} + {normalizedPaidLevel && ( + + {paidLevelLabels[normalizedPaidLevel]} + + )} ) } else { diff --git a/icons/mgc/moonshotai_original.svg b/icons/mgc/moonshotai_original.svg new file mode 100644 index 000000000..833ec209f --- /dev/null +++ b/icons/mgc/moonshotai_original.svg @@ -0,0 +1 @@ +MoonshotAI diff --git a/packages/internal/constants/src/enums.ts b/packages/internal/constants/src/enums.ts index f8a49f11a..f6a9e685d 100644 --- a/packages/internal/constants/src/enums.ts +++ b/packages/internal/constants/src/enums.ts @@ -33,6 +33,16 @@ export const UserRoleName: Record = { [UserRole.Basic]: "Basic", } as const +export const UserRolePriority: Record = { + [UserRole.Admin]: 4, + [UserRole.Pro]: 3, + [UserRole.Plus]: 2, + [UserRole.Basic]: 1, + [UserRole.PreProTrial]: 0, + [UserRole.Free]: 0, + [UserRole.Trial]: 0, +} as const + export const isFreeRole = (role?: UserRole | null) => { return role ? role === UserRole.Free || role === UserRole.Trial || role === UserRole.PreProTrial