mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
✨ feat: add InsufficientBudget error type and Pro badge i18n (#12886)
This commit is contained in:
@@ -355,6 +355,7 @@
|
||||
"pin": "Pin",
|
||||
"pinOff": "Unpin",
|
||||
"privacy": "Privacy Policy",
|
||||
"pro": "Pro",
|
||||
"productHunt.actionLabel": "Support us",
|
||||
"productHunt.description": "Support us on Product Hunt. Your support means a lot to us!",
|
||||
"productHunt.title": "We're on Product Hunt!",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"response.GoogleAIBlockReason.SAFETY": "Your content was blocked for safety policy reasons. Please adjust your request to avoid potentially harmful or inappropriate content.",
|
||||
"response.GoogleAIBlockReason.SPII": "Your content may contain sensitive personally identifiable information (PII). To protect privacy, please remove any sensitive details and try again.",
|
||||
"response.GoogleAIBlockReason.default": "Content blocked: {{blockReason}}. Please adjust your request and try again.",
|
||||
"response.InsufficientBudgetForModel": "Your remaining credits are insufficient for this model. Please top up credits, upgrade your plan, or try a less expensive model.",
|
||||
"response.InsufficientQuota": "Sorry, the quota for this key has been reached. Please check if your account balance is sufficient or try again after increasing the key's quota.",
|
||||
"response.InvalidAccessCode": "Invalid access code or empty. Please enter the correct access code or add a custom API Key.",
|
||||
"response.InvalidBedrockCredentials": "Bedrock authentication failed. Please check the AccessKeyId/SecretAccessKey and retry.",
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
"limitation.image.topupSuccess.action": "Continue Generating",
|
||||
"limitation.image.topupSuccess.desc": "Your top-up credits are now active. Enjoy AI image generation. Your current plan includes:",
|
||||
"limitation.image.topupSuccess.title": "Top-up Successful",
|
||||
"limitation.insufficientBudget.desc": "Your remaining credits are not enough for the estimated cost of this model. Please top up credits or switch to a less expensive model.",
|
||||
"limitation.insufficientBudget.retry": "Retry",
|
||||
"limitation.insufficientBudget.title": "Insufficient Credits for This Model",
|
||||
"limitation.limited.action": "Upgrade Now",
|
||||
"limitation.limited.advanceFeature": "Upgrade to enjoy premium features:",
|
||||
"limitation.limited.desc": "Your {{plan}} computing credits have been exhausted. Upgrade now to get more credits.",
|
||||
|
||||
@@ -355,6 +355,7 @@
|
||||
"pin": "置顶",
|
||||
"pinOff": "取消置顶",
|
||||
"privacy": "隐私政策",
|
||||
"pro": "Pro",
|
||||
"productHunt.actionLabel": "支持我们",
|
||||
"productHunt.description": "在 Product Hunt 上支持我们,您的支持对我们意义重大!",
|
||||
"productHunt.title": "我们登上 Product Hunt 了!",
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"response.GoogleAIBlockReason.SAFETY": "内容触发安全策略。请移除可能有害或不当的部分后重试",
|
||||
"response.GoogleAIBlockReason.SPII": "内容可能包含敏感个人身份信息。为保护隐私,请移除后重试",
|
||||
"response.GoogleAIBlockReason.default": "内容被阻止:{{blockReason}}。请调整后重试",
|
||||
"response.InsufficientBudgetForModel": "剩余额度不足以使用此模型。请充值额度、升级订阅计划,或尝试使用费用更低的模型。",
|
||||
"response.InsufficientQuota": "配额已用尽。请检查余额/配额设置,或更换可用的 API Key 后重试",
|
||||
"response.InvalidAccessCode": "访问密码为空或不正确。请重新输入,或改用自定义 API Key",
|
||||
"response.InvalidBedrockCredentials": "Bedrock 鉴权失败。请检查 AccessKeyId/SecretAccessKey 后重试",
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
"limitation.image.topupSuccess.action": "继续生成",
|
||||
"limitation.image.topupSuccess.desc": "您的充值积分已激活,畅享 AI 图像生成。当前套餐包含:",
|
||||
"limitation.image.topupSuccess.title": "充值成功",
|
||||
"limitation.insufficientBudget.desc": "剩余额度不足以支付此模型的预估费用。请充值积分或切换到费用更低的模型。",
|
||||
"limitation.insufficientBudget.retry": "重试",
|
||||
"limitation.insufficientBudget.title": "额度不足以使用此模型",
|
||||
"limitation.limited.action": "立即升级",
|
||||
"limitation.limited.advanceFeature": "升级以解锁高级功能:",
|
||||
"limitation.limited.desc": "您的 {{plan}} 计算积分已用尽。请立即升级以获取更多积分。",
|
||||
|
||||
10
package.json
10
package.json
@@ -152,6 +152,11 @@
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"@types/react": "19.2.13",
|
||||
"better-auth": "1.4.6",
|
||||
"better-call": "1.1.8",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"fast-xml-parser": "5.4.2",
|
||||
"pdfjs-dist": "5.4.530",
|
||||
"stylelint-config-clean-order": "7.0.0"
|
||||
},
|
||||
@@ -510,7 +515,10 @@
|
||||
"@types/react": "19.2.13",
|
||||
"better-auth": "1.4.6",
|
||||
"better-call": "1.1.8",
|
||||
"drizzle-orm": "^0.45.1"
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"fast-xml-parser": "5.4.2",
|
||||
"pdfjs-dist": "5.4.530",
|
||||
"stylelint-config-clean-order": "7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ const aihubmixModels: AIChatModelCard[] = [
|
||||
},
|
||||
contextWindowTokens: 1_050_000,
|
||||
description:
|
||||
'GPT-5.4 pro uses more compute to think harder and provide consistently better answers, available in the Responses API only.',
|
||||
displayName: 'GPT-5.4 pro',
|
||||
'GPT-5.4 Pro uses more compute to think harder and provide consistently better answers, available in the Responses API only.',
|
||||
displayName: 'GPT-5.4 Pro',
|
||||
id: 'gpt-5.4-pro',
|
||||
maxOutput: 128_000,
|
||||
pricing: {
|
||||
|
||||
@@ -89,8 +89,8 @@ export const openaiChatModels: AIChatModelCard[] = [
|
||||
},
|
||||
contextWindowTokens: 1_050_000,
|
||||
description:
|
||||
'GPT-5.4 pro uses more compute to think harder and provide consistently better answers, available in the Responses API only.',
|
||||
displayName: 'GPT-5.4 pro',
|
||||
'GPT-5.4 Pro uses more compute to think harder and provide consistently better answers, available in the Responses API only.',
|
||||
displayName: 'GPT-5.4 Pro',
|
||||
id: 'gpt-5.4-pro',
|
||||
maxOutput: 128_000,
|
||||
pricing: {
|
||||
|
||||
@@ -6,6 +6,7 @@ export const ChatErrorType = {
|
||||
InvalidAccessCode: 'InvalidAccessCode', // is in valid password
|
||||
FreePlanLimit: 'FreePlanLimit', // Free plan usage limit
|
||||
SubscriptionPlanLimit: 'SubscriptionPlanLimit', // Subscription user limit exceeded
|
||||
InsufficientBudgetForModel: 'InsufficientBudgetForModel', // Has credits but not enough for estimated model cost
|
||||
SubscriptionKeyMismatch: 'SubscriptionKeyMismatch', // Subscription key mismatch
|
||||
|
||||
SupervisorDecisionFailed: 'SupervisorDecisionFailed', // Supervisor decision failed
|
||||
|
||||
8
src/business/client/hooks/useBusinessModelListGuard.ts
Normal file
8
src/business/client/hooks/useBusinessModelListGuard.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface BusinessModelListGuard {
|
||||
isModelRestricted?: (modelId: string, providerId: string) => boolean;
|
||||
onRestrictedModelClick?: () => void;
|
||||
}
|
||||
|
||||
export const useBusinessModelListGuard = (): BusinessModelListGuard => {
|
||||
return {};
|
||||
};
|
||||
@@ -237,6 +237,7 @@ export const ModelInfoTags = memo<ModelInfoTagsProps>(
|
||||
interface ModelItemRenderProps extends ChatModelCard, Partial<Omit<FlexboxProps, 'id' | 'title'>> {
|
||||
abilities?: ModelAbilities;
|
||||
newBadgeLabel?: string;
|
||||
proBadgeLabel?: string;
|
||||
showInfoTag?: boolean;
|
||||
}
|
||||
|
||||
@@ -249,6 +250,7 @@ export const ModelItemRender = memo<ModelItemRenderProps>(
|
||||
functionCall,
|
||||
imageOutput,
|
||||
newBadgeLabel,
|
||||
proBadgeLabel,
|
||||
video,
|
||||
vision,
|
||||
id,
|
||||
@@ -294,6 +296,11 @@ export const ModelItemRender = memo<ModelItemRenderProps>(
|
||||
) : (
|
||||
<NewModelBadgeI18n releasedAt={releasedAt} />
|
||||
)}
|
||||
{proBadgeLabel && (
|
||||
<Tag color="gold" size="small">
|
||||
{proBadgeLabel}
|
||||
</Tag>
|
||||
)}
|
||||
</Flexbox>
|
||||
{showInfoTag && (
|
||||
<ModelInfoTags
|
||||
|
||||
@@ -28,14 +28,26 @@ import { SingleProviderModelItem } from './SingleProviderModelItem';
|
||||
|
||||
interface ListItemRendererProps {
|
||||
activeKey: string;
|
||||
isModelRestricted?: (modelId: string, providerId: string) => boolean;
|
||||
item: ListItem;
|
||||
newLabel: string;
|
||||
onClose: () => void;
|
||||
onModelChange: (modelId: string, providerId: string) => Promise<void>;
|
||||
onRestrictedModelClick?: () => void;
|
||||
proLabel?: string;
|
||||
}
|
||||
|
||||
export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
({ activeKey, item, newLabel, onModelChange, onClose }) => {
|
||||
({
|
||||
activeKey,
|
||||
isModelRestricted,
|
||||
item,
|
||||
newLabel,
|
||||
onModelChange,
|
||||
onClose,
|
||||
onRestrictedModelClick,
|
||||
proLabel,
|
||||
}) => {
|
||||
const { t } = useTranslation('components');
|
||||
const navigate = useNavigate();
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
@@ -114,6 +126,7 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
case 'provider-model-item': {
|
||||
const key = menuKey(item.provider.id, item.model.id);
|
||||
const isActive = key === activeKey;
|
||||
const restricted = isModelRestricted?.(item.model.id, item.provider.id);
|
||||
|
||||
return (
|
||||
<Flexbox style={{ marginBlock: 1, marginInline: 4 }}>
|
||||
@@ -122,6 +135,11 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
className={cx(menuSharedStyles.item, isActive && styles.menuItemActive)}
|
||||
style={{ paddingBlock: 8, paddingInline: 8 }}
|
||||
onClick={async () => {
|
||||
if (restricted) {
|
||||
onRestrictedModelClick?.();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
setDetailOpen(false);
|
||||
onModelChange(item.model.id, item.provider.id);
|
||||
onClose();
|
||||
@@ -132,6 +150,7 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
{...item.model.abilities}
|
||||
showInfoTag
|
||||
newBadgeLabel={newLabel}
|
||||
proBadgeLabel={restricted ? proLabel : undefined}
|
||||
/>
|
||||
</DropdownMenuSubmenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
@@ -150,6 +169,7 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
const singleProvider = item.data.providers[0];
|
||||
const key = menuKey(singleProvider.id, item.data.model.id);
|
||||
const isActive = key === activeKey;
|
||||
const restricted = isModelRestricted?.(item.data.model.id, singleProvider.id);
|
||||
|
||||
return (
|
||||
<Flexbox style={{ marginBlock: 1, marginInline: 4 }}>
|
||||
@@ -158,12 +178,21 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
className={cx(menuSharedStyles.item, isActive && styles.menuItemActive)}
|
||||
style={{ paddingBlock: 8, paddingInline: 8 }}
|
||||
onClick={async () => {
|
||||
if (restricted) {
|
||||
onRestrictedModelClick?.();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
setDetailOpen(false);
|
||||
onModelChange(item.data.model.id, singleProvider.id);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<SingleProviderModelItem data={item.data} newLabel={newLabel} />
|
||||
<SingleProviderModelItem
|
||||
data={item.data}
|
||||
newLabel={newLabel}
|
||||
proBadgeLabel={restricted ? proLabel : undefined}
|
||||
/>
|
||||
</DropdownMenuSubmenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuPositioner anchor={null} placement="right" sideOffset={16}>
|
||||
@@ -183,9 +212,12 @@ export const ListItemRenderer = memo<ListItemRendererProps>(
|
||||
<MultipleProvidersModelItem
|
||||
activeKey={activeKey}
|
||||
data={item.data}
|
||||
isModelRestricted={isModelRestricted}
|
||||
newLabel={newLabel}
|
||||
proLabel={proLabel}
|
||||
onClose={onClose}
|
||||
onModelChange={onModelChange}
|
||||
onRestrictedModelClick={onRestrictedModelClick}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
DropdownMenuPositioner,
|
||||
DropdownMenuSubmenuRoot,
|
||||
DropdownMenuSubmenuTrigger,
|
||||
Flexbox,
|
||||
menuSharedStyles,
|
||||
Tag,
|
||||
} from '@lobehub/ui';
|
||||
import { cx } from 'antd-style';
|
||||
import { Check, LucideBolt } from 'lucide-react';
|
||||
@@ -30,13 +32,25 @@ import ModelDetailPanel from '../ModelDetailPanel';
|
||||
interface MultipleProvidersModelItemProps {
|
||||
activeKey: string;
|
||||
data: ModelWithProviders;
|
||||
isModelRestricted?: (modelId: string, providerId: string) => boolean;
|
||||
newLabel: string;
|
||||
onClose: () => void;
|
||||
onModelChange: (modelId: string, providerId: string) => Promise<void>;
|
||||
onRestrictedModelClick?: () => void;
|
||||
proLabel?: string;
|
||||
}
|
||||
|
||||
export const MultipleProvidersModelItem = memo<MultipleProvidersModelItemProps>(
|
||||
({ activeKey, data, newLabel, onModelChange, onClose }) => {
|
||||
({
|
||||
activeKey,
|
||||
data,
|
||||
isModelRestricted,
|
||||
newLabel,
|
||||
onModelChange,
|
||||
onClose,
|
||||
onRestrictedModelClick,
|
||||
proLabel,
|
||||
}) => {
|
||||
const { t } = useTranslation('components');
|
||||
const navigate = useNavigate();
|
||||
const [submenuOpen, setSubmenuOpen] = useState(false);
|
||||
@@ -44,16 +58,34 @@ export const MultipleProvidersModelItem = memo<MultipleProvidersModelItemProps>(
|
||||
const activeProvider = data.providers.find((p) => menuKey(p.id, data.model.id) === activeKey);
|
||||
const isActive = !!activeProvider;
|
||||
|
||||
const allRestricted =
|
||||
isModelRestricted &&
|
||||
data.providers.length > 0 &&
|
||||
data.providers.every((p) => isModelRestricted(data.model.id, p.id));
|
||||
|
||||
return (
|
||||
<DropdownMenuSubmenuRoot open={submenuOpen} onOpenChange={setSubmenuOpen}>
|
||||
<DropdownMenuSubmenuRoot
|
||||
open={submenuOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (allRestricted && open) return;
|
||||
setSubmenuOpen(open);
|
||||
}}
|
||||
>
|
||||
<DropdownMenuSubmenuTrigger
|
||||
className={cx(menuSharedStyles.item, isActive && styles.menuItemActive)}
|
||||
style={{ paddingBlock: 8, paddingInline: 8 }}
|
||||
onClick={() => {
|
||||
if (allRestricted) {
|
||||
onRestrictedModelClick?.();
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModelItemRender
|
||||
{...data.model}
|
||||
{...data.model.abilities}
|
||||
newBadgeLabel={newLabel}
|
||||
proBadgeLabel={allRestricted ? proLabel : undefined}
|
||||
showInfoTag={true}
|
||||
/>
|
||||
</DropdownMenuSubmenuTrigger>
|
||||
@@ -71,11 +103,17 @@ export const MultipleProvidersModelItem = memo<MultipleProvidersModelItemProps>(
|
||||
{data.providers.map((p) => {
|
||||
const key = menuKey(p.id, data.model.id);
|
||||
const isProviderActive = activeKey === key;
|
||||
const providerRestricted = isModelRestricted?.(data.model.id, p.id);
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
onClick={async () => {
|
||||
if (providerRestricted) {
|
||||
onRestrictedModelClick?.();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
await onModelChange(data.model.id, p.id);
|
||||
onClose();
|
||||
}}
|
||||
@@ -84,14 +122,23 @@ export const MultipleProvidersModelItem = memo<MultipleProvidersModelItemProps>(
|
||||
{isProviderActive ? <Check size={16} /> : null}
|
||||
</DropdownMenuItemIcon>
|
||||
<DropdownMenuItemLabel>
|
||||
<ProviderItemRender
|
||||
logo={p.logo}
|
||||
name={p.name}
|
||||
provider={p.id}
|
||||
size={20}
|
||||
source={p.source}
|
||||
type={'avatar'}
|
||||
/>
|
||||
<Flexbox horizontal align="center" gap={8}>
|
||||
<Flexbox horizontal align="center" style={{ flex: 'none' }}>
|
||||
<ProviderItemRender
|
||||
logo={p.logo}
|
||||
name={p.name}
|
||||
provider={p.id}
|
||||
size={20}
|
||||
source={p.source}
|
||||
type={'avatar'}
|
||||
/>
|
||||
</Flexbox>
|
||||
{providerRestricted && proLabel && (
|
||||
<Tag color="gold" size="small">
|
||||
{proLabel}
|
||||
</Tag>
|
||||
)}
|
||||
</Flexbox>
|
||||
</DropdownMenuItemLabel>
|
||||
<DropdownMenuItemExtra>
|
||||
<ActionIcon
|
||||
|
||||
@@ -7,17 +7,21 @@ import { type ModelWithProviders } from '../../types';
|
||||
interface SingleProviderModelItemProps {
|
||||
data: ModelWithProviders;
|
||||
newLabel: string;
|
||||
proBadgeLabel?: string;
|
||||
}
|
||||
|
||||
export const SingleProviderModelItem = memo<SingleProviderModelItemProps>(({ data, newLabel }) => {
|
||||
return (
|
||||
<ModelItemRender
|
||||
{...data.model}
|
||||
{...data.model.abilities}
|
||||
newBadgeLabel={newLabel}
|
||||
showInfoTag={true}
|
||||
/>
|
||||
);
|
||||
});
|
||||
export const SingleProviderModelItem = memo<SingleProviderModelItemProps>(
|
||||
({ data, newLabel, proBadgeLabel }) => {
|
||||
return (
|
||||
<ModelItemRender
|
||||
{...data.model}
|
||||
{...data.model.abilities}
|
||||
newBadgeLabel={newLabel}
|
||||
proBadgeLabel={proBadgeLabel}
|
||||
showInfoTag={true}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SingleProviderModelItem.displayName = 'SingleProviderModelItem';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type FC } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useBusinessModelListGuard } from '@/business/client/hooks/useBusinessModelListGuard';
|
||||
import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
|
||||
|
||||
import { FOOTER_HEIGHT, ITEM_HEIGHT, MAX_PANEL_HEIGHT, TOOLBAR_HEIGHT } from '../../const';
|
||||
@@ -33,6 +34,8 @@ export const List: FC<ListProps> = ({
|
||||
}) => {
|
||||
const { t: tCommon } = useTranslation('common');
|
||||
const newLabel = tCommon('new');
|
||||
const { isModelRestricted, onRestrictedModelClick } = useBusinessModelListGuard();
|
||||
const proLabel = isModelRestricted ? tCommon('pro') : undefined;
|
||||
|
||||
const enabledList = useEnabledChatModels();
|
||||
const { model, provider } = useModelAndProvider(modelProp, providerProp);
|
||||
@@ -98,11 +101,14 @@ export const List: FC<ListProps> = ({
|
||||
const renderItem = (key?: string) => (
|
||||
<ListItemRenderer
|
||||
activeKey={activeKey}
|
||||
isModelRestricted={isModelRestricted}
|
||||
item={item}
|
||||
key={key}
|
||||
newLabel={newLabel}
|
||||
proLabel={proLabel}
|
||||
onClose={handleClose}
|
||||
onModelChange={handleModelChange}
|
||||
onRestrictedModelClick={onRestrictedModelClick}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -419,6 +419,7 @@ export default {
|
||||
'navPanel.searchAgent': 'Search Agent...',
|
||||
'navPanel.searchResultEmpty': 'No search results found',
|
||||
'new': 'New',
|
||||
'pro': 'Pro',
|
||||
'noContent': 'No content',
|
||||
'oauth': 'SSO Login',
|
||||
'officialSite': 'Official Website',
|
||||
|
||||
@@ -112,6 +112,8 @@ export default {
|
||||
'The model service is currently under heavy load. Please try again later.',
|
||||
'response.FreePlanLimit':
|
||||
'You are currently a free user and cannot use this feature. Please upgrade to a paid plan to continue using it.',
|
||||
'response.InsufficientBudgetForModel':
|
||||
'Your remaining credits are insufficient for this model. Please top up credits, upgrade your plan, or try a less expensive model.',
|
||||
'response.GoogleAIBlockReason.BLOCKLIST':
|
||||
'Your content contains prohibited terms. Please review and modify your input, then try again.',
|
||||
'response.GoogleAIBlockReason.IMAGE_SAFETY':
|
||||
|
||||
@@ -113,6 +113,10 @@ export default {
|
||||
'limitation.expired.desc':
|
||||
'Your {{plan}} computing credits expired on {{expiredAt}}. Upgrade your plan now to get computing credits.',
|
||||
'limitation.expired.title': 'Computing Credits Expired',
|
||||
'limitation.insufficientBudget.desc':
|
||||
'Your remaining credits are not enough for the estimated cost of this model. Please top up credits or switch to a less expensive model.',
|
||||
'limitation.insufficientBudget.retry': 'Retry',
|
||||
'limitation.insufficientBudget.title': 'Insufficient Credits for This Model',
|
||||
'limitation.hobby.action': 'Configured, continue chatting',
|
||||
'limitation.hobby.configAPI': 'Configure API',
|
||||
'limitation.hobby.desc':
|
||||
|
||||
@@ -17,7 +17,8 @@ const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
|
||||
|
||||
switch (errorType) {
|
||||
case ChatErrorType.SubscriptionPlanLimit:
|
||||
case ChatErrorType.FreePlanLimit: {
|
||||
case ChatErrorType.FreePlanLimit:
|
||||
case ChatErrorType.InsufficientBudgetForModel: {
|
||||
return 403;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user