160 lines
5.7 KiB
TypeScript
160 lines
5.7 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { App as AntApp, Empty } from 'antd';
|
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
import type { ModelOverrides } from '../../api';
|
|
import AgentSidebar from './components/AgentSidebar';
|
|
import ChatHeader from './components/ChatHeader';
|
|
import ChatBody from './components/ChatBody';
|
|
import ChatInput from './components/ChatInput';
|
|
import ChatDrawers from './components/ChatDrawers';
|
|
import { useChatScroll } from './hooks/useChatScroll';
|
|
import { useChatData } from './hooks/useChatData';
|
|
import { useChatSender } from './hooks/useChatSender';
|
|
|
|
export default function ChatPage() {
|
|
const { id } = useParams();
|
|
const navigate = useNavigate();
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const { message } = AntApp.useApp();
|
|
|
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
const [highlightId, setHighlightId] = useState<string | null>(null);
|
|
const [overrides, setOverrides] = useState<ModelOverrides>({});
|
|
|
|
const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
|
|
const [mcpDrawerOpen, setMcpDrawerOpen] = useState(false);
|
|
const [paramsDrawerOpen, setParamsDrawerOpen] = useState(false);
|
|
const [tplDrawerOpen, setTplDrawerOpen] = useState(false);
|
|
|
|
const abortRef = useRef<AbortController | null>(null);
|
|
const { bodyRef, scrollBottom, initialScrollDoneRef } = useChatScroll();
|
|
|
|
useEffect(() => {
|
|
const s = searchParams.get('session');
|
|
const m = searchParams.get('msg');
|
|
if (s) {
|
|
setSessionId(s);
|
|
if (m) setHighlightId(m);
|
|
const next = new URLSearchParams(searchParams);
|
|
next.delete('session');
|
|
next.delete('msg');
|
|
setSearchParams(next, { replace: true });
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [searchParams]);
|
|
|
|
const { agent, agentList, messages, setMessages, branches, setBranches, loadMessages } = useChatData({
|
|
agentId: id,
|
|
sessionId,
|
|
highlightId,
|
|
setHighlightId,
|
|
scrollBottom,
|
|
initialScrollDoneRef,
|
|
setOverrides: (updater) => setOverrides(updater),
|
|
abort: () => abortRef.current?.abort()
|
|
});
|
|
|
|
const sender = useChatSender({
|
|
agentId: id,
|
|
agent,
|
|
sessionId,
|
|
overrides,
|
|
setOverrides: (updater) => setOverrides(updater),
|
|
messages,
|
|
setMessages,
|
|
setBranches,
|
|
loadMessages,
|
|
scrollBottom,
|
|
notify: { success: (t) => message.success(t), error: (t) => message.error(t) },
|
|
abortRef
|
|
});
|
|
|
|
return (
|
|
<div className="chat-shell">
|
|
<AgentSidebar
|
|
agentList={agentList}
|
|
activeAgentId={id}
|
|
onCreate={() => navigate('/agents/new')}
|
|
onSelect={(aid) => navigate(`/chat/${aid}`)}
|
|
/>
|
|
|
|
<section className="chat-main">
|
|
{!agent ? (
|
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<Empty description="请在左侧选择一个智能体开始对话" />
|
|
</div>
|
|
) : (
|
|
<>
|
|
<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}
|
|
/>
|
|
|
|
<ChatBody
|
|
bodyRef={bodyRef}
|
|
agent={agent}
|
|
messages={messages}
|
|
branches={branches}
|
|
highlightId={highlightId}
|
|
sending={sender.sending}
|
|
streaming={sender.streaming}
|
|
onRegenerate={sender.handleRegenerate}
|
|
onSwitchBranch={sender.handleSwitchBranch}
|
|
onCopy={(text) => navigator.clipboard?.writeText(text).then(() => message.success('已复制'))}
|
|
/>
|
|
|
|
<ChatInput
|
|
input={sender.input}
|
|
setInput={sender.setInput}
|
|
sending={sender.sending}
|
|
attachments={sender.attachments}
|
|
setAttachments={(updater) => sender.setAttachments(updater)}
|
|
imageUrls={sender.imageUrls}
|
|
setImageUrls={(updater) => sender.setImageUrls(updater)}
|
|
onSend={sender.handleSend}
|
|
onStop={sender.handleStop}
|
|
onAttach={sender.handleAttach}
|
|
onOpenTpl={() => setTplDrawerOpen(true)}
|
|
modelOptions={sender.modelOptions}
|
|
activeModelValue={sender.activeModelValue}
|
|
onChangeModel={(modelId) => {
|
|
const selected = sender.modelOptions.find((m) => m.value === modelId);
|
|
setOverrides((o) => ({ ...o, model_id: String(modelId), model: selected?.label || String(modelId) }));
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</section>
|
|
|
|
<ChatDrawers
|
|
agentId={id}
|
|
agent={agent}
|
|
input={sender.input}
|
|
setInput={(updater) => sender.setInput(updater(sender.input))}
|
|
overrides={overrides}
|
|
setOverrides={(updater) => setOverrides(updater)}
|
|
sessionId={sessionId}
|
|
setSessionId={setSessionId}
|
|
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>
|
|
);
|
|
}
|
|
|