feat(ai-mcp): 优化外部客户端安装选择状态

This commit is contained in:
Syngnat
2026-06-09 12:11:03 +08:00
parent b5ba49ff8f
commit 17a3d72852
2 changed files with 38 additions and 12 deletions

View File

@@ -63,13 +63,17 @@ describe('AIMCPClientInstallPanel', () => {
);
expect(markup).toContain('这里是在把 GoNavi MCP 接入 Claude Code / Codex');
expect(markup).toContain('给外部工具调用');
expect(markup).toContain('这里的“安装”只会写入外部 CLI 的用户级 MCP 配置');
expect(markup).toContain('接入外部客户端');
expect(markup).toContain('目标客户端');
expect(markup).toContain('选择外部客户端(二选一)');
expect(markup).toContain('选择目标客户端');
expect(markup).toContain('写入接入配置');
expect(markup).toContain('重启目标客户端');
expect(markup).toContain('未接入');
expect(markup).toContain('需更新');
expect(markup).toContain('外部工具接入状态:已存在旧配置,需更新');
expect(markup).toContain('外部工具接入状态:未接入');
expect(markup).toContain('复制配置路径');
expect(markup).toContain('复制启动命令');
expect(markup).toContain('更新 Codex 接入配置');
@@ -127,7 +131,7 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('接入到 Claude Code');
expect(markup).toContain('安装到 Claude Code(外部工具)');
expect(markup).toContain('CLI 检测:未检测到 claude');
expect(markup).toContain('未检测到本机 claude 命令');
expect(markup).toContain('已接入');
@@ -182,8 +186,8 @@ describe('AIMCPClientInstallPanel', () => {
);
expect(markup).toContain('当前状态:已接入当前 GoNavi无需重复操作');
expect(markup).toContain('Claude Code 已接入当前 GoNavi');
expect(markup).toContain('下面的主按钮会自动禁用,避免重复操作');
expect(markup).toContain('Claude Code 已接入,无需重复安装');
expect(markup).toContain('下面的主按钮会自动禁用,避免重复写入');
});
it('prefers the client that already matches current GoNavi over another stale installed record', () => {

View File

@@ -56,6 +56,19 @@ const getStatusTone = (status: AIMCPClientInstallStatus | undefined, darkMode: b
};
};
const getInstallStateLabel = (status: AIMCPClientInstallStatus | undefined) => {
if (status?.matchesCurrent) {
return '外部工具接入状态:已接入当前 GoNavi';
}
if (status?.installed) {
return '外部工具接入状态:已存在旧配置,需更新';
}
if (hasStatusIssue(status)) {
return '外部工具接入状态:读取失败';
}
return '外部工具接入状态:未接入';
};
const resolveClientCommandName = (status: AIMCPClientInstallStatus | undefined) => {
const command = String(status?.clientCommand || '').trim();
if (command) {
@@ -116,12 +129,12 @@ const getSelectedClientStateLine = (status: AIMCPClientInstallStatus | undefined
const resolveActionLabel = (status: AIMCPClientInstallStatus | undefined) => {
const label = status?.displayName || '目标客户端';
if (status?.matchesCurrent) {
return `${label} 已接入当前 GoNavi`;
return `${label} 已接入,无需重复安装`;
}
if (status?.installed) {
return `更新 ${label} 接入配置`;
}
return `接入${label}`;
return `安装${label}(外部工具)`;
};
const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
@@ -165,17 +178,17 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
}}
>
<div style={{ fontWeight: 700, fontSize: 13, color: overlayTheme.titleText }}>
GoNavi MCP Claude Code / Codex GoNavi
GoNavi MCP Claude Code / Codex GoNavi
</div>
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
Claude Code Codex GoNavi GoNavi MCP GoNavi GoNavi
CLI MCP GoNavi MCP GoNavi GoNavi
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ fontWeight: 700, fontSize: 14, color: overlayTheme.titleText }}></div>
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
1 GoNavi MCP exe
Claude Code Codex 1 GoNavi
</div>
</div>
<div
@@ -228,8 +241,12 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ fontWeight: 700, fontSize: 13, color: overlayTheme.titleText }}></div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }}>
<div style={{ fontWeight: 700, fontSize: 13, color: overlayTheme.titleText }}></div>
<div
role="radiogroup"
aria-label="选择要安装 GoNavi MCP 的外部客户端"
style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 10 }}
>
{statuses.map((status) => {
const client = status.client === 'codex' ? 'codex' : 'claude-code';
const active = selectedClient === client;
@@ -238,6 +255,8 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
<button
key={status.client}
type="button"
role="radio"
aria-checked={active}
onClick={() => onSelectClient(client)}
style={{
padding: '14px 16px',
@@ -296,6 +315,9 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
<div style={{ fontSize: 12, color: overlayTheme.titleText, lineHeight: 1.7 }}>
{getClientOptionSummary(status)}
</div>
<div style={{ fontSize: 12, color: active ? overlayTheme.selectedText : overlayTheme.mutedText, lineHeight: 1.6, fontWeight: 700 }}>
{getInstallStateLabel(status)}
</div>
<div style={{ fontSize: 11, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
{active ? '当前已选中,将只对这个客户端执行写入或更新。' : '点击后切换到这个客户端。'}
</div>
@@ -404,7 +426,7 @@ const AIMCPClientInstallPanel: React.FC<AIMCPClientInstallPanelProps> = ({
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
{getClientDetectionSummary(selectedStatus)}
{' '}
GoNavi
GoNavi
</div>
<Button
type={selectedStatus?.matchesCurrent ? 'default' : 'primary'}