Files
MyGoNavi/frontend/src/components/ai/AIMCPHTTPServerPanel.tsx
Syngnat c3a3387ee3 feat(ai): 优化 AI 对话体验与 MCP 接入配置
- AI 请求:增强 OpenAI 兼容接口降级逻辑,文本模型自动省略图片并在 400 场景重试
- MCP 接入:支持自定义 HTTP 服务监听地址、端口和 Authorization Bearer Token
- MCP 生命周期:停止服务后保留授权信息,并将主动关闭子进程视为正常停止
- 交互优化:移除 AI 对话导出入口,支持关闭常驻状态提示并收敛设置弹窗 toast 宽度
- UI 调整:优化 AI 输入框边框、聚焦态和 Authorization 运行中只读可查看体验
- 测试覆盖:补充 OpenAI 降级、MCP HTTP、AI Header 和设置面板相关用例
2026-06-12 14:51:37 +08:00

156 lines
5.4 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, Switch, Tag } from 'antd';
import { CopyOutlined } from '@ant-design/icons';
import type { AIMCPHTTPServerStatus } from '../../types';
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
export interface AIMCPHTTPServerDraft {
addr: string;
path: string;
authorizationHeader: string;
}
export interface AIMCPHTTPServerPanelProps {
status: AIMCPHTTPServerStatus;
draft: AIMCPHTTPServerDraft;
loading: boolean;
cardBg: string;
cardBorder: string;
darkMode: boolean;
overlayTheme: OverlayWorkbenchTheme;
onDraftChange: (patch: Partial<AIMCPHTTPServerDraft>) => void;
onToggle: (checked: boolean) => void;
onCopyURL: () => void;
onCopyAuthorization: () => void;
}
const AIMCPHTTPServerPanel: React.FC<AIMCPHTTPServerPanelProps> = ({
status,
draft,
loading,
cardBg,
cardBorder,
darkMode,
overlayTheme,
onDraftChange,
onToggle,
onCopyURL,
onCopyAuthorization,
}) => {
const running = status?.running === true;
const url = String(status?.url || '').trim();
const authorizationHeader = String(status?.authorizationHeader || '').trim();
const inputStyle: React.CSSProperties = {
borderRadius: 10,
background: darkMode ? 'rgba(15,23,42,0.82)' : '#fff',
};
return (
<div
style={{
padding: '14px 16px',
borderRadius: 14,
border: `1px solid ${cardBorder}`,
background: cardBg,
display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start' }}>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<div style={{ fontWeight: 800, fontSize: 14, color: overlayTheme.titleText }}>GoNavi MCP HTTP </div>
<Tag color={running ? 'success' : 'default'} style={{ marginInlineEnd: 0 }}>
{running ? '已启动' : '未启动'}
</Tag>
<Tag color="blue" style={{ marginInlineEnd: 0 }}>
schema-only
</Tag>
</div>
<div style={{ marginTop: 6, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
OpenClawHermans Agent 使 DDL
</div>
</div>
<Switch
checked={running}
loading={loading}
onChange={onToggle}
checkedChildren="开"
unCheckedChildren="关"
/>
</div>
<div
style={{
borderRadius: 12,
border: `1px solid ${cardBorder}`,
background: darkMode ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.72)',
padding: '10px 12px',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<span style={{ fontSize: 11, fontWeight: 700, color: overlayTheme.mutedText }}> / </span>
<Input
size="small"
value={draft.addr}
disabled={running || loading}
placeholder="127.0.0.1:8765"
onChange={(event) => onDraftChange({ addr: event.target.value })}
style={inputStyle}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<span style={{ fontSize: 11, fontWeight: 700, color: overlayTheme.mutedText }}>Authorization</span>
<Input.Password
size="small"
value={draft.authorizationHeader}
disabled={loading}
readOnly={running || loading}
placeholder="Bearer gnv_xxx留空自动生成"
autoComplete="off"
onChange={(event) => onDraftChange({ authorizationHeader: event.target.value })}
style={inputStyle}
/>
</div>
</div>
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
{running
? status.message || '服务运行中,可把 URL 和 Authorization Header 配置到远程 MCP 客户端。'
: '可自定义本机监听端口和 Bearer Token留空 Authorization 时启动会自动生成随机 Token。'}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
<code
style={{
fontSize: 12,
color: overlayTheme.titleText,
background: darkMode ? 'rgba(0,0,0,0.22)' : 'rgba(0,0,0,0.04)',
borderRadius: 8,
padding: '4px 7px',
}}
>
{url || 'http://127.0.0.1:8765/mcp'}
</code>
<Button size="small" icon={<CopyOutlined />} disabled={!running || !url} onClick={onCopyURL}>
URL
</Button>
<Button
size="small"
icon={<CopyOutlined />}
disabled={!running || !authorizationHeader}
onClick={onCopyAuthorization}
>
Authorization
</Button>
</div>
</div>
</div>
);
};
export default AIMCPHTTPServerPanel;