mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
feat: Implement extension and bot limitations across services and UI (#1991)
- Added checks for maximum allowed extensions, bots, and pipelines in the backend services (PluginsRouterGroup, BotService, MCPService, PipelineService). - Updated system configuration to include limitation settings for max_bots, max_pipelines, and max_extensions. - Enhanced frontend components to handle limitations, providing user feedback when limits are reached. - Added internationalization support for limitation messages in English, Japanese, Simplified Chinese, and Traditional Chinese.
This commit is contained in:
@@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||
import BotDetailDialog from '@/app/home/bots/BotDetailDialog';
|
||||
import { CustomApiError } from '@/app/infra/entities/common';
|
||||
import { systemInfo } from '@/app/infra/http';
|
||||
|
||||
export default function BotConfigPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -60,6 +61,11 @@ export default function BotConfigPage() {
|
||||
}
|
||||
|
||||
function handleCreateBotClick() {
|
||||
const maxBots = systemInfo.limitation?.max_bots ?? -1;
|
||||
if (maxBots >= 0 && botList.length >= maxBots) {
|
||||
toast.error(t('limitation.maxBotsReached', { max: maxBots }));
|
||||
return;
|
||||
}
|
||||
setSelectedBotId('');
|
||||
setDetailDialogOpen(true);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { systemInfo } from '@/app/infra/http';
|
||||
|
||||
export default function PluginConfigPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -87,6 +88,11 @@ export default function PluginConfigPage() {
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
const maxPipelines = systemInfo.limitation?.max_pipelines ?? -1;
|
||||
if (maxPipelines >= 0 && pipelineList.length >= maxPipelines) {
|
||||
toast.error(t('limitation.maxPipelinesReached', { max: maxPipelines }));
|
||||
return;
|
||||
}
|
||||
setIsEditForm(false);
|
||||
setSelectedPipelineId('');
|
||||
setDialogOpen(true);
|
||||
|
||||
@@ -453,7 +453,10 @@ export default function MCPFormDialog({
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to save MCP server:', error);
|
||||
toast.error(isEditMode ? t('mcp.updateFailed') : t('mcp.createFailed'));
|
||||
const errMsg = (error as CustomApiError).msg || '';
|
||||
toast.error(
|
||||
(isEditMode ? t('mcp.updateFailed') : t('mcp.createFailed')) + errMsg,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,28 @@ export default function PluginConfigPage() {
|
||||
setFetchingAssets(false);
|
||||
}
|
||||
|
||||
async function checkExtensionsLimit(): Promise<boolean> {
|
||||
const maxExtensions = systemInfo.limitation?.max_extensions ?? -1;
|
||||
if (maxExtensions < 0) return true;
|
||||
try {
|
||||
const [pluginsResp, mcpResp] = await Promise.all([
|
||||
httpClient.getPlugins(),
|
||||
httpClient.getMCPServers(),
|
||||
]);
|
||||
const total =
|
||||
(pluginsResp.plugins?.length ?? 0) + (mcpResp.servers?.length ?? 0);
|
||||
if (total >= maxExtensions) {
|
||||
toast.error(
|
||||
t('limitation.maxExtensionsReached', { max: maxExtensions }),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
// If we can't check, let backend handle it
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function fetchGithubReleases() {
|
||||
if (!githubURL.trim()) {
|
||||
toast.error(t('plugins.enterRepoUrl'));
|
||||
@@ -328,6 +350,8 @@ export default function PluginConfigPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await checkExtensionsLimit())) return;
|
||||
|
||||
setModalOpen(true);
|
||||
setPluginInstallStatus(PluginInstallStatus.INSTALLING);
|
||||
setInstallError(null);
|
||||
@@ -336,7 +360,8 @@ export default function PluginConfigPage() {
|
||||
[t, pluginSystemStatus, installPlugin],
|
||||
);
|
||||
|
||||
const handleFileSelect = useCallback(() => {
|
||||
const handleFileSelect = useCallback(async () => {
|
||||
if (!(await checkExtensionsLimit())) return;
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
@@ -633,7 +658,8 @@ export default function PluginConfigPage() {
|
||||
{activeTab === 'mcp-servers' ? (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
if (!(await checkExtensionsLimit())) return;
|
||||
setActiveTab('mcp-servers');
|
||||
setIsEditMode(false);
|
||||
setEditingServerName(null);
|
||||
@@ -661,7 +687,8 @@ export default function PluginConfigPage() {
|
||||
{t('plugins.uploadLocal')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
if (!(await checkExtensionsLimit())) return;
|
||||
setInstallSource('github');
|
||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
||||
setInstallError(null);
|
||||
@@ -683,7 +710,8 @@ export default function PluginConfigPage() {
|
||||
</TabsContent>
|
||||
<TabsContent value="market" className="flex-1 overflow-y-auto mt-0">
|
||||
<MarketPage
|
||||
installPlugin={(plugin: PluginV4) => {
|
||||
installPlugin={async (plugin: PluginV4) => {
|
||||
if (!(await checkExtensionsLimit())) return;
|
||||
setInstallSource('marketplace');
|
||||
setInstallInfo({
|
||||
plugin_author: plugin.author,
|
||||
|
||||
@@ -240,13 +240,21 @@ export interface PluginReorderElement {
|
||||
}
|
||||
|
||||
// system
|
||||
export interface SystemLimitation {
|
||||
max_bots: number;
|
||||
max_pipelines: number;
|
||||
max_extensions: number;
|
||||
}
|
||||
|
||||
export interface ApiRespSystemInfo {
|
||||
debug: boolean;
|
||||
version: string;
|
||||
edition: string;
|
||||
cloud_service_url: string;
|
||||
enable_marketplace: boolean;
|
||||
allow_modify_login_info: boolean;
|
||||
disable_models_service: boolean;
|
||||
limitation: SystemLimitation;
|
||||
}
|
||||
|
||||
export interface ApiRespPluginSystemStatus {
|
||||
|
||||
@@ -6,10 +6,16 @@ import { ApiRespSystemInfo } from '@/app/infra/entities/api';
|
||||
export let systemInfo: ApiRespSystemInfo = {
|
||||
debug: false,
|
||||
version: '',
|
||||
edition: 'community',
|
||||
enable_marketplace: true,
|
||||
cloud_service_url: '',
|
||||
allow_modify_login_info: true,
|
||||
disable_models_service: false,
|
||||
limitation: {
|
||||
max_bots: -1,
|
||||
max_pipelines: -1,
|
||||
max_extensions: -1,
|
||||
},
|
||||
};
|
||||
|
||||
// 用户信息
|
||||
|
||||
@@ -962,6 +962,14 @@ const enUS = {
|
||||
sessions: 'Sessions',
|
||||
},
|
||||
},
|
||||
limitation: {
|
||||
maxBotsReached:
|
||||
'Maximum number of bots ({{max}}) reached. Please remove an existing bot before creating a new one.',
|
||||
maxPipelinesReached:
|
||||
'Maximum number of pipelines ({{max}}) reached. Please remove an existing pipeline before creating a new one.',
|
||||
maxExtensionsReached:
|
||||
'Maximum number of extensions ({{max}}) reached. Please remove an existing MCP server or plugin before adding a new one.',
|
||||
},
|
||||
};
|
||||
|
||||
export default enUS;
|
||||
|
||||
@@ -949,6 +949,14 @@ const jaJP = {
|
||||
sessions: 'セッション',
|
||||
},
|
||||
},
|
||||
limitation: {
|
||||
maxBotsReached:
|
||||
'ボット数が上限({{max}}個)に達しました。新しいボットを作成するには、既存のボットを削除してください。',
|
||||
maxPipelinesReached:
|
||||
'パイプライン数が上限({{max}}個)に達しました。新しいパイプラインを作成するには、既存のパイプラインを削除してください。',
|
||||
maxExtensionsReached:
|
||||
'拡張機能数が上限({{max}}個)に達しました。新しい MCP サーバーやプラグインを追加するには、既存のものを削除してください。',
|
||||
},
|
||||
};
|
||||
|
||||
export default jaJP;
|
||||
|
||||
@@ -922,6 +922,14 @@ const zhHans = {
|
||||
sessions: '会话记录',
|
||||
},
|
||||
},
|
||||
limitation: {
|
||||
maxBotsReached:
|
||||
'已达到机器人数量上限({{max}}个)。请先删除已有机器人后再创建新的。',
|
||||
maxPipelinesReached:
|
||||
'已达到流水线数量上限({{max}}个)。请先删除已有流水线后再创建新的。',
|
||||
maxExtensionsReached:
|
||||
'已达到扩展数量上限({{max}}个)。请先删除已有的 MCP 服务器或插件后再添加新的。',
|
||||
},
|
||||
};
|
||||
|
||||
export default zhHans;
|
||||
|
||||
@@ -897,6 +897,14 @@ const zhHant = {
|
||||
sessions: '會話記錄',
|
||||
},
|
||||
},
|
||||
limitation: {
|
||||
maxBotsReached:
|
||||
'已達到機器人數量上限({{max}}個)。請先刪除已有機器人後再建立新的。',
|
||||
maxPipelinesReached:
|
||||
'已達到流水線數量上限({{max}}個)。請先刪除已有流水線後再建立新的。',
|
||||
maxExtensionsReached:
|
||||
'已達到擴充功能數量上限({{max}}個)。請先刪除已有的 MCP 伺服器或外掛後再新增。',
|
||||
},
|
||||
};
|
||||
|
||||
export default zhHant;
|
||||
|
||||
Reference in New Issue
Block a user