mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-10 15:56:03 +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>
|
||||
|
||||
Reference in New Issue
Block a user