Files
MyGoNavi/frontend/src/components/ai/AIChatHeader.tsx
Syngnat 24d9db4c51 feat(ui): 完成新版 UI 全量改造
- 整体布局:按新版 UI 重构左侧导航、对象树、连接分组和右键菜单体系

- 数据视图:优化 DDL 侧栏、横向滚动、筛选输入、编辑入口和虚拟表格体验

- AI 面板:重构新版入口、输入区、模型选择、快捷键和悬浮布局

- 标签与快捷键:补齐 Tab 悬浮信息、复制交互和 Mac/Windows 快捷键配置

- 工程质量:新增 v2 主题样式、菜单组件、外观工具和回归测试覆盖
2026-05-22 17:41:06 +08:00

152 lines
7.3 KiB
TypeScript

import React from 'react';
import { Button, Tooltip } from 'antd';
import { HistoryOutlined, RobotOutlined, ClearOutlined, SettingOutlined, CloseOutlined, ExportOutlined, PlusOutlined, ThunderboltOutlined } from '@ant-design/icons';
import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
import type { AIChatMessage } from '../../types';
interface AIChatHeaderProps {
darkMode: boolean;
mutedColor: string;
textColor: string;
overlayTheme: OverlayWorkbenchTheme;
isV2Ui?: boolean;
onHistoryClick: () => void;
onClear: () => void;
onSettingsClick: () => void;
onClose: () => void;
messages?: AIChatMessage[];
sessionTitle?: string;
activeMode?: 'chat' | 'insights' | 'history';
onModeChange?: (mode: 'chat' | 'insights' | 'history') => void;
}
const exportToMarkdown = (messages: AIChatMessage[], title: string) => {
const lines: string[] = [`# ${title}`, '', `> 导出时间:${new Date().toLocaleString()}`, ''];
messages.forEach(msg => {
const role = msg.role === 'user' ? '👤 You' : '🤖 GoNavi AI';
lines.push(`## ${role}`);
lines.push('');
lines.push(msg.content);
lines.push('');
lines.push('---');
lines.push('');
});
const blob = new Blob([lines.join('\n')], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title.replace(/[/\\?%*:|"<>]/g, '-')}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
export const AIChatHeader: React.FC<AIChatHeaderProps> = ({
darkMode, mutedColor, textColor, overlayTheme,
isV2Ui = false,
onHistoryClick, onClear, onSettingsClick, onClose,
messages = [], sessionTitle = '新对话',
activeMode = 'chat',
onModeChange,
}) => {
if (!isV2Ui) {
return (
<div className="ai-chat-header" style={{ borderBottom: 'none', padding: '10px 16px', background: darkMode ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.01)' }}>
<div className="ai-chat-header-left" style={{ gap: 8 }}>
<Tooltip title="历史会话">
<Button type="text" size="small" icon={<HistoryOutlined />} onClick={onHistoryClick} style={{ color: mutedColor }} />
</Tooltip>
<div className="ai-logo" style={{ background: overlayTheme.iconBg, color: overlayTheme.iconColor, display: 'flex', alignItems: 'center', justifyContent: 'center', width: 20, height: 20, borderRadius: 6, fontSize: 12 }}>
<RobotOutlined />
</div>
<span className="ai-title" style={{ color: textColor, fontSize: 13, fontWeight: 600 }}>GoNavi AI</span>
</div>
<div className="ai-chat-header-right">
{messages.length > 0 && (
<Tooltip title="导出为 Markdown">
<Button type="text" size="small" icon={<ExportOutlined />} onClick={() => exportToMarkdown(messages, sessionTitle)} style={{ color: mutedColor }} />
</Tooltip>
)}
<Tooltip title="新对话 (清空当前)">
<Button type="text" size="small" icon={<ClearOutlined />} onClick={onClear} style={{ color: mutedColor }} />
</Tooltip>
<Tooltip title="AI 设置">
<Button type="text" size="small" icon={<SettingOutlined />} onClick={onSettingsClick} style={{ color: mutedColor }} />
</Tooltip>
<Tooltip title="关闭面板">
<Button type="text" size="small" icon={<CloseOutlined />} onClick={onClose} style={{ color: mutedColor }} />
</Tooltip>
</div>
</div>
);
}
return (
<div className="ai-chat-header gn-v2-ai-header" style={{ borderBottom: 'none', padding: '10px 16px', background: darkMode ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.01)' }}>
<div className="gn-v2-ai-header-top">
<div className="ai-chat-header-left gn-v2-ai-brand" style={{ gap: 8 }}>
<div className="ai-logo" style={{ background: overlayTheme.iconBg, color: overlayTheme.iconColor, display: 'flex', alignItems: 'center', justifyContent: 'center', width: 20, height: 20, borderRadius: 6, fontSize: 12 }}>
<RobotOutlined />
</div>
<div className="ai-title-stack">
<span className="ai-title" style={{ color: textColor, fontSize: 13, fontWeight: 600 }}>GoNavi AI</span>
<small>{sessionTitle} · </small>
</div>
<span className="gn-v2-ai-provider-badge">BETA</span>
</div>
<div className="ai-chat-header-right gn-v2-ai-header-actions">
<Tooltip title="新对话">
<Button type="text" size="small" icon={<PlusOutlined />} onClick={onClear} style={{ color: mutedColor }} />
</Tooltip>
<Tooltip title="历史会话">
<Button type="text" size="small" icon={<HistoryOutlined />} onClick={onHistoryClick} style={{ color: mutedColor }} />
</Tooltip>
<Tooltip title="AI 设置">
<Button type="text" size="small" icon={<SettingOutlined />} onClick={onSettingsClick} style={{ color: mutedColor }} />
</Tooltip>
<Tooltip title="关闭面板">
<Button type="text" size="small" icon={<CloseOutlined />} onClick={onClose} style={{ color: mutedColor }} />
</Tooltip>
</div>
</div>
<div className="gn-v2-ai-mode-tabs" aria-label="AI 工作模式">
<button
type="button"
className={activeMode === 'chat' ? 'is-active' : undefined}
onClick={() => onModeChange?.('chat')}
>
<RobotOutlined />
<span></span>
</button>
<button
type="button"
className={activeMode === 'insights' ? 'is-active' : undefined}
onClick={() => onModeChange?.('insights')}
>
<ThunderboltOutlined />
<span></span>
</button>
<button
type="button"
className={activeMode === 'history' ? 'is-active' : undefined}
onClick={() => onModeChange?.('history')}
>
<HistoryOutlined />
<span></span>
</button>
</div>
{messages.length > 0 && (
<div className="gn-v2-ai-session-row">
<button type="button" className="gn-v2-ai-export-button" onClick={() => exportToMarkdown(messages, sessionTitle)}>
<ExportOutlined />
<span></span>
</button>
</div>
)}
</div>
);
};