Files
MyGoNavi/frontend/src/components/ai/AIMCPQuickAddServerPanel.tsx
Syngnat 4cac8ef3c9 feat(mcp): 优化新增服务模板入口
- 模板入口移入一行命令快速新增面板

- 增加启动命令预览,降低 command 和 args 拆分成本

- 移除设置页重复模板区块并补充交互测试
2026-06-12 01:41:31 +08:00

149 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { Button, Input } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { AIMCPServerConfig } from '../../types';
import {
parseMCPCommandDraft,
type ParseMCPCommandDraftResult,
} from '../../utils/mcpCommandDraft';
import {
buildMCPLaunchPreview,
MCP_COMMAND_PARSE_EXAMPLE,
} from '../../utils/mcpServerGuidance';
import { buildMCPQuickAddServerSeed } from '../../utils/mcpServerDraftSeed';
import { MCP_SERVER_DRAFT_TEMPLATES } from '../../utils/mcpServerTemplates';
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
import AIMCPCommandDraftPreview from './AIMCPCommandDraftPreview';
import { buildMCPHintStyle, mcpLabelStyle } from './AIMCPHelpBlock';
interface AIMCPQuickAddServerPanelProps {
cardBg: string;
cardBorder: string;
inputBg: string;
darkMode: boolean;
overlayTheme: OverlayWorkbenchTheme;
onAddServer: (seed?: Partial<AIMCPServerConfig>) => void;
}
const renderParseSummary = (
rawCommandDraft: string,
parsedCommandDraft: ParseMCPCommandDraftResult,
overlayTheme: OverlayWorkbenchTheme,
) => {
if (!rawCommandDraft.trim()) {
return '支持带引号路径、带空格参数,以及 KEY=VALUE / $env:KEY=VALUE; / set KEY=VALUE && 环境变量前缀。';
}
if (!parsedCommandDraft.ok || !parsedCommandDraft.draft) {
return parsedCommandDraft.error || '完整命令解析失败,请检查命令格式。';
}
const envCount = Object.keys(parsedCommandDraft.draft.env || {}).length;
return (
<span style={{ color: overlayTheme.mutedText }}>
{parsedCommandDraft.draft.command} {parsedCommandDraft.draft.args.length} {envCount}
</span>
);
};
const AIMCPQuickAddServerPanel: React.FC<AIMCPQuickAddServerPanelProps> = ({
cardBg,
cardBorder,
inputBg,
darkMode,
overlayTheme,
onAddServer,
}) => {
const [rawCommandDraft, setRawCommandDraft] = React.useState('');
const parsedCommandDraft = parseMCPCommandDraft(rawCommandDraft);
const handleAddFromCommand = () => {
if (!parsedCommandDraft.ok || !parsedCommandDraft.draft) {
return;
}
onAddServer(buildMCPQuickAddServerSeed(parsedCommandDraft.draft));
setRawCommandDraft('');
};
return (
<div
style={{
padding: '14px 16px',
borderRadius: 14,
border: `1px solid ${cardBorder}`,
background: cardBg,
display: 'flex',
flexDirection: 'column',
gap: 10,
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ ...mcpLabelStyle, color: overlayTheme.titleText, fontSize: 14 }}></div>
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
README GoNavi commandargs env MCP 稿
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ ...mcpLabelStyle, color: overlayTheme.titleText }}></div>
<div style={buildMCPHintStyle(overlayTheme.mutedText)}>
command args 稿 GoNavi
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 10 }}>
{MCP_SERVER_DRAFT_TEMPLATES.map((template) => (
<button
key={template.key}
type="button"
onClick={() => onAddServer(template.seed)}
style={{
textAlign: 'left',
padding: '12px 13px',
borderRadius: 12,
border: `1px solid ${cardBorder}`,
background: darkMode ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.72)',
color: overlayTheme.titleText,
cursor: 'pointer',
}}
>
<div style={{ fontSize: 13, fontWeight: 700 }}>{template.title}</div>
<div style={{ marginTop: 4, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>{template.description}</div>
<code style={{ display: 'block', marginTop: 8, fontFamily: 'var(--gn-font-mono)', fontSize: 12, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', color: overlayTheme.titleText }}>
{buildMCPLaunchPreview(String(template.seed.command || ''), template.seed.args)}
</code>
<div style={{ marginTop: 6, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>{template.detail}</div>
</button>
))}
</div>
</div>
<Input.TextArea
rows={2}
value={rawCommandDraft}
onChange={(event) => setRawCommandDraft(event.target.value)}
placeholder={`粘贴完整命令,例如:\n${MCP_COMMAND_PARSE_EXAMPLE}`}
style={{ borderRadius: 10, background: inputBg, border: `1px solid ${cardBorder}`, fontFamily: 'var(--gn-font-mono)' }}
/>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div style={{ ...buildMCPHintStyle(parsedCommandDraft.ok || !rawCommandDraft.trim() ? overlayTheme.mutedText : '#dc2626') }}>
{renderParseSummary(rawCommandDraft, parsedCommandDraft, overlayTheme)}
</div>
<Button
icon={<PlusOutlined />}
onClick={handleAddFromCommand}
disabled={!parsedCommandDraft.ok}
style={{ borderRadius: 10, fontWeight: 600 }}
>
稿
</Button>
</div>
{parsedCommandDraft.ok && parsedCommandDraft.draft && rawCommandDraft.trim() && (
<AIMCPCommandDraftPreview
draft={parsedCommandDraft.draft}
darkMode={darkMode}
overlayTheme={overlayTheme}
cardBorder={cardBorder}
/>
)}
</div>
);
};
export default AIMCPQuickAddServerPanel;