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:
Junyan Chin
2026-02-22 04:25:45 -05:00
committed by GitHub
parent aa09a27a63
commit 42caae1bcf
17 changed files with 161 additions and 5 deletions

View File

@@ -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,
);
}
}

View File

@@ -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,