feat: add moonshotai provider and user role priority constants

This commit is contained in:
DIYgod 2025-11-15 12:10:03 +08:00
parent e48d40a7e7
commit 4e1c9bfdb4
No known key found for this signature in database
3 changed files with 73 additions and 4 deletions

View File

@ -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<ProviderType, string> = {
auto: "i-mgc-folo-bot-original size-4 -ml-0.5",
@ -25,6 +29,38 @@ const providerIcons: Record<ProviderType, string> = {
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<ModelPaidLevel, number> = {
basic: 1,
plus: 2,
pro: 3,
}
const paidLevelBadgeStyles: Record<ModelPaidLevel, string> = {
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<ModelPaidLevel, string> = {
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
>
<i className={cn("size-3", iconClass)} />
<span className="hidden max-w-20 truncate @md:inline">
{availableModelsMenu.find((item) => item.value === currentModel)?.label || modelName}
{selectedMenuItem?.label || modelName}
</span>
{hasMultipleModels && <i className="i-mingcute-down-line size-3 opacity-60" />}
</div>
@ -84,13 +126,19 @@ export const AIModelIndicator = memo(({ className, onModelChange }: AIModelIndic
<DropdownMenu>
<DropdownMenuTrigger asChild>{modelContent}</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-48">
{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 (
<DropdownMenuItem
key={value}
className="gap-2"
className={cn("gap-2", requiresUpgrade && "text-text-secondary")}
onClick={handleModelSelect}
checked={isSelected}
>
<i className={cn("size-3", itemIconClass)} />
<span className="truncate">{label || itemModelName}</span>
{normalizedPaidLevel && (
<span
className={cn(
"ml-auto inline-flex rounded-full border px-1.5 text-[9px] font-semibold uppercase tracking-wide",
paidLevelBadgeStyles[normalizedPaidLevel],
)}
>
{paidLevelLabels[normalizedPaidLevel]}
</span>
)}
</DropdownMenuItem>
)
} else {

View File

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="64" viewBox="0 0 24 24" width="64" xmlns="http://www.w3.org/2000/svg" style="flex: 0 0 auto; line-height: 1;"><title>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -33,6 +33,16 @@ export const UserRoleName: Record<UserRole, string> = {
[UserRole.Basic]: "Basic",
} as const
export const UserRolePriority: Record<UserRole, number> = {
[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