feat(web): split MCP resources into tab

This commit is contained in:
Hyu
2026-06-28 01:42:08 +08:00
parent 097219556c
commit 5ab237138e
9 changed files with 75 additions and 15 deletions
@@ -246,7 +246,6 @@ function ToolsList({ tools, t }: { tools: MCPTool[]; t: TFunction }) {
);
}
// Resources list component
function ResourcesList({
resources,
serverName,
@@ -285,7 +284,7 @@ function ResourcesList({
};
return (
<div className="space-y-2 max-h-[400px] overflow-y-auto">
<div className="space-y-2 pb-6">
{resources.map((resource, index) => (
<Card key={index} className="py-3 shadow-none">
<CardHeader
@@ -336,15 +335,19 @@ function ResourcesList({
);
}
type RuntimePanelContent = 'all' | 'tools' | 'resources';
function RuntimePanel({
mcpTesting,
runtimeInfo,
serverName,
content = 'all',
t,
}: {
mcpTesting: boolean;
runtimeInfo: MCPServerRuntimeInfo | null;
serverName: string;
content?: RuntimePanelContent;
t: TFunction;
}) {
// Show tools whenever we have runtime info — either an edit-mode server or a
@@ -353,7 +356,9 @@ function RuntimePanel({
if (!runtimeInfo) {
return (
<div className="flex min-h-[280px] items-center justify-center rounded-lg border border-dashed text-sm text-muted-foreground">
{t('mcp.noToolsFound')}
{content === 'resources'
? t('mcp.noResourcesFound')
: t('mcp.noToolsFound')}
</div>
);
}
@@ -362,6 +367,14 @@ function RuntimePanel({
!mcpTesting && runtimeInfo.status === MCPSessionStatus.CONNECTED;
const tools = runtimeInfo.tools || [];
const resources = runtimeInfo.resources || [];
const showTools = content === 'all' || content === 'tools';
const showResources = content === 'all' || content === 'resources';
const empty =
content === 'tools'
? tools.length === 0
: content === 'resources'
? resources.length === 0
: tools.length === 0 && resources.length === 0;
return (
<section className="space-y-4">
@@ -371,20 +384,26 @@ function RuntimePanel({
</div>
)}
{isConnected && tools.length > 0 && <ToolsList tools={tools} t={t} />}
{isConnected && showTools && tools.length > 0 && (
<ToolsList tools={tools} t={t} />
)}
{isConnected && resources.length > 0 && (
{isConnected && showResources && resources.length > 0 && (
<div className="space-y-2">
<div className="text-sm font-medium">
{t('mcp.resourceCount', { count: resources.length })}
</div>
{content === 'all' && (
<div className="text-sm font-medium">
{t('mcp.resourceCount', { count: resources.length })}
</div>
)}
<ResourcesList resources={resources} serverName={serverName} t={t} />
</div>
)}
{isConnected && tools.length === 0 && resources.length === 0 && (
{isConnected && empty && (
<div className="flex min-h-[220px] items-center justify-center rounded-lg border border-dashed text-sm text-muted-foreground">
{t('mcp.noToolsFound')}
{content === 'resources'
? t('mcp.noResourcesFound')
: t('mcp.noToolsFound')}
</div>
)}
</section>
@@ -1141,16 +1160,20 @@ 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).
// Tools/resources lists 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
// Counts live in the tab labels (only when connected); the panel
// body itself no longer repeats a title/subtitle.
const toolsConnected =
const runtimeConnected =
!mcpTesting && runtimeInfo?.status === MCPSessionStatus.CONNECTED;
const toolsCount = runtimeInfo?.tools?.length ?? 0;
const toolsTabLabel = toolsConnected
const resourcesCount = runtimeInfo?.resources?.length ?? 0;
const toolsTabLabel = runtimeConnected
? `${t('mcp.tabTools')} ${toolsCount}`
: t('mcp.tabTools');
const resourcesTabLabel = runtimeConnected
? `${t('mcp.tabResources')} ${resourcesCount}`
: t('mcp.tabResources');
const detailPanel = isEditMode ? (
<Tabs defaultValue="tools" className="flex h-full min-h-0 flex-col">
@@ -1161,6 +1184,9 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
<TabsTrigger value="tools" className="flex-none px-4">
{toolsTabLabel}
</TabsTrigger>
<TabsTrigger value="resources" className="flex-none px-4">
{resourcesTabLabel}
</TabsTrigger>
</TabsList>
<TabsContent value="docs" className="mt-4 min-h-0 flex-1 overflow-y-auto">
<MCPReadme readme={readme} />
@@ -1169,7 +1195,25 @@ const MCPForm = forwardRef<MCPFormHandle, MCPFormProps>(function MCPForm(
value="tools"
className="mt-4 min-h-0 flex-1 overflow-y-auto"
>
{runtimePanel}
<RuntimePanel
mcpTesting={mcpTesting}
runtimeInfo={runtimeInfo}
serverName={form.getValues('name')}
content="tools"
t={t}
/>
</TabsContent>
<TabsContent
value="resources"
className="mt-4 min-h-0 flex-1 overflow-y-auto"
>
<RuntimePanel
mcpTesting={mcpTesting}
runtimeInfo={runtimeInfo}
serverName={form.getValues('name')}
content="resources"
t={t}
/>
</TabsContent>
</Tabs>
) : (
+2
View File
@@ -804,7 +804,9 @@ const enUS = {
toolsFound: 'tools',
unknownError: 'Unknown error',
noToolsFound: 'No tools found',
noResourcesFound: 'No resources found',
tabTools: 'Tools',
tabResources: 'Resources',
tabDocs: 'Docs',
noReadme: 'No documentation available',
parseResultFailed: 'Failed to parse test result',
+2
View File
@@ -818,7 +818,9 @@ const esES = {
toolsFound: 'herramientas',
unknownError: 'Error desconocido',
noToolsFound: 'No se encontraron herramientas',
noResourcesFound: 'No se encontraron recursos',
tabTools: 'Herramientas',
tabResources: 'Recursos',
tabDocs: 'Documentación',
noReadme: 'No hay documentación disponible',
parseResultFailed: 'Error al analizar el resultado de la prueba',
+2
View File
@@ -810,7 +810,9 @@ const jaJP = {
toolsFound: '個のツール',
unknownError: '不明なエラー',
noToolsFound: 'ツールが見つかりません',
noResourcesFound: 'リソースが見つかりません',
tabTools: 'ツール',
tabResources: 'リソース',
tabDocs: 'ドキュメント',
noReadme: 'ドキュメントがありません',
parseResultFailed: 'テスト結果の解析に失敗しました',
+2
View File
@@ -815,7 +815,9 @@ const ruRU = {
toolsFound: 'инструментов',
unknownError: 'Неизвестная ошибка',
noToolsFound: 'Инструменты не найдены',
noResourcesFound: 'Ресурсы не найдены',
tabTools: 'Инструменты',
tabResources: 'Ресурсы',
tabDocs: 'Документация',
noReadme: 'Документация отсутствует',
parseResultFailed: 'Не удалось разобрать результат теста',
+2
View File
@@ -793,7 +793,9 @@ const thTH = {
toolsFound: 'เครื่องมือ',
unknownError: 'ข้อผิดพลาดที่ไม่ทราบสาเหตุ',
noToolsFound: 'ไม่พบเครื่องมือ',
noResourcesFound: 'ไม่พบทรัพยากร',
tabTools: 'เครื่องมือ',
tabResources: 'ทรัพยากร',
tabDocs: 'เอกสาร',
noReadme: 'ไม่มีเอกสาร',
parseResultFailed: 'ไม่สามารถแยกวิเคราะห์ผลการทดสอบได้',
+2
View File
@@ -808,7 +808,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',
noResourcesFound: 'Không tìm thấy tài nguyên nào',
tabTools: 'Công cụ',
tabResources: 'Tài nguyên',
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',
+2
View File
@@ -771,7 +771,9 @@ const zhHans = {
toolsFound: '个工具',
unknownError: '未知错误',
noToolsFound: '未找到任何工具',
noResourcesFound: '未找到任何资源',
tabTools: '工具',
tabResources: '资源',
tabDocs: '文档',
noReadme: '暂无文档',
parseResultFailed: '解析测试结果失败',
+2
View File
@@ -770,7 +770,9 @@ const zhHant = {
toolsFound: '個工具',
unknownError: '未知錯誤',
noToolsFound: '未找到任何工具',
noResourcesFound: '未找到任何資源',
tabTools: '工具',
tabResources: '資源',
tabDocs: '文件',
noReadme: '暫無文件',
parseResultFailed: '解析測試結果失敗',