mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-06 13:56:02 +00:00
feat(mcp): add Docs/Tools tablist on detail page, tidy sidebar label
Wrap the MCP detail right panel in a compact left-aligned Docs/Tools tablist (Docs first). Move the tool count into the Tools tab label and drop the redundant panel title/subtitle; connecting/failed states still render the status component. Shorten the sidebar 'Installed Extensions' entry to 'Installed' across all 8 locales, and add tabTools/tabDocs/ noReadme strings.
This commit is contained in:
@@ -40,6 +40,13 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/ui/tabs';
|
||||
import MCPReadme from '@/app/home/mcp/components/mcp-form/MCPReadme';
|
||||
import {
|
||||
MCPServerRuntimeInfo,
|
||||
MCPTool,
|
||||
@@ -264,35 +271,10 @@ function RuntimePanel({
|
||||
|
||||
const isConnected =
|
||||
!mcpTesting && runtimeInfo.status === MCPSessionStatus.CONNECTED;
|
||||
// Only treat an explicit error (or box-unavailable) as failed; while testing,
|
||||
// connecting, or in an initial/unresolved state, show "connecting" so we
|
||||
// don't flash "connection failed" during a normal connection attempt.
|
||||
const isFailed =
|
||||
!mcpTesting &&
|
||||
(runtimeInfo.status === MCPSessionStatus.ERROR ||
|
||||
runtimeInfo.error_phase === 'box_unavailable');
|
||||
const tools = runtimeInfo.tools || [];
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-medium">{t('mcp.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isConnected
|
||||
? t('mcp.toolCount', { count: tools.length })
|
||||
: isFailed
|
||||
? t('mcp.connectionFailedStatus')
|
||||
: t('mcp.connecting')}
|
||||
</p>
|
||||
</div>
|
||||
{isConnected && (
|
||||
<Badge variant="outline">
|
||||
{t('mcp.toolCount', { count: tools.length })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isConnected && (
|
||||
<div className="rounded-md bg-muted/40 p-3">
|
||||
<StatusDisplay testing={mcpTesting} runtimeInfo={runtimeInfo} t={t} />
|
||||
@@ -434,6 +416,9 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
|
||||
const [runtimeInfo, setRuntimeInfo] = useState<MCPServerRuntimeInfo | null>(
|
||||
null,
|
||||
);
|
||||
// README markdown captured from LangBot Space at install time, surfaced in
|
||||
// the Docs tab of the detail panel. Empty for manually-created servers.
|
||||
const [readme, setReadme] = useState<string>('');
|
||||
const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const watchMode = form.watch('mode');
|
||||
const {
|
||||
@@ -611,6 +596,7 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
|
||||
setStdioArgs(newStdioArgs);
|
||||
form.reset(formValues);
|
||||
setRuntimeInfo(server.runtime_info ?? null);
|
||||
setReadme(server.readme ?? '');
|
||||
} catch (error) {
|
||||
console.error('Failed to load server:', error);
|
||||
toast.error(t('mcp.loadFailed'));
|
||||
@@ -1063,6 +1049,45 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
|
||||
/>
|
||||
);
|
||||
|
||||
// In edit mode the right side shows a tablist switching between the live
|
||||
// Tools list and the Docs (README captured from LangBot Space at install).
|
||||
// Create mode has neither, so it falls back to the bare runtime placeholder.
|
||||
// The tool count lives in the tab label (only when connected); the panel
|
||||
// body itself no longer repeats a title/subtitle.
|
||||
const toolsConnected =
|
||||
!mcpTesting && runtimeInfo?.status === MCPSessionStatus.CONNECTED;
|
||||
const toolsCount = runtimeInfo?.tools?.length ?? 0;
|
||||
const toolsTabLabel = toolsConnected
|
||||
? `${t('mcp.tabTools')} ${toolsCount}`
|
||||
: t('mcp.tabTools');
|
||||
|
||||
const detailPanel = isEditMode ? (
|
||||
<Tabs defaultValue="tools" className="flex h-full min-h-0 flex-col">
|
||||
<TabsList>
|
||||
<TabsTrigger value="docs" className="flex-none px-4">
|
||||
{t('mcp.tabDocs')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tools" className="flex-none px-4">
|
||||
{toolsTabLabel}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent
|
||||
value="docs"
|
||||
className="mt-4 min-h-0 flex-1 overflow-y-auto"
|
||||
>
|
||||
<MCPReadme readme={readme} />
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="tools"
|
||||
className="mt-4 min-h-0 flex-1 overflow-y-auto"
|
||||
>
|
||||
{runtimePanel}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : (
|
||||
runtimePanel
|
||||
);
|
||||
|
||||
if (layout === 'split') {
|
||||
return (
|
||||
<Form {...form}>
|
||||
@@ -1078,7 +1103,7 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
|
||||
</div>
|
||||
<div className="hidden w-px shrink-0 bg-border lg:block" />
|
||||
<div className="min-w-0 flex-1 pb-6 lg:min-h-0 lg:overflow-y-auto lg:overflow-x-hidden">
|
||||
{runtimePanel}
|
||||
{detailPanel}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -1093,7 +1118,7 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
|
||||
className="space-y-5"
|
||||
>
|
||||
{sideHeader}
|
||||
{runtimePanel}
|
||||
{detailPanel}
|
||||
{configSection}
|
||||
{sideFooter}
|
||||
</form>
|
||||
|
||||
@@ -2,7 +2,7 @@ const enUS = {
|
||||
sidebar: {
|
||||
home: 'Home',
|
||||
extensions: 'Extensions',
|
||||
installedPlugins: 'Installed Extensions',
|
||||
installedPlugins: 'Installed',
|
||||
pluginMarket: 'Extension Market',
|
||||
mcpServers: 'MCP Servers',
|
||||
addExtension: 'Add Extension',
|
||||
@@ -765,6 +765,9 @@ const enUS = {
|
||||
toolsFound: 'tools',
|
||||
unknownError: 'Unknown error',
|
||||
noToolsFound: 'No tools found',
|
||||
tabTools: 'Tools',
|
||||
tabDocs: 'Docs',
|
||||
noReadme: 'No documentation available',
|
||||
parseResultFailed: 'Failed to parse test result',
|
||||
noResultReturned: 'Test returned no result',
|
||||
getTaskFailed: 'Failed to get task status',
|
||||
|
||||
@@ -2,7 +2,7 @@ const esES = {
|
||||
sidebar: {
|
||||
home: 'Inicio',
|
||||
extensions: 'Extensiones',
|
||||
installedPlugins: 'Plugins instalados',
|
||||
installedPlugins: 'Instalados',
|
||||
pluginMarket: 'Tienda',
|
||||
mcpServers: 'Servidores MCP',
|
||||
addExtension: 'Añadir extensión',
|
||||
@@ -779,6 +779,9 @@ const esES = {
|
||||
toolsFound: 'herramientas',
|
||||
unknownError: 'Error desconocido',
|
||||
noToolsFound: 'No se encontraron herramientas',
|
||||
tabTools: 'Herramientas',
|
||||
tabDocs: 'Documentación',
|
||||
noReadme: 'No hay documentación disponible',
|
||||
parseResultFailed: 'Error al analizar el resultado de la prueba',
|
||||
noResultReturned: 'La prueba no devolvió resultados',
|
||||
getTaskFailed: 'Error al obtener el estado de la tarea',
|
||||
|
||||
@@ -2,7 +2,7 @@ const jaJP = {
|
||||
sidebar: {
|
||||
home: 'ホーム',
|
||||
extensions: '拡張機能',
|
||||
installedPlugins: 'インストール済みプラグイン',
|
||||
installedPlugins: 'インストール済み',
|
||||
pluginMarket: 'プラグインマーケット',
|
||||
mcpServers: 'MCPサーバー',
|
||||
addExtension: '拡張機能を追加',
|
||||
@@ -770,6 +770,9 @@ const jaJP = {
|
||||
toolsFound: '個のツール',
|
||||
unknownError: '不明なエラー',
|
||||
noToolsFound: 'ツールが見つかりません',
|
||||
tabTools: 'ツール',
|
||||
tabDocs: 'ドキュメント',
|
||||
noReadme: 'ドキュメントがありません',
|
||||
parseResultFailed: 'テスト結果の解析に失敗しました',
|
||||
noResultReturned: 'テスト結果が返されませんでした',
|
||||
getTaskFailed: 'タスクステータスの取得に失敗しました',
|
||||
|
||||
@@ -2,7 +2,7 @@ const ruRU = {
|
||||
sidebar: {
|
||||
home: 'Главная',
|
||||
extensions: 'Расширения',
|
||||
installedPlugins: 'Установленные плагины',
|
||||
installedPlugins: 'Установленные',
|
||||
pluginMarket: 'Маркетплейс',
|
||||
mcpServers: 'MCP-серверы',
|
||||
addExtension: 'Добавить расширение',
|
||||
@@ -775,6 +775,9 @@ const ruRU = {
|
||||
toolsFound: 'инструментов',
|
||||
unknownError: 'Неизвестная ошибка',
|
||||
noToolsFound: 'Инструменты не найдены',
|
||||
tabTools: 'Инструменты',
|
||||
tabDocs: 'Документация',
|
||||
noReadme: 'Документация отсутствует',
|
||||
parseResultFailed: 'Не удалось разобрать результат теста',
|
||||
noResultReturned: 'Тест не вернул результат',
|
||||
getTaskFailed: 'Не удалось получить статус задачи',
|
||||
|
||||
@@ -2,7 +2,7 @@ const thTH = {
|
||||
sidebar: {
|
||||
home: 'หน้าแรก',
|
||||
extensions: 'ส่วนขยาย',
|
||||
installedPlugins: 'ปลั๊กอินที่ติดตั้ง',
|
||||
installedPlugins: 'ที่ติดตั้งแล้ว',
|
||||
pluginMarket: 'ตลาดปลั๊กอิน',
|
||||
mcpServers: 'เซิร์ฟเวอร์ MCP',
|
||||
addExtension: 'เพิ่มส่วนขยาย',
|
||||
@@ -755,6 +755,9 @@ const thTH = {
|
||||
toolsFound: 'เครื่องมือ',
|
||||
unknownError: 'ข้อผิดพลาดที่ไม่ทราบสาเหตุ',
|
||||
noToolsFound: 'ไม่พบเครื่องมือ',
|
||||
tabTools: 'เครื่องมือ',
|
||||
tabDocs: 'เอกสาร',
|
||||
noReadme: 'ไม่มีเอกสาร',
|
||||
parseResultFailed: 'ไม่สามารถแยกวิเคราะห์ผลการทดสอบได้',
|
||||
noResultReturned: 'การทดสอบไม่ส่งผลลัพธ์กลับมา',
|
||||
getTaskFailed: 'ไม่สามารถดึงสถานะงานได้',
|
||||
|
||||
@@ -2,7 +2,7 @@ const viVN = {
|
||||
sidebar: {
|
||||
home: 'Trang chủ',
|
||||
extensions: 'Tiện ích mở rộng',
|
||||
installedPlugins: 'Plugin đã cài đặt',
|
||||
installedPlugins: 'Đã cài đặt',
|
||||
pluginMarket: 'Chợ ứng dụng',
|
||||
mcpServers: 'Máy chủ MCP',
|
||||
addExtension: 'Thêm tiện ích mở rộng',
|
||||
@@ -769,6 +769,9 @@ const viVN = {
|
||||
toolsFound: 'công cụ',
|
||||
unknownError: 'Lỗi không xác định',
|
||||
noToolsFound: 'Không tìm thấy công cụ nào',
|
||||
tabTools: 'Công cụ',
|
||||
tabDocs: 'Tài liệu',
|
||||
noReadme: 'Không có tài liệu',
|
||||
parseResultFailed: 'Phân tích kết quả kiểm tra thất bại',
|
||||
noResultReturned: 'Kiểm tra không trả về kết quả',
|
||||
getTaskFailed: 'Lấy trạng thái tác vụ thất bại',
|
||||
|
||||
@@ -2,7 +2,7 @@ const zhHans = {
|
||||
sidebar: {
|
||||
home: '首页',
|
||||
extensions: '扩展',
|
||||
installedPlugins: '已安装扩展',
|
||||
installedPlugins: '已安装',
|
||||
pluginMarket: '扩展市场',
|
||||
mcpServers: 'MCP 服务器',
|
||||
addExtension: '添加扩展',
|
||||
@@ -737,6 +737,9 @@ const zhHans = {
|
||||
toolsFound: '个工具',
|
||||
unknownError: '未知错误',
|
||||
noToolsFound: '未找到任何工具',
|
||||
tabTools: '工具',
|
||||
tabDocs: '文档',
|
||||
noReadme: '暂无文档',
|
||||
parseResultFailed: '解析测试结果失败',
|
||||
noResultReturned: '测试未返回结果',
|
||||
getTaskFailed: '获取任务状态失败',
|
||||
|
||||
@@ -2,7 +2,7 @@ const zhHant = {
|
||||
sidebar: {
|
||||
home: '首頁',
|
||||
extensions: '擴展',
|
||||
installedPlugins: '已安裝外掛',
|
||||
installedPlugins: '已安裝',
|
||||
pluginMarket: '外掛市場',
|
||||
mcpServers: 'MCP 伺服器',
|
||||
addExtension: '添加擴展',
|
||||
@@ -736,6 +736,9 @@ const zhHant = {
|
||||
toolsFound: '個工具',
|
||||
unknownError: '未知錯誤',
|
||||
noToolsFound: '未找到任何工具',
|
||||
tabTools: '工具',
|
||||
tabDocs: '文件',
|
||||
noReadme: '暫無文件',
|
||||
parseResultFailed: '解析測試結果失敗',
|
||||
noResultReturned: '測試未返回結果',
|
||||
getTaskFailed: '獲取任務狀態失敗',
|
||||
|
||||
Reference in New Issue
Block a user