mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-27 07:54:19 +00:00
fix: refine extension ui and backend errors
This commit is contained in:
@@ -76,18 +76,26 @@ export default function HomeLayout({
|
||||
|
||||
// Auto-redirect to wizard on first visit (wizard not yet completed on this instance)
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const checkWizard = async () => {
|
||||
try {
|
||||
// Always re-fetch to ensure we have the latest wizard_status from backend
|
||||
await initializeSystemInfo();
|
||||
if (systemInfo.wizard_status === 'none') {
|
||||
navigate('/wizard');
|
||||
await initializeSystemInfo({ throwOnError: true });
|
||||
if (!cancelled && systemInfo.wizard_status === 'none') {
|
||||
navigate('/wizard', { replace: true });
|
||||
}
|
||||
} catch {
|
||||
// If fetching system info fails, don't redirect
|
||||
if (!cancelled) {
|
||||
navigate('/backend-unavailable', { replace: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
checkWizard();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -50,17 +50,6 @@ export default function ExtensionCardComponent({
|
||||
cardVO.iconURL || httpClient.getPluginIconURL(cardVO.author, cardVO.name);
|
||||
const showFallback = iconFailed || !iconSrc;
|
||||
|
||||
const getTypeBadgeColor = (type: ExtensionType) => {
|
||||
switch (type) {
|
||||
case 'mcp':
|
||||
return 'border-sky-500 text-sky-600 dark:border-sky-400 dark:text-sky-300';
|
||||
case 'skill':
|
||||
return 'border-emerald-500 text-emerald-600 dark:border-emerald-400 dark:text-emerald-300';
|
||||
default:
|
||||
return 'border-violet-500 text-violet-600 dark:border-violet-400 dark:text-violet-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeLabel = (type: ExtensionType) => {
|
||||
switch (type) {
|
||||
case 'mcp':
|
||||
@@ -72,6 +61,30 @@ export default function ExtensionCardComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeIcon = (type: ExtensionType) => {
|
||||
switch (type) {
|
||||
case 'mcp':
|
||||
return Server;
|
||||
case 'skill':
|
||||
return Sparkles;
|
||||
default:
|
||||
return Puzzle;
|
||||
}
|
||||
};
|
||||
|
||||
const renderTypeBadge = (type: ExtensionType) => {
|
||||
const TypeIcon = getTypeIcon(type);
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="flex-shrink-0 gap-1.5 border-blue-200 bg-blue-50/60 text-[0.7rem] text-blue-700 dark:border-blue-500/40 dark:bg-blue-500/10 dark:text-blue-300"
|
||||
>
|
||||
<TypeIcon className="size-3.5" />
|
||||
{getTypeLabel(type)}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPluginContent = () => (
|
||||
<>
|
||||
<div className="text-[0.7rem] text-muted-foreground truncate w-full">
|
||||
@@ -84,12 +97,7 @@ export default function ExtensionCardComponent({
|
||||
<Badge variant="outline" className="text-[0.7rem] flex-shrink-0">
|
||||
v{cardVO.version}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor(cardVO.type)}`}
|
||||
>
|
||||
{getTypeLabel(cardVO.type)}
|
||||
</Badge>
|
||||
{renderTypeBadge(cardVO.type)}
|
||||
{cardVO.debug && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -143,12 +151,7 @@ export default function ExtensionCardComponent({
|
||||
<div className="text-[1.2rem] text-foreground truncate max-w-[10rem]">
|
||||
{cardVO.label}
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor('mcp')}`}
|
||||
>
|
||||
MCP
|
||||
</Badge>
|
||||
{renderTypeBadge('mcp')}
|
||||
{cardVO.mode && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -169,12 +172,10 @@ export default function ExtensionCardComponent({
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-[0.8rem] text-muted-foreground line-clamp-2 w-full">
|
||||
{cardVO.description || t('mcp.noToolsFound')}
|
||||
{cardVO.tools !== undefined && cardVO.tools > 0 && (
|
||||
<span className="ml-1">
|
||||
{t('mcp.toolCount', { count: cardVO.tools })}
|
||||
</span>
|
||||
)}
|
||||
{cardVO.description ||
|
||||
(cardVO.tools !== undefined && cardVO.tools > 0
|
||||
? t('mcp.toolCount', { count: cardVO.tools })
|
||||
: t('mcp.noToolsFound'))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -188,12 +189,7 @@ export default function ExtensionCardComponent({
|
||||
<div className="text-[1.2rem] text-foreground truncate max-w-[10rem]">
|
||||
{cardVO.label}
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-[0.7rem] flex-shrink-0 ${getTypeBadgeColor('skill')}`}
|
||||
>
|
||||
{t('common.skill')}
|
||||
</Badge>
|
||||
{renderTypeBadge('skill')}
|
||||
</div>
|
||||
<div className="text-[0.8rem] text-muted-foreground line-clamp-2 w-full">
|
||||
{cardVO.description}
|
||||
|
||||
@@ -90,12 +90,17 @@ export const getCloudServiceClientSync = (): CloudServiceClient => {
|
||||
* 手动初始化系统信息
|
||||
* 可以在应用启动时调用此方法预先获取系统信息
|
||||
*/
|
||||
export const initializeSystemInfo = async (): Promise<void> => {
|
||||
export const initializeSystemInfo = async (options?: {
|
||||
throwOnError?: boolean;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
Object.assign(systemInfo, await backendClient.getSystemInfo());
|
||||
cloudServiceClient.updateBaseURL(systemInfo.cloud_service_url);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize system info:', error);
|
||||
if (options?.throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AlertCircle, Home, RefreshCw } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export default function BackendUnavailablePage() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
||||
<div className="mx-auto flex max-w-md flex-col items-center text-center">
|
||||
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
|
||||
<AlertCircle className="h-8 w-8 text-destructive" />
|
||||
</div>
|
||||
|
||||
<p className="mb-2 text-sm font-medium text-destructive">
|
||||
{t('errorPage.backendUnavailableStatus')}
|
||||
</p>
|
||||
|
||||
<h1 className="text-2xl font-semibold text-foreground">
|
||||
{t('common.loginLoadError')}
|
||||
</h1>
|
||||
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
{t('common.loginLoadErrorDesc')}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2"
|
||||
onClick={() => navigate('/login')}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
{t('errorPage.backToLogin')}
|
||||
</Button>
|
||||
<Button className="gap-2" onClick={() => window.location.reload()}>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
{t('common.retry')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1500,8 +1500,10 @@ const enUS = {
|
||||
notFound: 'Page not found',
|
||||
notFoundDescription:
|
||||
'The page you are looking for does not exist or has been moved.',
|
||||
backendUnavailableStatus: 'Backend unavailable',
|
||||
goBack: 'Go Back',
|
||||
backToHome: 'Back to Home',
|
||||
backToLogin: 'Back to Login',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'Select a plugin page from the sidebar',
|
||||
|
||||
@@ -1429,8 +1429,10 @@ const esES = {
|
||||
'Ocurrió un error inesperado. Por favor, inténtelo de nuevo más tarde.',
|
||||
notFound: 'Página no encontrada',
|
||||
notFoundDescription: 'La página que buscas no existe o ha sido movida.',
|
||||
backendUnavailableStatus: 'Backend no disponible',
|
||||
goBack: 'Volver',
|
||||
backToHome: 'Ir al inicio',
|
||||
backToLogin: 'Volver al inicio de sesión',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'Selecciona una página de plugin en la barra lateral',
|
||||
|
||||
@@ -1411,8 +1411,10 @@ const jaJP = {
|
||||
notFound: 'ページが見つかりません',
|
||||
notFoundDescription:
|
||||
'お探しのページは存在しないか、移動された可能性があります。',
|
||||
backendUnavailableStatus: 'バックエンドを利用できません',
|
||||
goBack: '戻る',
|
||||
backToHome: 'ホームに戻る',
|
||||
backToLogin: 'ログインに戻る',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'サイドバーからプラグインページを選択してください',
|
||||
|
||||
@@ -1393,6 +1393,18 @@ const ruRU = {
|
||||
backToWorkbench: 'Вернуться к рабочей панели',
|
||||
},
|
||||
},
|
||||
errorPage: {
|
||||
unexpectedError: 'Что-то пошло не так',
|
||||
unexpectedErrorDescription:
|
||||
'Произошла непредвиденная ошибка. Повторите попытку позже.',
|
||||
notFound: 'Страница не найдена',
|
||||
notFoundDescription:
|
||||
'Страница, которую вы ищете, не существует или была перемещена.',
|
||||
backendUnavailableStatus: 'Бэкенд недоступен',
|
||||
goBack: 'Назад',
|
||||
backToHome: 'На главную',
|
||||
backToLogin: 'Вернуться к входу',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'Выберите страницу плагина на боковой панели',
|
||||
invalidPage: 'Недопустимая страница плагина',
|
||||
|
||||
@@ -1369,8 +1369,10 @@ const thTH = {
|
||||
'เกิดข้อผิดพลาดที่ไม่คาดคิด กรุณาลองใหม่อีกครั้งในภายหลัง',
|
||||
notFound: 'ไม่พบหน้า',
|
||||
notFoundDescription: 'หน้าที่คุณกำลังมองหาไม่มีอยู่หรือถูกย้ายแล้ว',
|
||||
backendUnavailableStatus: 'แบ็กเอนด์ไม่พร้อมใช้งาน',
|
||||
goBack: 'ย้อนกลับ',
|
||||
backToHome: 'กลับหน้าหลัก',
|
||||
backToLogin: 'กลับไปหน้าเข้าสู่ระบบ',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'เลือกหน้าปลั๊กอินจากแถบด้านข้าง',
|
||||
|
||||
@@ -1393,8 +1393,10 @@ const viVN = {
|
||||
notFound: 'Không tìm thấy trang',
|
||||
notFoundDescription:
|
||||
'Trang bạn tìm kiếm không tồn tại hoặc đã được di chuyển.',
|
||||
backendUnavailableStatus: 'Backend không khả dụng',
|
||||
goBack: 'Quay lại',
|
||||
backToHome: 'Về trang chủ',
|
||||
backToLogin: 'Quay lại đăng nhập',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: 'Chọn một trang plugin từ thanh bên',
|
||||
|
||||
@@ -1436,8 +1436,10 @@ const zhHans = {
|
||||
unexpectedErrorDescription: '发生了意外错误,请稍后重试。',
|
||||
notFound: '页面未找到',
|
||||
notFoundDescription: '你访问的页面不存在或已被移动。',
|
||||
backendUnavailableStatus: '后端服务不可用',
|
||||
goBack: '返回上页',
|
||||
backToHome: '返回首页',
|
||||
backToLogin: '返回登录',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: '从侧边栏选择一个插件页面',
|
||||
|
||||
@@ -1343,8 +1343,10 @@ const zhHant = {
|
||||
unexpectedErrorDescription: '發生了意外錯誤,請稍後重試。',
|
||||
notFound: '頁面未找到',
|
||||
notFoundDescription: '你訪問的頁面不存在或已被移動。',
|
||||
backendUnavailableStatus: '後端服務不可用',
|
||||
goBack: '返回上頁',
|
||||
backToHome: '返回首頁',
|
||||
backToLogin: '返回登入',
|
||||
},
|
||||
pluginPages: {
|
||||
selectFromSidebar: '從側邊欄選擇一個插件頁面',
|
||||
|
||||
@@ -25,6 +25,7 @@ import MCPPage from '@/app/home/mcp/page';
|
||||
import KnowledgePage from '@/app/home/knowledge/page';
|
||||
import SkillsPage from '@/app/home/skills/page';
|
||||
import ErrorPage from '@/components/ErrorPage';
|
||||
import BackendUnavailablePage from '@/components/BackendUnavailablePage';
|
||||
import PluginPagesPage from '@/app/home/plugin-pages/page';
|
||||
|
||||
const Loading = () => <div>Loading...</div>;
|
||||
@@ -65,6 +66,10 @@ export const router = createBrowserRouter([
|
||||
path: '/wizard',
|
||||
element: <WizardPage />,
|
||||
},
|
||||
{
|
||||
path: '/backend-unavailable',
|
||||
element: <BackendUnavailablePage />,
|
||||
},
|
||||
{
|
||||
path: '/auth/space/callback',
|
||||
element: <SpaceCallbackPage />,
|
||||
|
||||
Reference in New Issue
Block a user