194 lines
6.5 KiB
TypeScript
194 lines
6.5 KiB
TypeScript
import { useEffect, useState, useCallback, useRef } from 'react';
|
||
import { App as AntApp, Empty, Modal } from 'antd';
|
||
import type { ChatPageLogicOutput } from './ChatPageLogic';
|
||
import { markdownToPlainText } from './utils/copy';
|
||
import { useChatPageLogic } from './ChatPageLogic';
|
||
import ChatBody from './components/ChatBody';
|
||
import ChatDrawers from './components/ChatDrawers';
|
||
import ChatHeader from './components/ChatHeader';
|
||
import ChatInput from './components/ChatInput';
|
||
import ChatOutline from './components/ChatOutline';
|
||
import type { ModelOverrides } from '../../api';
|
||
import { useAuth } from '../../store/auth';
|
||
import './styles/chat-page-pure.css';
|
||
|
||
/**
|
||
* 纯会话页面组件
|
||
* 仅保留聊天主区域(section.chat-main),去掉全局 Sidebar 和 AgentSidebar(chat-side)
|
||
* 会话功能与原聊天页面完全一致
|
||
*/
|
||
export default function ChatPagePure() {
|
||
const logic = useChatPageLogic();
|
||
const { message } = AntApp.useApp();
|
||
const { logout } = useAuth();
|
||
const [outlineCollapsed, setOutlineCollapsed] = useState(true);
|
||
const isAutoScrolling = useRef(false);
|
||
|
||
const {
|
||
id,
|
||
roomId,
|
||
highlightId,
|
||
historyDrawerOpen,
|
||
mcpDrawerOpen,
|
||
paramsDrawerOpen,
|
||
tplDrawerOpen,
|
||
agent,
|
||
agentList,
|
||
messages,
|
||
branches,
|
||
bodyRef,
|
||
sender,
|
||
navigate,
|
||
overrides,
|
||
setOverrides,
|
||
setRoomId,
|
||
setHighlightId,
|
||
setHistoryDrawerOpen,
|
||
setMcpDrawerOpen,
|
||
setParamsDrawerOpen,
|
||
setTplDrawerOpen,
|
||
handleNewSession,
|
||
} = logic;
|
||
|
||
// 初始化时,如果有历史消息,激活最后一条 Agent 消息
|
||
useEffect(() => {
|
||
if (messages.length > 0 && !highlightId) {
|
||
const agentMsgs = messages.filter(m => m.role === 'assistant' || m.role === 'agent' || m.speaker?.type === 'agent');
|
||
if (agentMsgs.length > 0) {
|
||
setHighlightId(agentMsgs[agentMsgs.length - 1].id);
|
||
}
|
||
}
|
||
}, [messages.length]);
|
||
|
||
const handleJump = useCallback((msgId: string) => {
|
||
isAutoScrolling.current = true;
|
||
setHighlightId(msgId);
|
||
const el = document.getElementById('msg-' + msgId);
|
||
if (el) {
|
||
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||
setTimeout(() => {
|
||
isAutoScrolling.current = false;
|
||
}, 800);
|
||
}
|
||
}, [setHighlightId]);
|
||
|
||
// 退出登录
|
||
const handleLogout = () => {
|
||
Modal.confirm({
|
||
title: '确定要退出登录吗?',
|
||
onOk: async () => {
|
||
await logout();
|
||
window.location.href = '/login';
|
||
},
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="chat-pure-shell">
|
||
<section className="chat-main">
|
||
{!agent ? (
|
||
<div className="chat-empty-state">
|
||
<Empty description="请选择一个智能体开始对话" />
|
||
</div>
|
||
) : (
|
||
<div className="chat-content-layout">
|
||
<div className="chat-conversation-panel">
|
||
<ChatHeader
|
||
agent={agent}
|
||
useStream={sender.useStream}
|
||
setUseStream={sender.setUseStream}
|
||
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
||
onOpenParams={() => setParamsDrawerOpen(true)}
|
||
onOpenMcp={() => setMcpDrawerOpen(true)}
|
||
onManageAgent={() => navigate(`/agents/${id}`)}
|
||
onClear={sender.handleClear}
|
||
onLogout={handleLogout}
|
||
/>
|
||
|
||
<ChatBody
|
||
bodyRef={bodyRef}
|
||
agent={agent}
|
||
agentList={agentList}
|
||
currentAgentId={id!}
|
||
messages={messages}
|
||
branches={branches}
|
||
highlightId={highlightId}
|
||
sending={sender.sending}
|
||
streaming={sender.streaming}
|
||
onRegenerate={sender.handleRegenerate}
|
||
onSwitchBranch={sender.handleSwitchBranch}
|
||
scrollBottom={logic.scrollBottom}
|
||
initialScrollDoneRef={logic.initialScrollDoneRef}
|
||
onCopy={(text, mode) => {
|
||
const content = mode === 'markdown' ? text : markdownToPlainText(text);
|
||
navigator.clipboard?.writeText(content).then(() => {
|
||
message.success(mode === 'markdown' ? '已复制(Markdown)' : '已复制(纯文本)');
|
||
});
|
||
}}
|
||
/>
|
||
|
||
<ChatInput
|
||
input={sender.input}
|
||
setInput={sender.setInput}
|
||
sending={sender.sending}
|
||
attachments={sender.attachments}
|
||
setAttachments={sender.setAttachments}
|
||
imageUrls={sender.imageUrls}
|
||
setImageUrls={sender.setImageUrls}
|
||
onSend={sender.handleSend}
|
||
onStop={sender.handleStop}
|
||
onAttach={sender.handleAttach}
|
||
onOpenTpl={() => setTplDrawerOpen(true)}
|
||
modelOptions={sender.modelOptions}
|
||
activeModelValue={sender.activeModelValue}
|
||
onChangeModel={(modelId) => {
|
||
const picked = sender.modelOptions.find((o) => o.value === modelId);
|
||
setOverrides((o: ModelOverrides) => ({
|
||
...o,
|
||
model_id: modelId,
|
||
model: picked?.label ?? o.model
|
||
}));
|
||
}}
|
||
agentList={agentList}
|
||
onInsertMention={() => {}}
|
||
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
||
onNewSession={handleNewSession}
|
||
/>
|
||
</div>
|
||
|
||
<ChatOutline
|
||
messages={messages}
|
||
activeId={highlightId}
|
||
collapsed={outlineCollapsed}
|
||
onToggleCollapse={() => setOutlineCollapsed(!outlineCollapsed)}
|
||
onJump={handleJump}
|
||
/>
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<ChatDrawers
|
||
agentId={id}
|
||
agent={agent}
|
||
input={sender.input}
|
||
setInput={(updater) => sender.setInput(updater)}
|
||
overrides={overrides}
|
||
setOverrides={setOverrides}
|
||
roomId={roomId}
|
||
setRoomId={setRoomId}
|
||
setHighlightId={setHighlightId}
|
||
sessionRefresh={sender.sessionRefresh}
|
||
mcpDrawerOpen={mcpDrawerOpen}
|
||
setMcpDrawerOpen={setMcpDrawerOpen}
|
||
tplDrawerOpen={tplDrawerOpen}
|
||
setTplDrawerOpen={setTplDrawerOpen}
|
||
paramsDrawerOpen={paramsDrawerOpen}
|
||
setParamsDrawerOpen={setParamsDrawerOpen}
|
||
historyDrawerOpen={historyDrawerOpen}
|
||
setHistoryDrawerOpen={setHistoryDrawerOpen}
|
||
notify={{ success: (t) => message.success(t) }}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|