mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-22 11:15:47 +00:00
fix: handle cloud trial limit in chat input state
This commit is contained in:
parent
cb2d229808
commit
79938ff08e
13 changed files with 122 additions and 10 deletions
|
|
@ -46,6 +46,7 @@ const getChatStoreTotalTokens = (chatStore: VanillaChatStore): number => {
|
|||
|
||||
const USAGE_WARNING_RATIO = 0.75;
|
||||
const FREE_STARTING_CREDITS = 500;
|
||||
const API_CODE_TRIAL_LIMIT = '22';
|
||||
|
||||
interface SubscriptionLimitInfo {
|
||||
plan_key?: string | null;
|
||||
|
|
@ -72,6 +73,11 @@ const toFiniteNumber = (value: unknown): number | null =>
|
|||
const usagePercent = (used: number, limit: number) =>
|
||||
Math.min(100, Math.max(0, Math.round((used / limit) * 100)));
|
||||
|
||||
const hasApiCode = (value: unknown, code: string) =>
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
String((value as { code?: unknown }).code) === code;
|
||||
|
||||
const buildUsageLimitBannerState = (
|
||||
subscription: SubscriptionLimitInfo | null,
|
||||
currentCredits: number | null,
|
||||
|
|
@ -200,6 +206,7 @@ export default function ChatBox(): JSX.Element {
|
|||
const [subscriptionUsage, setSubscriptionUsage] =
|
||||
useState<SubscriptionLimitInfo | null>(null);
|
||||
const [currentCredits, setCurrentCredits] = useState<number | null>(null);
|
||||
const [cloudUsageLimitReached, setCloudUsageLimitReached] = useState(false);
|
||||
const [dismissedUsageLimitBannerId, setDismissedUsageLimitBannerId] =
|
||||
useState<string | null>(null);
|
||||
|
||||
|
|
@ -234,24 +241,45 @@ export default function ChatBox(): JSX.Element {
|
|||
[subscriptionUsage, currentCredits, t]
|
||||
);
|
||||
|
||||
const cloudUsageLimitMessage = useMemo(() => {
|
||||
if (modelType !== 'cloud' || !cloudUsageLimitReached) return null;
|
||||
return [
|
||||
usageLimitBannerState?.message ||
|
||||
t('chat.usage-limit-trial-daily-exhausted'),
|
||||
t('chat.usage-limit-switch-model-hint'),
|
||||
].join(' ');
|
||||
}, [modelType, cloudUsageLimitReached, usageLimitBannerState, t]);
|
||||
|
||||
const effectiveUsageLimitBannerState = useMemo(() => {
|
||||
if (!cloudUsageLimitMessage) return usageLimitBannerState;
|
||||
|
||||
return {
|
||||
id: 'cloud-usage-limit-blocked',
|
||||
message: cloudUsageLimitMessage,
|
||||
actionLabel:
|
||||
usageLimitBannerState?.actionLabel || t('chat.usage-limit-action'),
|
||||
severity: 'danger' as const,
|
||||
};
|
||||
}, [cloudUsageLimitMessage, usageLimitBannerState, t]);
|
||||
|
||||
const usageLimitBanner = useMemo(() => {
|
||||
if (
|
||||
!usageLimitBannerState ||
|
||||
usageLimitBannerState.id === dismissedUsageLimitBannerId
|
||||
!effectiveUsageLimitBannerState ||
|
||||
effectiveUsageLimitBannerState.id === dismissedUsageLimitBannerId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...usageLimitBannerState,
|
||||
...effectiveUsageLimitBannerState,
|
||||
onAction: () => {
|
||||
window.location.href = `${SITE_URL}/pricing`;
|
||||
},
|
||||
onDismiss: () => {
|
||||
setDismissedUsageLimitBannerId(usageLimitBannerState.id);
|
||||
setDismissedUsageLimitBannerId(effectiveUsageLimitBannerState.id);
|
||||
},
|
||||
};
|
||||
}, [usageLimitBannerState, dismissedUsageLimitBannerId]);
|
||||
}, [effectiveUsageLimitBannerState, dismissedUsageLimitBannerId]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshUsageLimits();
|
||||
|
|
@ -297,22 +325,41 @@ export default function ChatBox(): JSX.Element {
|
|||
if (modelType === 'cloud') {
|
||||
// For cloud model, check if API key exists
|
||||
const res = await proxyFetchGet('/api/v1/user/key');
|
||||
if (hasApiCode(res, API_CODE_TRIAL_LIMIT)) {
|
||||
setCloudUsageLimitReached(true);
|
||||
setHasModel(false);
|
||||
refreshUsageLimits();
|
||||
return;
|
||||
}
|
||||
setCloudUsageLimitReached(false);
|
||||
setHasModel(!!res.value);
|
||||
} else if (modelType === 'local' || modelType === 'custom') {
|
||||
setCloudUsageLimitReached(false);
|
||||
// For local/custom model, check if provider exists
|
||||
const res = await proxyFetchGet('/api/v1/providers', { prefer: true });
|
||||
const providerList = res.items || [];
|
||||
setHasModel(providerList.length > 0);
|
||||
} else {
|
||||
setCloudUsageLimitReached(false);
|
||||
setHasModel(false);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('Failed to check model config:', err);
|
||||
if (
|
||||
modelType === 'cloud' &&
|
||||
hasApiCode(err?.response?.data, API_CODE_TRIAL_LIMIT)
|
||||
) {
|
||||
setCloudUsageLimitReached(true);
|
||||
setHasModel(false);
|
||||
refreshUsageLimits();
|
||||
return;
|
||||
}
|
||||
setCloudUsageLimitReached(false);
|
||||
setHasModel(false);
|
||||
} finally {
|
||||
setIsConfigLoaded(true);
|
||||
}
|
||||
}, [modelType]);
|
||||
}, [modelType, refreshUsageLimits]);
|
||||
|
||||
// Check model config on mount and when modelType changes
|
||||
useEffect(() => {
|
||||
|
|
@ -501,6 +548,8 @@ export default function ChatBox(): JSX.Element {
|
|||
);
|
||||
}, [chatStore?.activeTaskId, chatStore?.tasks]);
|
||||
|
||||
const isCloudUsageLimited = modelType === 'cloud' && cloudUsageLimitReached;
|
||||
|
||||
const isInputDisabled = useMemo(() => {
|
||||
if (!chatStore?.activeTaskId || !chatStore.tasks[chatStore.activeTaskId])
|
||||
return true;
|
||||
|
|
@ -513,6 +562,7 @@ export default function ChatBox(): JSX.Element {
|
|||
if (isTaskBusy) return true;
|
||||
|
||||
// Standard checks - check model
|
||||
if (isCloudUsageLimited) return true;
|
||||
if (!hasModel) return true;
|
||||
if (useCloudModelInDev) return true;
|
||||
if (task.isContextExceeded) return true;
|
||||
|
|
@ -521,6 +571,7 @@ export default function ChatBox(): JSX.Element {
|
|||
}, [
|
||||
chatStore?.activeTaskId,
|
||||
chatStore?.tasks,
|
||||
isCloudUsageLimited,
|
||||
hasModel,
|
||||
useCloudModelInDev,
|
||||
isTaskBusy,
|
||||
|
|
@ -537,6 +588,13 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
// Check model configuration before starting task
|
||||
if (!hasModel) {
|
||||
if (isCloudUsageLimited) {
|
||||
toast.error(
|
||||
cloudUsageLimitMessage ||
|
||||
t('chat.usage-limit-trial-daily-exhausted')
|
||||
);
|
||||
return;
|
||||
}
|
||||
toast.error('Please select a model first.');
|
||||
navigate('/history?tab=agents');
|
||||
return;
|
||||
|
|
@ -570,7 +628,15 @@ export default function ChatBox(): JSX.Element {
|
|||
}
|
||||
}
|
||||
},
|
||||
[chatStore, projectStore.activeProjectId, hasModel, navigate]
|
||||
[
|
||||
chatStore,
|
||||
projectStore.activeProjectId,
|
||||
hasModel,
|
||||
isCloudUsageLimited,
|
||||
cloudUsageLimitMessage,
|
||||
navigate,
|
||||
t,
|
||||
]
|
||||
);
|
||||
|
||||
// Handle skill_prompt from URL - pre-fill message when navigating from Skills page
|
||||
|
|
@ -640,6 +706,12 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
// Check model configuration
|
||||
if (!hasModel) {
|
||||
if (isCloudUsageLimited) {
|
||||
toast.error(
|
||||
cloudUsageLimitMessage || t('chat.usage-limit-trial-daily-exhausted')
|
||||
);
|
||||
return;
|
||||
}
|
||||
toast.error('Please select a model first.');
|
||||
navigate('/history?tab=agents');
|
||||
return;
|
||||
|
|
@ -1322,7 +1394,17 @@ export default function ChatBox(): JSX.Element {
|
|||
|
||||
{/* Suggestion Area - Bottom area, flex-1 to push content up */}
|
||||
<div className="mt-3 flex h-[210px] flex-1 items-start justify-center gap-2">
|
||||
{!hasModel ? (
|
||||
{isCloudUsageLimited ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 rounded-md bg-surface-warning px-sm py-xs">
|
||||
<TriangleAlert size={20} className="text-icon-warning" />
|
||||
<span className="flex-1 text-xs font-medium leading-[20px] text-text-warning">
|
||||
{cloudUsageLimitMessage ||
|
||||
t('chat.usage-limit-trial-daily-exhausted')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : !hasModel ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
onClick={() => {
|
||||
|
|
@ -1337,7 +1419,7 @@ export default function ChatBox(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{hasModel && (
|
||||
{hasModel && !isCloudUsageLimited && (
|
||||
<div className="mr-2 flex flex-col items-center gap-2">
|
||||
{[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "إزالة الملف",
|
||||
"drop-files-to-attach": "أسقط الملفات للإرفاق",
|
||||
"usage-limit-action": "احصل على استخدام أكثر",
|
||||
"usage-limit-switch-model-hint": "بدّل إلى نموذج محلي/مخصص أو استخدم مفتاح API آخر للمتابعة.",
|
||||
"usage-limit-trial-daily-warning": "لقد استخدمت {{percent}}% من حد التجربة المجانية لهذا اليوم",
|
||||
"usage-limit-trial-daily-exhausted": "لقد وصلت إلى حد التجربة المجانية لهذا اليوم",
|
||||
"usage-limit-trial-total-warning": "لقد استخدمت {{percent}}% من استخدام التجربة المجانية",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Datei entfernen",
|
||||
"drop-files-to-attach": "Dateien zum Anhängen ablegen",
|
||||
"usage-limit-action": "Mehr Nutzung erhalten",
|
||||
"usage-limit-switch-model-hint": "Wechseln Sie zu einem lokalen/benutzerdefinierten Modell oder verwenden Sie einen anderen API-Schlüssel, um fortzufahren.",
|
||||
"usage-limit-trial-daily-warning": "Sie haben {{percent}}% des heutigen Testlimits genutzt",
|
||||
"usage-limit-trial-daily-exhausted": "Sie haben das heutige Testlimit erreicht",
|
||||
"usage-limit-trial-total-warning": "Sie haben {{percent}}% Ihres Testkontingents genutzt",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Remove file",
|
||||
"drop-files-to-attach": "Drop files to attach",
|
||||
"usage-limit-action": "Get more usage",
|
||||
"usage-limit-switch-model-hint": "Switch to a local/custom model or use another API key to continue.",
|
||||
"usage-limit-trial-daily-warning": "You've used {{percent}}% of today's free trial limit",
|
||||
"usage-limit-trial-daily-exhausted": "You've reached today's free trial limit",
|
||||
"usage-limit-trial-total-warning": "You've used {{percent}}% of your free trial usage",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Eliminar archivo",
|
||||
"drop-files-to-attach": "Arrastra archivos para adjuntar",
|
||||
"usage-limit-action": "Obtener más uso",
|
||||
"usage-limit-switch-model-hint": "Cambia a un modelo local/personalizado o usa otra clave API para continuar.",
|
||||
"usage-limit-trial-daily-warning": "Has usado el {{percent}}% del límite de prueba de hoy",
|
||||
"usage-limit-trial-daily-exhausted": "Has alcanzado el límite de prueba de hoy",
|
||||
"usage-limit-trial-total-warning": "Has usado el {{percent}}% de tu uso de prueba",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Supprimer le fichier",
|
||||
"drop-files-to-attach": "Déposez les fichiers à joindre",
|
||||
"usage-limit-action": "Obtenir plus d'utilisation",
|
||||
"usage-limit-switch-model-hint": "Passez à un modèle local/personnalisé ou utilisez une autre clé API pour continuer.",
|
||||
"usage-limit-trial-daily-warning": "Vous avez utilisé {{percent}}% de la limite d'essai d'aujourd'hui",
|
||||
"usage-limit-trial-daily-exhausted": "Vous avez atteint la limite d'essai d'aujourd'hui",
|
||||
"usage-limit-trial-total-warning": "Vous avez utilisé {{percent}}% de votre essai gratuit",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Rimuovi file",
|
||||
"drop-files-to-attach": "Trascina i file da allegare",
|
||||
"usage-limit-action": "Ottieni più utilizzo",
|
||||
"usage-limit-switch-model-hint": "Passa a un modello locale/personalizzato o usa un'altra chiave API per continuare.",
|
||||
"usage-limit-trial-daily-warning": "Hai utilizzato il {{percent}}% del limite di prova di oggi",
|
||||
"usage-limit-trial-daily-exhausted": "Hai raggiunto il limite di prova di oggi",
|
||||
"usage-limit-trial-total-warning": "Hai utilizzato il {{percent}}% del tuo utilizzo di prova",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "ファイルを削除",
|
||||
"drop-files-to-attach": "ファイルをドロップして添付",
|
||||
"usage-limit-action": "利用枠を増やす",
|
||||
"usage-limit-switch-model-hint": "続行するには、ローカル/カスタムモデルに切り替えるか、別の API キーを使用してください。",
|
||||
"usage-limit-trial-daily-warning": "本日の無料トライアル上限の{{percent}}%を使用しました",
|
||||
"usage-limit-trial-daily-exhausted": "本日の無料トライアル上限に達しました",
|
||||
"usage-limit-trial-total-warning": "無料トライアル利用枠の{{percent}}%を使用しました",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "파일 제거",
|
||||
"drop-files-to-attach": "첨부할 파일을 여기에 놓으세요",
|
||||
"usage-limit-action": "사용량 더 받기",
|
||||
"usage-limit-switch-model-hint": "계속하려면 로컬/사용자 지정 모델로 전환하거나 다른 API 키를 사용하세요.",
|
||||
"usage-limit-trial-daily-warning": "오늘 무료 체험 한도의 {{percent}}%를 사용했습니다",
|
||||
"usage-limit-trial-daily-exhausted": "오늘 무료 체험 한도에 도달했습니다",
|
||||
"usage-limit-trial-total-warning": "무료 체험 사용량의 {{percent}}%를 사용했습니다",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "Удалить файл",
|
||||
"drop-files-to-attach": "Перетащите файлы для прикрепления",
|
||||
"usage-limit-action": "Получить больше использования",
|
||||
"usage-limit-switch-model-hint": "Переключитесь на локальную/пользовательскую модель или используйте другой API-ключ, чтобы продолжить.",
|
||||
"usage-limit-trial-daily-warning": "Вы использовали {{percent}}% сегодняшнего лимита пробного периода",
|
||||
"usage-limit-trial-daily-exhausted": "Вы достигли сегодняшнего лимита пробного периода",
|
||||
"usage-limit-trial-total-warning": "Вы использовали {{percent}}% пробного лимита",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "移除文件",
|
||||
"drop-files-to-attach": "拖放文件以附加",
|
||||
"usage-limit-action": "获取更多用量",
|
||||
"usage-limit-switch-model-hint": "你也可以切换到本地/自定义模型,或使用其他 API key 继续。",
|
||||
"usage-limit-trial-daily-warning": "你已使用今日免费试用额度的 {{percent}}%",
|
||||
"usage-limit-trial-daily-exhausted": "你已达到今日免费试用额度上限",
|
||||
"usage-limit-trial-total-warning": "你已使用免费试用总额度的 {{percent}}%",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"remove-file": "移除文件",
|
||||
"drop-files-to-attach": "拖放文件以附加",
|
||||
"usage-limit-action": "取得更多用量",
|
||||
"usage-limit-switch-model-hint": "你也可以切換到本機/自訂模型,或使用其他 API key 繼續。",
|
||||
"usage-limit-trial-daily-warning": "你已使用今日免費試用額度的 {{percent}}%",
|
||||
"usage-limit-trial-daily-exhausted": "你已達到今日免費試用額度上限",
|
||||
"usage-limit-trial-total-warning": "你已使用免費試用總額度的 {{percent}}%",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ import { getAuthStore, getWorkerList, type CloudModelType } from './authStore';
|
|||
import { usePageTabStore } from './pageTabStore';
|
||||
import { useProjectStore } from './projectStore';
|
||||
|
||||
const API_CODE_TRIAL_LIMIT = '22';
|
||||
|
||||
const hasApiCode = (value: unknown, code: string) =>
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
String((value as { code?: unknown }).code) === code;
|
||||
|
||||
interface Task {
|
||||
messages: Message[];
|
||||
type: string;
|
||||
|
|
@ -832,6 +839,18 @@ const chatStore = (initial?: Partial<ChatStore>) =>
|
|||
} else if (modelType === 'cloud') {
|
||||
// get current model
|
||||
const res = await proxyFetchGet('/api/v1/user/key');
|
||||
if (hasApiCode(res, API_CODE_TRIAL_LIMIT)) {
|
||||
throw new Error(
|
||||
res.text ||
|
||||
'Free trial usage limit reached. Switch to a local/custom model or use another API key to continue.'
|
||||
);
|
||||
}
|
||||
if (!res.value) {
|
||||
throw new Error(
|
||||
res.text ||
|
||||
'Failed to get cloud model key. Please check your account or model settings.'
|
||||
);
|
||||
}
|
||||
if (res.warning_code && res.warning_code === '21') {
|
||||
showStorageToast();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue