mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 02:19:58 +08:00
✨ feat(mcp): 支持一键补齐缺失启动参数
- 在 MCP 参数提示中按当前 command 生成缺失必填参数 - 支持一键追加 docker、npx、python 等常见启动参数 - 增加组件交互测试覆盖参数补齐行为
This commit is contained in:
52
frontend/src/components/ai/AIMCPArgumentHints.test.tsx
Normal file
52
frontend/src/components/ai/AIMCPArgumentHints.test.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
|
||||
import AIMCPArgumentHints from './AIMCPArgumentHints';
|
||||
|
||||
const flattenRendererText = (node: any): string => {
|
||||
if (node == null || typeof node === 'boolean') {
|
||||
return '';
|
||||
}
|
||||
if (typeof node === 'string' || typeof node === 'number') {
|
||||
return String(node);
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
return node.map((item) => flattenRendererText(item)).join('');
|
||||
}
|
||||
return flattenRendererText(node.children ?? node.props?.children);
|
||||
};
|
||||
|
||||
describe('AIMCPArgumentHints', () => {
|
||||
it('can append missing required args from command-specific hints', async () => {
|
||||
const onArgsChange = vi.fn();
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<AIMCPArgumentHints
|
||||
command="docker"
|
||||
args={['run', '--rm']}
|
||||
onArgsChange={onArgsChange}
|
||||
cardBorder="rgba(0,0,0,0.08)"
|
||||
darkMode={false}
|
||||
overlayTheme={buildOverlayWorkbenchTheme(false)}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const buttons = renderer.root.findAll(
|
||||
(node) => node.type === 'button' && flattenRendererText(node).includes('一键补齐缺失必填参数'),
|
||||
);
|
||||
|
||||
expect(buttons.length).toBe(1);
|
||||
expect(flattenRendererText(buttons[0])).toContain('-i / mcp/server-fetch:latest');
|
||||
|
||||
await act(async () => {
|
||||
buttons[0].props.onClick();
|
||||
});
|
||||
|
||||
expect(onArgsChange).toHaveBeenCalledWith(['run', '--rm', '-i', 'mcp/server-fetch:latest']);
|
||||
});
|
||||
});
|
||||
@@ -2,19 +2,31 @@ import React from 'react';
|
||||
|
||||
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
|
||||
import { buildMCPArgumentHintProfile } from '../../utils/mcpArgumentHints';
|
||||
import { splitShellLikeCommand } from '../../utils/mcpCommandDraft';
|
||||
import { buildMCPHintStyle, mcpLabelStyle } from './AIMCPHelpBlock';
|
||||
|
||||
interface AIMCPArgumentHintsProps {
|
||||
command: string;
|
||||
args?: string[];
|
||||
onArgsChange?: (args: string[]) => void;
|
||||
cardBorder: string;
|
||||
darkMode: boolean;
|
||||
overlayTheme: OverlayWorkbenchTheme;
|
||||
}
|
||||
|
||||
const buildMissingRequiredArgs = (
|
||||
profile: NonNullable<ReturnType<typeof buildMCPArgumentHintProfile>>,
|
||||
): string[] =>
|
||||
profile.steps
|
||||
.filter((step) => step.required && !step.satisfied)
|
||||
.flatMap((step) => splitShellLikeCommand(step.example).tokens)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const AIMCPArgumentHints: React.FC<AIMCPArgumentHintsProps> = ({
|
||||
command,
|
||||
args,
|
||||
onArgsChange,
|
||||
cardBorder,
|
||||
darkMode,
|
||||
overlayTheme,
|
||||
@@ -23,6 +35,8 @@ const AIMCPArgumentHints: React.FC<AIMCPArgumentHintsProps> = ({
|
||||
if (!profile) {
|
||||
return null;
|
||||
}
|
||||
const missingRequiredArgs = buildMissingRequiredArgs(profile);
|
||||
const canApplyMissingArgs = Boolean(onArgsChange && missingRequiredArgs.length > 0);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -74,6 +88,25 @@ const AIMCPArgumentHints: React.FC<AIMCPArgumentHintsProps> = ({
|
||||
必填参数看起来已经齐了,测试失败时再对照 README 检查业务参数和环境变量。
|
||||
</div>
|
||||
)}
|
||||
{canApplyMissingArgs ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onArgsChange?.([...(args || []), ...missingRequiredArgs])}
|
||||
style={{
|
||||
alignSelf: 'flex-start',
|
||||
padding: '5px 11px',
|
||||
borderRadius: 999,
|
||||
border: `1px solid ${cardBorder}`,
|
||||
background: darkMode ? 'rgba(245,158,11,0.14)' : 'rgba(245,158,11,0.10)',
|
||||
color: '#b45309',
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
一键补齐缺失必填参数:{missingRequiredArgs.join(' / ')}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -134,6 +134,7 @@ const AIMCPServerFormPanel: React.FC<AIMCPServerFormPanelProps> = ({
|
||||
<AIMCPArgumentHints
|
||||
command={server.command}
|
||||
args={server.args}
|
||||
onArgsChange={(args) => onChange({ args })}
|
||||
cardBorder={cardBorder}
|
||||
darkMode={darkMode}
|
||||
overlayTheme={overlayTheme}
|
||||
|
||||
Reference in New Issue
Block a user