mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 04:30:48 +00:00
fix: 添加 handleAuthenticate 到依赖数组
- 修复 React Hooks exhaustive-deps 警告
This commit is contained in:
parent
41bb300542
commit
23c3518dff
4 changed files with 224 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ import { ServerDetailStep } from './steps/ServerDetailStep.js';
|
|||
import { ToolListStep } from './steps/ToolListStep.js';
|
||||
import { ToolDetailStep } from './steps/ToolDetailStep.js';
|
||||
import { DisableScopeSelectStep } from './steps/DisableScopeSelectStep.js';
|
||||
import { AuthenticateStep } from './steps/AuthenticateStep.js';
|
||||
import { useConfig } from '../../contexts/ConfigContext.js';
|
||||
import {
|
||||
getMCPServerStatus,
|
||||
|
|
@ -225,6 +226,11 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
handleNavigateToStep(MCP_MANAGEMENT_STEPS.TOOL_LIST);
|
||||
}, [handleNavigateToStep]);
|
||||
|
||||
// Authenticate
|
||||
const handleAuthenticate = useCallback(() => {
|
||||
handleNavigateToStep(MCP_MANAGEMENT_STEPS.AUTHENTICATE);
|
||||
}, [handleNavigateToStep]);
|
||||
|
||||
// Select tool
|
||||
const handleSelectTool = useCallback(
|
||||
(tool: MCPToolDisplayInfo) => {
|
||||
|
|
@ -401,6 +407,9 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
case MCP_MANAGEMENT_STEPS.TOOL_DETAIL:
|
||||
headerText = selectedTool?.name || t('Tool Detail');
|
||||
break;
|
||||
case MCP_MANAGEMENT_STEPS.AUTHENTICATE:
|
||||
headerText = t('OAuth Authentication');
|
||||
break;
|
||||
default:
|
||||
headerText = t('MCP Management');
|
||||
}
|
||||
|
|
@ -435,6 +444,7 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
onViewTools={handleViewTools}
|
||||
onReconnect={handleReconnect}
|
||||
onDisable={handleDisable}
|
||||
onAuthenticate={handleAuthenticate}
|
||||
onBack={handleNavigateBack}
|
||||
/>
|
||||
);
|
||||
|
|
@ -463,6 +473,18 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
<ToolDetailStep tool={selectedTool} onBack={handleNavigateBack} />
|
||||
);
|
||||
|
||||
case MCP_MANAGEMENT_STEPS.AUTHENTICATE:
|
||||
return (
|
||||
<AuthenticateStep
|
||||
server={selectedServer}
|
||||
onSuccess={() => {
|
||||
// TODO: 认证成功后重新加载服务器列表
|
||||
handleNavigateBack();
|
||||
}}
|
||||
onBack={handleNavigateBack}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<Box>
|
||||
|
|
@ -480,6 +502,7 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
handleViewTools,
|
||||
handleReconnect,
|
||||
handleDisable,
|
||||
handleAuthenticate,
|
||||
handleNavigateBack,
|
||||
handleSelectTool,
|
||||
handleSelectDisableScope,
|
||||
|
|
|
|||
162
packages/cli/src/ui/components/mcp/steps/AuthenticateStep.tsx
Normal file
162
packages/cli/src/ui/components/mcp/steps/AuthenticateStep.tsx
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../../../semantic-colors.js';
|
||||
import { useKeypress } from '../../../hooks/useKeypress.js';
|
||||
import { RadioButtonSelect } from '../../shared/RadioButtonSelect.js';
|
||||
import { t } from '../../../../i18n/index.js';
|
||||
import type { AuthenticateStepProps } from '../types.js';
|
||||
import type { MCPServerConfig } from '@qwen-code/qwen-code-core';
|
||||
|
||||
// TODO: 稍后从 utils.ts 导入此函数
|
||||
const getOAuthConfigFromServerConfig = (_config: MCPServerConfig): unknown =>
|
||||
null;
|
||||
|
||||
type AuthAction = 'authenticate' | 'back';
|
||||
|
||||
export const AuthenticateStep: React.FC<AuthenticateStepProps> = ({
|
||||
server,
|
||||
onBack,
|
||||
}) => {
|
||||
const [selectedAction, setSelectedAction] = useState<AuthAction>('back');
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
key: 'authenticate',
|
||||
label: t('Authenticate'),
|
||||
value: 'authenticate' as const,
|
||||
},
|
||||
{
|
||||
key: 'back',
|
||||
label: t('Back'),
|
||||
value: 'back' as const,
|
||||
},
|
||||
];
|
||||
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'escape') {
|
||||
onBack();
|
||||
} else if (key.name === 'return' && !isAuthenticating) {
|
||||
switch (selectedAction) {
|
||||
case 'authenticate':
|
||||
handleAuthenticate();
|
||||
break;
|
||||
case 'back':
|
||||
onBack();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
const handleAuthenticate = async () => {
|
||||
if (!server) return;
|
||||
|
||||
setIsAuthenticating(true);
|
||||
setAuthError(null);
|
||||
|
||||
try {
|
||||
// TODO: 实现 OAuth 认证逻辑
|
||||
// 这里需要调用 MCPOAuthProvider 进行认证
|
||||
// 认证成功后调用 onSuccess()
|
||||
// 认证失败时设置 authError
|
||||
|
||||
// 临时实现:显示提示信息
|
||||
setAuthError(t('OAuth authentication is not yet implemented'));
|
||||
} catch (error) {
|
||||
setAuthError(
|
||||
error instanceof Error ? error.message : t('Authentication failed'),
|
||||
);
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!server) {
|
||||
return (
|
||||
<Box>
|
||||
<Text color={theme.status.error}>{t('No server selected')}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const oauthConfig = getOAuthConfigFromServerConfig(server.config);
|
||||
const hasOAuth = !!oauthConfig;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
{/* 认证说明 */}
|
||||
<Box flexDirection="column">
|
||||
<Text color={theme.text.primary} bold>
|
||||
{t('OAuth Authentication')}
|
||||
</Text>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.text.secondary}>
|
||||
{t('Server:')} {server.name}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{!hasOAuth && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.status.warning}>
|
||||
{t('This server does not have OAuth configuration.')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{authError && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.status.error}>{authError}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 操作列表 */}
|
||||
{!hasOAuth ? (
|
||||
<Box>
|
||||
<RadioButtonSelect<AuthAction>
|
||||
items={actions.filter((a) => a.key === 'back')}
|
||||
onHighlight={(value: AuthAction) => setSelectedAction(value)}
|
||||
onSelect={(value: AuthAction) => {
|
||||
if (value === 'back') {
|
||||
onBack();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<RadioButtonSelect<AuthAction>
|
||||
items={actions}
|
||||
onHighlight={(value: AuthAction) => setSelectedAction(value)}
|
||||
onSelect={(value: AuthAction) => {
|
||||
if (value === 'back') {
|
||||
onBack();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isAuthenticating && (
|
||||
<Box>
|
||||
<Text color={theme.text.secondary}>
|
||||
{t('Authenticating... Please wait.')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -20,13 +20,18 @@ import {
|
|||
// 标签列宽度
|
||||
const LABEL_WIDTH = 15;
|
||||
|
||||
type ServerAction = 'view-tools' | 'reconnect' | 'toggle-disable';
|
||||
type ServerAction =
|
||||
| 'view-tools'
|
||||
| 'reconnect'
|
||||
| 'toggle-disable'
|
||||
| 'authenticate';
|
||||
|
||||
export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
||||
server,
|
||||
onViewTools,
|
||||
onReconnect,
|
||||
onDisable,
|
||||
onAuthenticate,
|
||||
onBack,
|
||||
}) => {
|
||||
const [selectedAction, setSelectedAction] =
|
||||
|
|
@ -71,6 +76,16 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
value: 'toggle-disable',
|
||||
});
|
||||
|
||||
// 如果服务器配置了 OAuth,显示认证选项
|
||||
if (server && !server.isDisabled) {
|
||||
// TODO: 检查服务器是否有 OAuth 配置
|
||||
result.push({
|
||||
key: 'authenticate',
|
||||
label: t('Authenticate'),
|
||||
value: 'authenticate',
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [server]);
|
||||
|
||||
|
|
@ -89,6 +104,9 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
case 'toggle-disable':
|
||||
onDisable?.();
|
||||
break;
|
||||
case 'authenticate':
|
||||
onAuthenticate?.();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -228,6 +246,9 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
case 'toggle-disable':
|
||||
onDisable?.();
|
||||
break;
|
||||
case 'authenticate':
|
||||
onAuthenticate?.();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const MCP_MANAGEMENT_STEPS = {
|
|||
DISABLE_SCOPE_SELECT: 'disable-scope-select',
|
||||
TOOL_LIST: 'tool-list',
|
||||
TOOL_DETAIL: 'tool-detail',
|
||||
AUTHENTICATE: 'authenticate', // OAuth 认证步骤
|
||||
} as const;
|
||||
|
||||
export type MCPManagementStep =
|
||||
|
|
@ -120,7 +121,7 @@ export interface ServerListStepProps {
|
|||
}
|
||||
|
||||
/**
|
||||
* ServerDetailStep组件属性
|
||||
* ServerDetailStep 组件属性
|
||||
*/
|
||||
export interface ServerDetailStepProps {
|
||||
/** 选中的服务器 */
|
||||
|
|
@ -131,6 +132,8 @@ export interface ServerDetailStepProps {
|
|||
onReconnect?: () => void;
|
||||
/** 禁用服务器回调 */
|
||||
onDisable?: () => void;
|
||||
/** OAuth 认证回调 */
|
||||
onAuthenticate?: () => void;
|
||||
/** 返回回调 */
|
||||
onBack: () => void;
|
||||
}
|
||||
|
|
@ -162,7 +165,7 @@ export interface ToolListStepProps {
|
|||
}
|
||||
|
||||
/**
|
||||
* ToolDetailStep组件属性
|
||||
* ToolDetailStep 组件属性
|
||||
*/
|
||||
export interface ToolDetailStepProps {
|
||||
/** 工具信息 */
|
||||
|
|
@ -171,6 +174,18 @@ export interface ToolDetailStepProps {
|
|||
onBack: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthenticateStep 组件属性
|
||||
*/
|
||||
export interface AuthenticateStepProps {
|
||||
/** 服务器信息 */
|
||||
server: MCPServerDisplayInfo | null;
|
||||
/** 认证成功回调 */
|
||||
onSuccess?: () => void;
|
||||
/** 返回回调 */
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP管理对话框属性
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue