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