feat: add beta environment banner

Add beta environment warning banner for Cloud beta deployments with i18n support (zh/en/ja)
This commit is contained in:
RockChinQ
2026-09-28 13:17:03 +08:00
committed by GitHub
parent a75e57ade3
commit e778445982
9 changed files with 2240 additions and 428 deletions
@@ -200,11 +200,22 @@ class SystemRouterGroup(group.RouterGroup):
if invitation_delivery_service is None:
invitation_delivery_service = InvitationDeliveryService(self.ap)
# Detect beta flag from telemetry diagnostics manager
is_beta = False
diagnostics = getattr(self.ap, 'diagnostics', None)
if diagnostics is not None:
is_beta = getattr(diagnostics, 'beta', False)
# Detect Cloud deployment mode
is_cloud = getattr(getattr(self.ap, 'deployment', None), 'mode', 'oss') == 'cloud'
return self.success(
data={
'version': constants.semantic_version,
'debug': constants.debug_mode,
'edition': constants.edition,
'beta': is_beta,
'deployment_mode': 'cloud' if is_cloud else 'oss',
'enable_marketplace': self.ap.instance_config.data.get('plugin', {}).get(
'enable_marketplace', True
),
+2113 -427
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -1,9 +1,15 @@
import { Outlet } from 'react-router-dom';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { BetaBanner } from '@/components/BetaBanner';
// Top-level route layout: drives the dynamic document title from the active
// route and renders the matched child route via <Outlet />.
export default function RootLayout() {
useDocumentTitle();
return <Outlet />;
return (
<>
<BetaBanner />
<Outlet />
</>
);
}
+2
View File
@@ -566,6 +566,8 @@ export interface ApiRespSystemInfo {
debug: boolean;
version: string;
edition: string;
beta: boolean;
deployment_mode: 'cloud' | 'oss';
/** Independent instance-level gate for local stdio MCP transports. */
mcp_stdio_enabled: boolean;
cloud_service_url: string;
+2
View File
@@ -25,6 +25,8 @@ export const systemInfo: ApiRespSystemInfo = {
debug: false,
version: '',
edition: 'community',
beta: false,
deployment_mode: 'oss',
mcp_stdio_enabled: false,
enable_marketplace: true,
cloud_service_url: '',
+80
View File
@@ -0,0 +1,80 @@
import { useTranslation } from 'react-i18next';
import { AlertCircle, X } from 'lucide-react';
import { useState, useEffect } from 'react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { backendClient } from '@/app/infra/http';
export function BetaBanner() {
const { t, i18n } = useTranslation();
const [show, setShow] = useState(false);
const [dismissed, setDismissed] = useState(false);
useEffect(() => {
const checkBetaStatus = async () => {
try {
const info = await backendClient.getSystemInfo();
const shouldShow =
info.deployment_mode === 'cloud' && info.beta === true;
setShow(shouldShow);
} catch {
setShow(false);
}
};
checkBetaStatus();
}, []);
if (!show || dismissed) {
return null;
}
const cloudUrl =
i18n.language === 'zh-Hans' || i18n.language === 'zh_CN'
? 'https://langbot.app/zh/cloud'
: i18n.language === 'ja' || i18n.language === 'ja_JP'
? 'https://langbot.app/ja/cloud'
: 'https://langbot.app/cloud';
const ossUrl =
i18n.language === 'zh-Hans' || i18n.language === 'zh_CN'
? 'https://langbot.app/zh/docs/quick-start/deploy'
: i18n.language === 'ja' || i18n.language === 'ja_JP'
? 'https://langbot.app/ja/docs/quick-start/deploy'
: 'https://langbot.app/docs/quick-start/deploy';
return (
<Alert className="rounded-none border-x-0 border-t-0 bg-amber-50 dark:bg-amber-950/20 border-amber-200 dark:border-amber-900/50 py-2">
<div className="flex items-start gap-3">
<AlertCircle className="h-4 w-4 mt-0.5 text-amber-600 dark:text-amber-500 shrink-0" />
<AlertDescription className="text-sm text-amber-900 dark:text-amber-100 flex-1">
{t('beta_banner.message')}{' '}
<a
href={cloudUrl}
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700 dark:hover:text-amber-300 font-medium"
>
{t('beta_banner.cloud_link')}
</a>{' '}
{t('beta_banner.or')}{' '}
<a
href={ossUrl}
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700 dark:hover:text-amber-300 font-medium"
>
{t('beta_banner.oss_link')}
</a>
{t('beta_banner.period')}
</AlertDescription>
<button
onClick={() => setDismissed(true)}
className="shrink-0 text-amber-600 dark:text-amber-500 hover:text-amber-800 dark:hover:text-amber-300 p-1 -mt-1 -mr-2"
aria-label={t('beta_banner.dismiss')}
>
<X className="h-4 w-4" />
</button>
</div>
</Alert>
);
}
+9
View File
@@ -3313,5 +3313,14 @@ const enUS = {
selectFromSidebar: 'Select a plugin page from the sidebar',
invalidPage: 'Invalid plugin page',
},
beta_banner: {
message:
'This environment is in Beta testing. Service stability is not guaranteed. Consider using',
cloud_link: 'LangBot Cloud (dedicated environment)',
or: 'or',
oss_link: 'self-hosted open-source version',
period: '.',
dismiss: 'Dismiss',
},
};
export default enUS;
+8
View File
@@ -3327,5 +3327,13 @@ const jaJP = {
selectFromSidebar: 'サイドバーからプラグインページを選択してください',
invalidPage: '無効なプラグインページ',
},
beta_banner: {
message: 'この環境はベータテスト中です。サービスの安定性は保証されません。',
cloud_link: 'LangBot Cloud(専用環境)',
or: 'または',
oss_link: 'セルフホスト版',
period: 'をご検討ください。',
dismiss: '閉じる',
},
};
export default jaJP;
+8
View File
@@ -3123,5 +3123,13 @@ const zhHans = {
selectFromSidebar: '从侧边栏选择一个插件页面',
invalidPage: '无效的插件页面',
},
beta_banner: {
message: '当前环境正在进行 Beta 测试,服务稳定性不受保证,建议优先选择',
cloud_link: '独立环境版 LangBot Cloud',
or: '或',
oss_link: '自部署开源版本',
period: '。',
dismiss: '关闭横幅',
},
};
export default zhHans;