feat: update ChatHistoryPanel with improved state management and UI enhancements
This commit is contained in:
@@ -282,159 +282,161 @@ export default function HomePage() {
|
||||
return (
|
||||
<section className="h-full w-full min-h-0">
|
||||
<div className="flex h-full w-full min-h-0 flex-col gap-2 md:flex-row">
|
||||
<ChatHistoryPanel
|
||||
buckets={historyBuckets}
|
||||
loading={!chat.initialized}
|
||||
selectedConversationId={chat.currentSessionKey}
|
||||
onNewChat={() => {
|
||||
void chatStore.newSession(selectedAgentId || undefined);
|
||||
}}
|
||||
onSelectConversation={(conversationId) => {
|
||||
chatStore.switchSession(conversationId);
|
||||
}}
|
||||
onRenameConversation={(conversationId) => {
|
||||
const currentLabel = chat.sessionLabels[conversationId]
|
||||
|| chat.sessions.find((session) => session.key === conversationId)?.displayName
|
||||
|| '';
|
||||
const nextLabel = window.prompt('重命名对话', currentLabel);
|
||||
if (nextLabel) {
|
||||
chatStore.renameSession(conversationId, nextLabel);
|
||||
}
|
||||
}}
|
||||
onDeleteConversation={(conversationId) => {
|
||||
const confirmed = window.confirm('确定删除该会话吗?删除后将无法恢复。');
|
||||
if (confirmed) {
|
||||
void chatStore.deleteSession(conversationId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-[20px] bg-white shadow-[0_10px_30px_rgba(15,23,42,0.08)] dark:bg-[#1b1b1d] md:flex-row">
|
||||
<ChatHistoryPanel
|
||||
buckets={historyBuckets}
|
||||
loading={!chat.initialized}
|
||||
selectedConversationId={chat.currentSessionKey}
|
||||
onNewChat={() => {
|
||||
void chatStore.newSession(selectedAgentId || undefined);
|
||||
}}
|
||||
onSelectConversation={(conversationId) => {
|
||||
chatStore.switchSession(conversationId);
|
||||
}}
|
||||
onRenameConversation={(conversationId) => {
|
||||
const currentLabel = chat.sessionLabels[conversationId]
|
||||
|| chat.sessions.find((session) => session.key === conversationId)?.displayName
|
||||
|| '';
|
||||
const nextLabel = window.prompt('重命名对话', currentLabel);
|
||||
if (nextLabel) {
|
||||
chatStore.renameSession(conversationId, nextLabel);
|
||||
}
|
||||
}}
|
||||
onDeleteConversation={(conversationId) => {
|
||||
const confirmed = window.confirm('确定删除该会话吗?删除后将无法恢复。');
|
||||
if (confirmed) {
|
||||
void chatStore.deleteSession(conversationId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[20px] bg-white shadow-[0_10px_30px_rgba(15,23,42,0.08)] dark:bg-[#1b1b1d]">
|
||||
<div className="flex items-center justify-between border-b border-[#edf2f7] px-6 py-4 dark:border-[#2a2a2d]">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-[#171717] dark:text-gray-100">智能对话</h2>
|
||||
<div className="mt-1 text-xs text-[#99A0AE] dark:text-gray-500">
|
||||
网关状态:{chat.gatewayStatus === 'connected' ? '已连接' : chat.gatewayStatus === 'reconnecting' ? '重连中' : '未连接'}
|
||||
{currentAgent ? ` · 当前代理:${currentAgent.name}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-[#525866] dark:text-gray-300">
|
||||
<span>代理</span>
|
||||
<select
|
||||
className="rounded-full border border-[#E5E8EE] bg-white px-3 py-1.5 text-xs text-[#525866] outline-none transition-colors hover:border-[#2B7FFF] dark:border-[#2a2a2d] dark:bg-[#232327] dark:text-gray-300"
|
||||
disabled={agentsState.loading || agentsState.agents.length === 0}
|
||||
value={selectedAgentId}
|
||||
onChange={(event) => {
|
||||
chatStore.selectAgent(event.target.value);
|
||||
}}
|
||||
>
|
||||
{agentsState.agents.map((agent) => (
|
||||
<option key={agent.id} value={agent.id}>
|
||||
{agent.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-[#E5E8EE] px-3 py-1.5 text-xs text-[#525866] transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:text-gray-300"
|
||||
onClick={() => {
|
||||
void agentsStore.load();
|
||||
void chatStore.loadSessions();
|
||||
void chatStore.loadHistory();
|
||||
}}
|
||||
>
|
||||
刷新会话
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<ChatMessageList loading={chat.loading} messages={visibleMessages} />
|
||||
<ChatComposer
|
||||
attachments={attachments}
|
||||
error={chat.error}
|
||||
isSending={chat.sending}
|
||||
value={inputMessage}
|
||||
onAttach={handleAttach}
|
||||
onChange={setInputMessage}
|
||||
onDismissError={() => chatStore.clearError()}
|
||||
onRemoveAttachment={(index) => {
|
||||
setAttachments((currentAttachments) => currentAttachments.filter((_, currentIndex) => currentIndex !== index));
|
||||
}}
|
||||
onSend={() => {
|
||||
void handleSendMessage();
|
||||
}}
|
||||
onStop={() => {
|
||||
void chatStore.abortRun();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[#edf2f7] dark:border-[#2a2a2d]">
|
||||
<div className="px-4 py-3">
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
<h3 className="text-base font-semibold text-[#171717] dark:text-gray-100">任务中心</h3>
|
||||
<div className="text-xs text-[#99A0AE] dark:text-gray-500">沿用当前视觉与入口结构</div>
|
||||
</div>
|
||||
|
||||
{taskCenterNotice ? (
|
||||
<div className="mb-3 rounded-[12px] border border-[#dfeaf6] bg-[#f8fbff] px-4 py-3 text-sm text-[#525866] dark:border-[#2a2a2d] dark:bg-[#232327] dark:text-gray-300">
|
||||
{taskCenterNotice}
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="flex items-center justify-between border-b border-[#edf2f7] px-6 py-4 dark:border-[#2a2a2d]">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-[#171717] dark:text-gray-100">智能对话</h2>
|
||||
<div className="mt-1 text-xs text-[#99A0AE] dark:text-gray-500">
|
||||
网关状态:{chat.gatewayStatus === 'connected' ? '已连接' : chat.gatewayStatus === 'reconnecting' ? '重连中' : '未连接'}
|
||||
{currentAgent ? ` · 当前代理:${currentAgent.name}` : ''}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1">
|
||||
{taskCenterList.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className="relative flex items-start gap-3 rounded-[10px] border border-[#dfeaf6] bg-white p-3.5 text-left transition-colors hover:bg-[#F5F7FA] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:hover:bg-[#2a2a2d]"
|
||||
onClick={() => {
|
||||
void handleTaskCenterItem(item);
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-[#525866] dark:text-gray-300">
|
||||
<span>代理</span>
|
||||
<select
|
||||
className="rounded-full border border-[#E5E8EE] bg-white px-3 py-1.5 text-xs text-[#525866] outline-none transition-colors hover:border-[#2B7FFF] dark:border-[#2a2a2d] dark:bg-[#232327] dark:text-gray-300"
|
||||
disabled={agentsState.loading || agentsState.agents.length === 0}
|
||||
value={selectedAgentId}
|
||||
onChange={(event) => {
|
||||
chatStore.selectAgent(event.target.value);
|
||||
}}
|
||||
>
|
||||
{item.type === 'channel' ? (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="absolute right-2 top-2 rounded-full p-1 text-[#99A0AE] transition-colors hover:text-[#525866] dark:hover:text-gray-200"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setTaskCenterNotice(null);
|
||||
void channelStore.refreshAvailableChannels();
|
||||
setAddChannelDialogOpen(true);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
{agentsState.agents.map((agent) => (
|
||||
<option key={agent.id} value={agent.id}>
|
||||
{agent.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-[#E5E8EE] px-3 py-1.5 text-xs text-[#525866] transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:text-gray-300"
|
||||
onClick={() => {
|
||||
void agentsStore.load();
|
||||
void chatStore.loadSessions();
|
||||
void chatStore.loadHistory();
|
||||
}}
|
||||
>
|
||||
刷新会话
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<ChatMessageList loading={chat.loading} messages={visibleMessages} />
|
||||
<ChatComposer
|
||||
attachments={attachments}
|
||||
error={chat.error}
|
||||
isSending={chat.sending}
|
||||
value={inputMessage}
|
||||
onAttach={handleAttach}
|
||||
onChange={setInputMessage}
|
||||
onDismissError={() => chatStore.clearError()}
|
||||
onRemoveAttachment={(index) => {
|
||||
setAttachments((currentAttachments) => currentAttachments.filter((_, currentIndex) => currentIndex !== index));
|
||||
}}
|
||||
onSend={() => {
|
||||
void handleSendMessage();
|
||||
}}
|
||||
onStop={() => {
|
||||
void chatStore.abortRun();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[#edf2f7] dark:border-[#2a2a2d]">
|
||||
<div className="px-4 py-3">
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
<h3 className="text-base font-semibold text-[#171717] dark:text-gray-100">任务中心</h3>
|
||||
<div className="text-xs text-[#99A0AE] dark:text-gray-500">沿用当前视觉与入口结构</div>
|
||||
</div>
|
||||
|
||||
{taskCenterNotice ? (
|
||||
<div className="mb-3 rounded-xl border border-[#dfeaf6] bg-[#f8fbff] px-4 py-3 text-sm text-[#525866] dark:border-[#2a2a2d] dark:bg-[#232327] dark:text-gray-300">
|
||||
{taskCenterNotice}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 max-[800px]:grid-cols-1">
|
||||
{taskCenterList.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className="relative flex items-start gap-3 rounded-[10px] border border-[#dfeaf6] bg-white p-3.5 text-left transition-colors hover:bg-[#F5F7FA] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:hover:bg-[#2a2a2d]"
|
||||
onClick={() => {
|
||||
void handleTaskCenterItem(item);
|
||||
}}
|
||||
>
|
||||
{item.type === 'channel' ? (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="absolute right-2 top-2 rounded-full p-1 text-[#99A0AE] transition-colors hover:text-[#525866] dark:hover:text-gray-200"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setTaskCenterNotice(null);
|
||||
void channelStore.refreshAvailableChannels();
|
||||
setAddChannelDialogOpen(true);
|
||||
}
|
||||
}}
|
||||
aria-label="配置渠道"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4 fill-none stroke-current" strokeWidth="1.8">
|
||||
<path
|
||||
d="M12 3V6M12 18V21M4.93 4.93L7.05 7.05M16.95 16.95L19.07 19.07M3 12H6M18 12H21M4.93 19.07L7.05 16.95M16.95 7.05L19.07 4.93M15 12A3 3 0 1 1 9 12A3 3 0 0 1 15 12Z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
) : null}
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-lg border border-dashed border-[#9fc0e8] bg-[#EFF6FF] text-[23px] text-[#3b82f6] dark:border-gray-700 dark:bg-[#222225]">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-[#171717] dark:text-gray-100">{item.title}</div>
|
||||
<div className="mt-1.5 text-[13px] text-[#9aa5b1] dark:text-gray-400">{item.desc}</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setTaskCenterNotice(null);
|
||||
void channelStore.refreshAvailableChannels();
|
||||
setAddChannelDialogOpen(true);
|
||||
}
|
||||
}}
|
||||
aria-label="配置渠道"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4 fill-none stroke-current" strokeWidth="1.8">
|
||||
<path
|
||||
d="M12 3V6M12 18V21M4.93 4.93L7.05 7.05M16.95 16.95L19.07 19.07M3 12H6M18 12H21M4.93 19.07L7.05 16.95M16.95 7.05L19.07 4.93M15 12A3 3 0 1 1 9 12A3 3 0 0 1 15 12Z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
) : null}
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-lg border border-dashed border-[#9fc0e8] bg-[#EFF6FF] text-[23px] text-[#3b82f6] dark:border-gray-700 dark:bg-[#222225]">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-[#171717] dark:text-gray-100">{item.title}</div>
|
||||
<div className="mt-1.5 text-[13px] text-[#9aa5b1] dark:text-gray-400">{item.desc}</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user