chatbox--完善流程推送 生成视频

This commit is contained in:
北枳 2025-08-23 21:26:44 +08:00
parent 65c21e1c40
commit 2a7a9f4701
6 changed files with 125 additions and 53 deletions

View File

@ -11,7 +11,7 @@ export function LoadMoreButton({ onClick, loading = false }: LoadMoreButtonProps
<button <button
onClick={onClick} onClick={onClick}
disabled={loading} disabled={loading}
className="w-full flex items-center justify-center gap-2 py-2 text-sm text-gray-400 hover:text-gray-300 hover:bg-gray-800/30 transition-colors disabled:opacity-50" className="w-full flex items-center justify-center gap-2 text-sm text-gray-400 hover:text-gray-300 hover:bg-gray-800/30 transition-colors disabled:opacity-50"
data-alt="load-more-button" data-alt="load-more-button"
> >
{loading ? ( {loading ? (

View File

@ -106,7 +106,16 @@ export function MessageRenderer({ msg }: MessageRendererProps) {
case "video": case "video":
return ( return (
<div key={idx} className="overflow-hidden rounded-xl"> <div key={idx} className="overflow-hidden rounded-xl">
<video controls src={b.url} poster={b.poster} className="w-full max-h-80 bg-black" /> <video
controls
src={b.url}
poster={b.poster}
className="w-full max-h-80 bg-black"
controlsList="nodownload noremoteplayback"
disablePictureInPicture
disableRemotePlayback
onContextMenu={e => e.preventDefault()}
/>
</div> </div>
); );
case "audio": case "audio":

View File

@ -86,8 +86,8 @@ export default function SmartChatBox({ isSmartChatBoxOpen, setIsSmartChatBoxOpen
<span>Chat</span> <span>Chat</span>
{/* System push toggle */} {/* System push toggle */}
<Switch <Switch
checkedChildren="系统推送开" checkedChildren="系统推送开"
unCheckedChildren="系统推送关" unCheckedChildren="系统推送关"
checked={systemPush} checked={systemPush}
onChange={toggleSystemPush} onChange={toggleSystemPush}
className="ml-2" className="ml-2"

View File

@ -13,7 +13,8 @@ import {
ScriptSummary, ScriptSummary,
CharacterGeneration, CharacterGeneration,
SketchGeneration, SketchGeneration,
ShotSketchGeneration ShotSketchGeneration,
ShotVideoGeneration
} from "./types"; } from "./types";
import { post } from "@/api/request"; import { post } from "@/api/request";
@ -28,7 +29,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '我想拍一个关于一个小女孩和她的机器人朋友的故事,故事发生在未来世界。' content: '我想拍一个关于一个小女孩和她的机器人朋友的故事,故事发生在未来世界。'
}]), }]),
created_at: '2024-03-20T10:00:00Z', created_at: '2024-03-20T10:00:00Z',
message_type: undefined,
function_name: undefined, function_name: undefined,
custom_data: undefined, custom_data: undefined,
status: 'success', status: 'success',
@ -40,7 +40,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '我会帮您创建一个温馨感人的科幻短片,讲述人工智能与人类情感的故事。', content: '我会帮您创建一个温馨感人的科幻短片,讲述人工智能与人类情感的故事。',
created_at: '2024-03-20T10:00:10Z', created_at: '2024-03-20T10:00:10Z',
message_type: 'project_init',
function_name: 'create_project', function_name: 'create_project',
custom_data: { custom_data: {
project_data: { project_data: {
@ -56,7 +55,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '故事概要在2045年的未来城市10岁的小女孩艾米丽收到了一个特别的生日礼物——一个具有高度情感智能的机器人伙伴"小星"。随着时间推移,他们建立了深厚的友谊。当小星因能源耗尽即将永久关闭时,艾米丽想尽办法寻找解决方案,最终通过她的坚持和创意,成功为小星找到了新的能源,让这段跨越人机界限的友谊得以延续。', content: '故事概要在2045年的未来城市10岁的小女孩艾米丽收到了一个特别的生日礼物——一个具有高度情感智能的机器人伙伴"小星"。随着时间推移,他们建立了深厚的友谊。当小星因能源耗尽即将永久关闭时,艾米丽想尽办法寻找解决方案,最终通过她的坚持和创意,成功为小星找到了新的能源,让这段跨越人机界限的友谊得以延续。',
created_at: '2024-03-20T10:01:00Z', created_at: '2024-03-20T10:01:00Z',
message_type: 'script_summary',
function_name: 'generate_script_summary', function_name: 'generate_script_summary',
custom_data: { custom_data: {
summary: '一个关于友谊和希望的温暖故事' summary: '一个关于友谊和希望的温暖故事'
@ -70,12 +68,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '主角艾米丽的形象已生成', content: '主角艾米丽的形象已生成',
created_at: '2024-03-20T10:02:00Z', created_at: '2024-03-20T10:02:00Z',
message_type: 'character_generation',
function_name: 'generate_character', function_name: 'generate_character',
custom_data: { custom_data: {
character_name: '艾米丽', character_name: '艾米丽',
image_path: 'https://picsum.photos/seed/emily/300/400', image_path: 'https://picsum.photos/seed/emily/300/400',
count: 1, completed_count: 1,
total_count: 2 total_count: 2
}, },
status: 'success', status: 'success',
@ -87,12 +84,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '机器人小星的形象已生成', content: '机器人小星的形象已生成',
created_at: '2024-03-20T10:03:00Z', created_at: '2024-03-20T10:03:00Z',
message_type: 'character_generation',
function_name: 'generate_character', function_name: 'generate_character',
custom_data: { custom_data: {
character_name: '小星', character_name: '小星',
image_path: 'https://picsum.photos/seed/robot/300/400', image_path: 'https://picsum.photos/seed/robot/300/400',
count: 2, completed_count: 2,
total_count: 2 total_count: 2
}, },
status: 'success', status: 'success',
@ -104,12 +100,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '未来城市场景设计完成', content: '未来城市场景设计完成',
created_at: '2024-03-20T10:04:00Z', created_at: '2024-03-20T10:04:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch', function_name: 'generate_sketch',
custom_data: { custom_data: {
sketch_name: '未来城市街景', sketch_name: '未来城市街景',
image_path: 'https://picsum.photos/seed/city/600/400', image_path: 'https://picsum.photos/seed/city/600/400',
count: 1, completed_count: 1,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
@ -121,12 +116,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '艾米丽的未来风格卧室设计完成', content: '艾米丽的未来风格卧室设计完成',
created_at: '2024-03-20T10:05:00Z', created_at: '2024-03-20T10:05:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch', function_name: 'generate_sketch',
custom_data: { custom_data: {
sketch_name: '艾米丽的卧室', sketch_name: '艾米丽的卧室',
image_path: 'https://picsum.photos/seed/room/600/400', image_path: 'https://picsum.photos/seed/room/600/400',
count: 2, completed_count: 2,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
@ -138,12 +132,11 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '高科技实验室场景设计完成', content: '高科技实验室场景设计完成',
created_at: '2024-03-20T10:06:00Z', created_at: '2024-03-20T10:06:00Z',
message_type: 'sketch_generation',
function_name: 'generate_sketch', function_name: 'generate_sketch',
custom_data: { custom_data: {
sketch_name: '未来实验室', sketch_name: '未来实验室',
image_path: 'https://picsum.photos/seed/lab/600/400', image_path: 'https://picsum.photos/seed/lab/600/400',
count: 3, completed_count: 3,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
@ -155,14 +148,13 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '第一个分镜:艾米丽收到礼物时的场景', content: '第一个分镜:艾米丽收到礼物时的场景',
created_at: '2024-03-20T10:07:00Z', created_at: '2024-03-20T10:07:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch', function_name: 'generate_shot_sketch',
custom_data: { custom_data: {
shot_type: '中景', shot_type: '中景',
atmosphere: '温馨、期待', atmosphere: '温馨、期待',
key_action: '艾米丽惊喜地打开礼物盒,小星缓缓启动', key_action: '艾米丽惊喜地打开礼物盒,小星缓缓启动',
url: 'https://picsum.photos/seed/shot1/600/400', url: 'https://picsum.photos/seed/shot1/600/400',
count: 1, completed_count: 1,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
@ -174,14 +166,13 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '第二个分镜:小星能源耗尽的场景', content: '第二个分镜:小星能源耗尽的场景',
created_at: '2024-03-20T10:08:00Z', created_at: '2024-03-20T10:08:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch', function_name: 'generate_shot_sketch',
custom_data: { custom_data: {
shot_type: '特写', shot_type: '特写',
atmosphere: '紧张、担忧', atmosphere: '紧张、担忧',
key_action: '小星的能源指示灯闪烁微弱,艾米丽神情焦急', key_action: '小星的能源指示灯闪烁微弱,艾米丽神情焦急',
url: 'https://picsum.photos/seed/shot2/600/400', url: 'https://picsum.photos/seed/shot2/600/400',
count: 2, completed_count: 2,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
@ -193,19 +184,34 @@ const MOCK_MESSAGES: RealApiMessage[] = [
role: 'system', role: 'system',
content: '第三个分镜:找到新能源解决方案的场景', content: '第三个分镜:找到新能源解决方案的场景',
created_at: '2024-03-20T10:09:00Z', created_at: '2024-03-20T10:09:00Z',
message_type: 'shot_sketch_generation',
function_name: 'generate_shot_sketch', function_name: 'generate_shot_sketch',
custom_data: { custom_data: {
shot_type: '全景', shot_type: '全景',
atmosphere: '欢欣、胜利', atmosphere: '欢欣、胜利',
key_action: '实验室中艾米丽成功激活新能源,小星重新焕发活力', key_action: '实验室中艾米丽成功激活新能源,小星重新焕发活力',
url: 'https://picsum.photos/seed/shot3/600/400', url: 'https://picsum.photos/seed/shot3/600/400',
count: 3, completed_count: 3,
total_count: 3 total_count: 3
}, },
status: 'success', status: 'success',
intent_type: 'procedure' intent_type: 'procedure'
}, },
// 分镜视频生成
{
id: 11.1,
role: 'system',
content: '分镜视频生成完成',
created_at: '2024-03-20T10:10:00Z',
function_name: 'generate_video',
custom_data: {
core_atmosphere: '欢欣、胜利',
urls: ['https://cdn.qikongjian.com/faces/1755798635_facefusion_output_1755798635.mp4'],
completed_count: 1,
total_count: 1
},
status: 'success',
intent_type: 'procedure'
},
// 用户反馈 // 用户反馈
{ {
id: 12, id: 12,
@ -215,7 +221,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '这个故事设计太棒了!特别喜欢艾米丽和小星的互动场景。' content: '这个故事设计太棒了!特别喜欢艾米丽和小星的互动场景。'
}]), }]),
created_at: '2024-03-20T10:10:00Z', created_at: '2024-03-20T10:10:00Z',
message_type: undefined,
function_name: undefined, function_name: undefined,
custom_data: undefined, custom_data: undefined,
status: 'success', status: 'success',
@ -230,7 +235,6 @@ const MOCK_MESSAGES: RealApiMessage[] = [
content: '谢谢您的肯定!我们可以继续优化任何场景或角色设计,您觉得有什么地方需要调整吗?' content: '谢谢您的肯定!我们可以继续优化任何场景或角色设计,您觉得有什么地方需要调整吗?'
}]), }]),
created_at: '2024-03-20T10:10:10Z', created_at: '2024-03-20T10:10:10Z',
message_type: undefined,
function_name: undefined, function_name: undefined,
custom_data: undefined, custom_data: undefined,
status: 'success', status: 'success',
@ -250,15 +254,19 @@ function isScriptSummary(data: any): data is ScriptSummary {
} }
function isCharacterGeneration(data: any): data is CharacterGeneration { function isCharacterGeneration(data: any): data is CharacterGeneration {
return data && 'character_name' in data && 'image_path' in data && 'count' in data && 'total_count' in data; return data && 'character_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data;
} }
function isSketchGeneration(data: any): data is SketchGeneration { function isSketchGeneration(data: any): data is SketchGeneration {
return data && 'sketch_name' in data && 'image_path' in data && 'count' in data && 'total_count' in data; return data && 'sketch_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data;
} }
function isShotSketchGeneration(data: any): data is ShotSketchGeneration { function isShotSketchGeneration(data: any): data is ShotSketchGeneration {
return data && 'shot_type' in data && 'atmosphere' in data && 'key_action' in data && 'url' in data && 'count' in data && 'total_count' in data; return data && 'shot_type' in data && 'atmosphere' in data && 'key_action' in data && 'url' in data && 'completed_count' in data && 'total_count' in data;
}
function isShotVideoGeneration(data: any): data is ShotVideoGeneration {
return data && 'core_atmosphere' in data && 'urls' in data && 'completed_count' in data && 'total_count' in data;
} }
/** /**
@ -267,7 +275,7 @@ function isShotSketchGeneration(data: any): data is ShotSketchGeneration {
function transformSystemMessage( function transformSystemMessage(
functionName: FunctionName, functionName: FunctionName,
content: string, content: string,
customData: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration customData: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration | ShotVideoGeneration
): MessageBlock[] { ): MessageBlock[] {
let blocks: MessageBlock[] = []; let blocks: MessageBlock[] = [];
@ -303,9 +311,9 @@ function transformSystemMessage(
text: '图片中演员形象仅供参考,后续可根据视频生成后进行调整。' text: '图片中演员形象仅供参考,后续可根据视频生成后进行调整。'
}, { }, {
type: 'progress', type: 'progress',
value: customData.count, value: customData.completed_count,
total: customData.total_count, total: customData.total_count,
label: `已生成 ${customData.count} 个演员,剧本中共有 ${customData.total_count}` label: `已生成 ${customData.completed_count} 个演员,剧本中共有 ${customData.total_count}`
}]; }];
} }
break; break;
@ -323,9 +331,9 @@ function transformSystemMessage(
text: '图片中场景仅供参考,后续可根据视频生成后进行调整。' text: '图片中场景仅供参考,后续可根据视频生成后进行调整。'
}, { }, {
type: 'progress', type: 'progress',
value: customData.count, value: customData.completed_count,
total: customData.total_count, total: customData.total_count,
label: `已生成 ${customData.count} 个场景,剧本中共有 ${customData.total_count}` label: `已生成 ${customData.completed_count} 个场景,剧本中共有 ${customData.total_count}`
}]; }];
} }
break; break;
@ -343,9 +351,29 @@ function transformSystemMessage(
text: '图片中故事板静帧仅供参考,后续可根据视频生成后进行调整。' text: '图片中故事板静帧仅供参考,后续可根据视频生成后进行调整。'
}, { }, {
type: 'progress', type: 'progress',
value: customData.count, value: customData.completed_count,
total: customData.total_count, total: customData.total_count,
label: `已生成 ${customData.count} 个故事板静帧,剧本中共有 ${customData.total_count}` label: `已生成 ${customData.completed_count} 个故事板静帧,剧本中共有 ${customData.total_count}`
}];
}
break;
case 'generate_video':
if (isShotVideoGeneration(customData)) {
blocks = [{
type: 'text',
text: `🎬 分镜视频生成 \n核心氛围${customData.core_atmosphere}`
}, {
type: 'video',
url: customData.urls[0] || ''
}, {
type: 'text',
text: '后续可在剪辑线上进行编辑。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个分镜视频,剧本中共有 ${customData.total_count}`
}]; }];
} }
break; break;

View File

@ -77,8 +77,7 @@ export interface MessagesResponse {
} }
type ContentType = "text" | "image" | "video" | "audio"; type ContentType = "text" | "image" | "video" | "audio";
export type MessageType = "project_init" | "script_summary" | "character_generation" | "sketch_generation" | "shot_sketch_generation"; export type FunctionName = "create_project" | "generate_script_summary" | "generate_character" | "generate_sketch" | "generate_shot_sketch" | "generate_video";
export type FunctionName = "create_project" | "generate_script_summary" | "generate_character" | "generate_sketch" | "generate_shot_sketch";
// 项目创建 // 项目创建
export interface ProjectInit { export interface ProjectInit {
@ -94,14 +93,14 @@ export interface ScriptSummary {
export interface CharacterGeneration { export interface CharacterGeneration {
character_name: string; // 角色名称 character_name: string; // 角色名称
image_path: string; // 角色图片 image_path: string; // 角色图片
count: number; // 生成数量 completed_count: number; // 生成数量
total_count: number; // 总数量 total_count: number; // 总数量
} }
// 场景草图生成 // 场景草图生成
export interface SketchGeneration { export interface SketchGeneration {
sketch_name: string; // 场景草图名称 sketch_name: string; // 场景草图名称
image_path: string; // 场景草图图片 image_path: string; // 场景草图图片
count: number; // 生成数量 completed_count: number; // 生成数量
total_count: number; // 总数量 total_count: number; // 总数量
} }
// 分镜草图生成 // 分镜草图生成
@ -110,7 +109,14 @@ export interface ShotSketchGeneration {
atmosphere: string; // 氛围描述 atmosphere: string; // 氛围描述
key_action: string; // 关键动作描述 key_action: string; // 关键动作描述
url: string; // 分镜草图图片 url: string; // 分镜草图图片
count: number; // 生成数量 completed_count: number; // 生成数量
total_count: number; // 总数量
}
// 分镜视频生成
export interface ShotVideoGeneration {
core_atmosphere: string; // 核心氛围
urls: string[]; // 分镜视频
completed_count: number; // 生成数量
total_count: number; // 总数量 total_count: number; // 总数量
} }
@ -124,9 +130,8 @@ export interface RealApiMessage {
id: number; id: number;
role: Role; role: Role;
content: string; content: string;
message_type?: MessageType;
function_name?: FunctionName; function_name?: FunctionName;
custom_data?: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration; custom_data?: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration | ShotVideoGeneration;
status: MessageStatus; status: MessageStatus;
intent_type: IntentType; intent_type: IntentType;
} }

View File

@ -23,14 +23,28 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 系统推送状态 // 系统推送状态
const [systemPush, setSystemPush] = useState(true); const [systemPush, setSystemPush] = useState(true);
const systemPushDisabledTimeRef = useRef<number | null>(null);
// 状态引用 // 状态引用
const configRef = useRef(config); const configRef = useRef(config);
const isInitialLoadRef = useRef(true); const isInitialLoadRef = useRef(true);
const timeoutIdRef = useRef<NodeJS.Timeout | null>(null); const timeoutIdRef = useRef<NodeJS.Timeout | null>(null);
const isPollingRef = useRef(false);
const isViewingHistoryRef = useRef(false); const isViewingHistoryRef = useRef(false);
const prevTotalCountRef = useRef(totalCount); const prevTotalCountRef = useRef(totalCount);
const isPollingRef = useRef(false);
// 过滤消息
const filterMessages = useCallback((messages: ChatMessage[]) => {
if (systemPush || !systemPushDisabledTimeRef.current) {
return messages;
}
// 系统推送关闭时,只显示关闭前的系统消息和所有非系统消息
return messages.filter(msg =>
msg.role !== 'system' ||
msg.createdAt <= systemPushDisabledTimeRef.current!
);
}, [systemPush]);
// 合并和去重消息 // 合并和去重消息
const mergeMessages = useCallback((oldMessages: ChatMessage[], newMessages: ChatMessage[]) => { const mergeMessages = useCallback((oldMessages: ChatMessage[], newMessages: ChatMessage[]) => {
@ -42,9 +56,12 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
}); });
// 转回数组并按 id 排序 // 转回数组并按 id 排序
return Array.from(messageMap.values()) const merged = Array.from(messageMap.values())
.sort((a, b) => Number(a.id) - Number(b.id)); .sort((a, b) => Number(a.id) - Number(b.id));
}, []);
// 过滤系统消息
return filterMessages(merged);
}, [filterMessages]);
// 更新 config 引用 // 更新 config 引用
useEffect(() => { useEffect(() => {
@ -68,9 +85,11 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
} }
const response = await fetchMessages(configRef.current, 0, PAGE_SIZE); const response = await fetchMessages(configRef.current, 0, PAGE_SIZE);
setLatestMessages(response.messages); const filteredMessages = filterMessages(response.messages);
setLatestMessages(response.messages); // 保存完整的消息列表
if (!isViewingHistoryRef.current) { if (!isViewingHistoryRef.current) {
setDisplayMessages(response.messages); setDisplayMessages(filteredMessages); // 显示过滤后的消息
} }
setTotalCount(response.totalCount); setTotalCount(response.totalCount);
setHasMore(response.hasMore); setHasMore(response.hasMore);
@ -83,7 +102,7 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
setIsLoading(false); setIsLoading(false);
} }
} }
}, []); }, [filterMessages]);
// 加载更多历史消息 // 加载更多历史消息
const loadMoreMessages = useCallback(async () => { const loadMoreMessages = useCallback(async () => {
@ -111,9 +130,9 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 返回最新消息 // 返回最新消息
const backToLatest = useCallback(async () => { const backToLatest = useCallback(async () => {
isViewingHistoryRef.current = false; isViewingHistoryRef.current = false;
setDisplayMessages(latestMessages); setDisplayMessages(filterMessages(latestMessages));
onMessagesUpdate?.(true); onMessagesUpdate?.(true);
}, [latestMessages, onMessagesUpdate]); }, [latestMessages, filterMessages, onMessagesUpdate]);
// 发送消息 // 发送消息
const handleSendMessage = useCallback(async (blocks: MessageBlock[]) => { const handleSendMessage = useCallback(async (blocks: MessageBlock[]) => {
@ -177,8 +196,19 @@ export function useMessages({ config, onMessagesUpdate }: UseMessagesProps): [Me
// 系统推送开关 // 系统推送开关
const toggleSystemPush = useCallback(() => { const toggleSystemPush = useCallback(() => {
setSystemPush(prev => !prev); setSystemPush(prev => {
}, []); if (prev) {
// 关闭系统推送时,记录当前时间
systemPushDisabledTimeRef.current = Date.now();
} else {
// 开启系统推送时,清除时间记录并更新显示
systemPushDisabledTimeRef.current = null;
// 立即更新显示的消息
setDisplayMessages(filterMessages(latestMessages));
}
return !prev;
});
}, [latestMessages, filterMessages]);
// 轮询获取最新消息 // 轮询获取最新消息
useEffect(() => { useEffect(() => {