114 lines
3.9 KiB
TypeScript
114 lines
3.9 KiB
TypeScript
import { ApiOutlined, DeleteOutlined, EditOutlined, SettingOutlined, EllipsisOutlined, HistoryOutlined, LogoutOutlined } from '@ant-design/icons';
|
|
import { Button, Dropdown, Modal, Switch } from 'antd';
|
|
import type { Agent } from '../../../api';
|
|
import './ChatHeader.css';
|
|
|
|
function formatAgentModel(raw: string | null | undefined) {
|
|
const s = String(raw ?? '').trim();
|
|
if (!s) return '默认模型';
|
|
if (s.startsWith('[') || s.startsWith('{')) {
|
|
try {
|
|
const parsed = JSON.parse(s);
|
|
if (Array.isArray(parsed)) {
|
|
const names = parsed
|
|
.map((x: any) => String(x?.name || x?.model || x?.id || '').trim())
|
|
.filter(Boolean);
|
|
if (names.length > 2) {
|
|
return names.slice(0, 2).join(', ') + '...';
|
|
}
|
|
if (names.length) return names.join(', ');
|
|
} else if (parsed && typeof parsed === 'object') {
|
|
const name = String((parsed as any).name || (parsed as any).model || (parsed as any).id || '').trim();
|
|
if (name) return name;
|
|
}
|
|
} catch {}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/');
|
|
|
|
export default function ChatHeader(props: {
|
|
agent: Agent;
|
|
useStream: boolean;
|
|
setUseStream: (v: boolean) => void;
|
|
onOpenHistory: () => void;
|
|
onOpenParams: () => void;
|
|
onOpenMcp: () => void;
|
|
onManageAgent: () => void;
|
|
onClear: () => void;
|
|
onLogout?: () => void;
|
|
}) {
|
|
const { agent, useStream, setUseStream, onOpenHistory, onOpenParams, onOpenMcp, onManageAgent, onClear, onLogout } = props;
|
|
const modelText = formatAgentModel(agent.model);
|
|
|
|
return (
|
|
<div className="chat-header">
|
|
<div className="chat-header-left">
|
|
<div className="chat-header-avatar">
|
|
{isImageUrl(agent.avatar) ? (
|
|
<img src={agent.avatar} alt="avatar" style={{ width: '100%', height: '100%', borderRadius: '50%' }} />
|
|
) : (
|
|
(agent.name?.charAt(0) || '?').toUpperCase()
|
|
)}
|
|
</div>
|
|
<div className="chat-header-info">
|
|
<div className="chat-header-name-row">
|
|
<span className="chat-header-name">{agent.name}</span>
|
|
</div>
|
|
<div className="chat-header-desc">
|
|
<span className="chat-header-model">{modelText}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="chat-header-right">
|
|
<div className="chat-header-stream-toggle">
|
|
<span>流式输出</span>
|
|
<Switch size="small" checked={useStream} onChange={setUseStream} />
|
|
</div>
|
|
|
|
<Button className="chat-header-btn" onClick={onOpenHistory}>
|
|
历史对话
|
|
</Button>
|
|
|
|
<Dropdown
|
|
menu={{
|
|
items: [
|
|
{ key: 'params', label: '模型参数', icon: <SettingOutlined />, onClick: onOpenParams },
|
|
{ key: 'mcp', label: 'MCP 资源', icon: <ApiOutlined />, onClick: onOpenMcp },
|
|
{ key: 'edit', label: '管理 Agent', icon: <EditOutlined />, onClick: onManageAgent },
|
|
{ type: 'divider' as const },
|
|
{
|
|
key: 'clear',
|
|
label: '清空对话',
|
|
icon: <DeleteOutlined />,
|
|
danger: true,
|
|
onClick: () => {
|
|
Modal.confirm({ title: '清空当前会话所有消息?', onOk: onClear });
|
|
}
|
|
},
|
|
...(onLogout
|
|
? [
|
|
{ type: 'divider' as const },
|
|
{
|
|
key: 'logout',
|
|
label: '退出登录',
|
|
icon: <LogoutOutlined />,
|
|
onClick: onLogout,
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
}}
|
|
placement="bottomRight"
|
|
>
|
|
<Button className="chat-header-more-btn">
|
|
<EllipsisOutlined />
|
|
</Button>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|