2025-08-23 19:11:16 +08:00

147 lines
4.5 KiB
TypeScript

import React, { useRef, useCallback } from "react";
import { ArrowRightFromLine, ChevronDown } from 'lucide-react';
import { Switch } from 'antd';
import { MessageRenderer } from "./MessageRenderer";
import { InputBar } from "./InputBar";
import { useMessages } from "./useMessages";
import { DateDivider } from "./DateDivider";
import { LoadMoreButton } from "./LoadMoreButton";
import { ChatMessage } from "./types";
interface SmartChatBoxProps {
isSmartChatBoxOpen: boolean;
setIsSmartChatBoxOpen: (v: boolean) => void;
projectId: string;
userId: number;
}
interface MessageGroup {
date: number;
messages: ChatMessage[];
}
function BackToLatestButton({ onClick }: { onClick: () => void }) {
return (
<button
onClick={onClick}
className="fixed bottom-24 right-4 bg-blue-500 hover:bg-blue-400 text-white rounded-full p-2 shadow-lg"
title="返回最新消息"
>
<ChevronDown size={20} />
</button>
);
}
export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen, projectId, userId }: SmartChatBoxProps) {
// 消息列表引用
const listRef = useRef<HTMLDivElement>(null);
// 处理消息更新时的滚动
const handleMessagesUpdate = useCallback((shouldScroll: boolean) => {
if (shouldScroll && listRef.current) {
listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
}
}, []);
// 使用消息管理 hook
const [
{ messages, isLoading, error, hasMore, loadMoreMessages, backToLatest, isViewingHistory },
{ sendMessage },
{ enabled: systemPush, toggle: toggleSystemPush }
] = useMessages({
config: { projectId, userId },
onMessagesUpdate: handleMessagesUpdate
});
// 按日期分组消息
const groupedMessages = React.useMemo(() => {
const groups: MessageGroup[] = [];
messages.forEach(message => {
const messageDate = new Date(message.createdAt).setHours(0, 0, 0, 0);
const existingGroup = groups.find(group => {
const groupDate = new Date(group.date).setHours(0, 0, 0, 0);
return groupDate === messageDate;
});
if (existingGroup) {
existingGroup.messages.push(message);
} else {
groups.push({
date: messageDate,
messages: [message]
});
}
});
return groups.sort((a, b) => a.date - b.date);
}, [messages]);
return (
<div className="h-full w-full text-gray-100 flex flex-col" data-alt="smart-chat-box">
{/* Header */}
<div className="px-4 py-3 border-b border-white/10 flex items-center justify-between" data-alt="chat-header">
<div className="font-semibold flex items-center gap-2">
<span>Chat</span>
{/* System push toggle */}
<Switch
checkedChildren="系统推送开"
unCheckedChildren="系统推送关"
checked={systemPush}
onChange={toggleSystemPush}
className="ml-2"
/>
</div>
<div className="text-xs opacity-70">
<ArrowRightFromLine
className="w-4 h-4 cursor-pointer"
onClick={() => setIsSmartChatBoxOpen(false)}
/>
</div>
</div>
{/* Message list */}
<div ref={listRef} className="flex-1 overflow-y-auto p-4" data-alt="message-list">
{/* Load more button */}
{hasMore && (
<LoadMoreButton onClick={loadMoreMessages} loading={isLoading} />
)}
{/* Messages grouped by date */}
<div className="space-y-3">
{groupedMessages.map((group) => (
<React.Fragment key={group.date}>
<DateDivider timestamp={group.date} />
{group.messages.map((message) => (
<MessageRenderer key={message.id} msg={message} />
))}
</React.Fragment>
))}
</div>
{/* Loading indicator */}
{isLoading && !hasMore && (
<div className="flex justify-center py-2">
<div className="animate-spin rounded-full h-5 w-5 border-2 border-gray-400 border-t-white" />
</div>
)}
{/* Error message */}
{error && (
<div className="text-red-500 text-center py-2 text-sm">
{error.message}
</div>
)}
{/* Back to latest button */}
{isViewingHistory && (
<BackToLatestButton onClick={backToLatest} />
)}
</div>
{/* Input */}
<InputBar onSend={sendMessage} />
</div>
);
}