聊天记录列表优化补齐

This commit is contained in:
2025-11-11 10:26:51 +08:00
parent 5a287a42ac
commit 2f804c7d40
6 changed files with 791 additions and 384 deletions

View File

@@ -97,6 +97,11 @@ export interface WeChatState {
setVideoLoading: (messageId: number, isLoading: boolean) => void;
/** 设置视频消息URL */
setVideoUrl: (messageId: number, videoUrl: string) => void;
// ==================== 文件消息处理 ====================
/** 设置文件消息下载状态 */
setFileDownloading: (messageId: number, isDownloading: boolean) => void;
/** 设置文件消息URL */
setFileDownloadUrl: (messageId: number, fileUrl: string) => void;
// ==================== 消息接收处理 ====================
/** 接收新消息处理 */

View File

@@ -27,6 +27,169 @@ let aiRequestTimer: NodeJS.Timeout | null = null;
let pendingMessages: ChatRecord[] = []; // 待处理的消息队列
let currentAiGenerationId: string | null = null; // 当前AI生成的唯一ID
const AI_REQUEST_DELAY = 3000; // 3秒延迟
const FILE_MESSAGE_TYPE = "file";
type FileMessagePayload = {
type?: string;
title?: string;
url?: string;
isDownloading?: boolean;
fileext?: string;
size?: number | string;
[key: string]: any;
};
const isJsonLike = (value: string) => {
const trimmed = value.trim();
return trimmed.startsWith("{") && trimmed.endsWith("}");
};
const parseFileJsonContent = (
rawContent: unknown,
): FileMessagePayload | null => {
if (typeof rawContent !== "string") {
return null;
}
const trimmed = rawContent.trim();
if (!trimmed || !isJsonLike(trimmed)) {
return null;
}
try {
const parsed = JSON.parse(trimmed);
if (
parsed &&
typeof parsed === "object" &&
parsed.type === FILE_MESSAGE_TYPE
) {
return parsed as FileMessagePayload;
}
} catch (error) {
console.warn("parseFileJsonContent failed:", error);
}
return null;
};
const extractFileTitleFromContent = (rawContent: unknown): string => {
if (typeof rawContent !== "string") {
return "";
}
const trimmed = rawContent.trim();
if (!trimmed) {
return "";
}
const cdataMatch =
trimmed.match(/<title><!\[CDATA\[(.*?)\]\]><\/title>/i) ||
trimmed.match(/"title"\s*:\s*"([^"]+)"/i);
if (cdataMatch?.[1]) {
return cdataMatch[1].trim();
}
const simpleMatch = trimmed.match(/<title>([^<]+)<\/title>/i);
if (simpleMatch?.[1]) {
return simpleMatch[1].trim();
}
return "";
};
const isFileLikeMessage = (msg: ChatRecord): boolean => {
if ((msg as any).fileDownloadMeta) {
return true;
}
if (typeof msg.content === "string") {
const trimmed = msg.content.trim();
if (!trimmed) {
return false;
}
if (
/"type"\s*:\s*"file"/i.test(trimmed) ||
/<appattach/i.test(trimmed) ||
/<fileext/i.test(trimmed)
) {
return true;
}
}
return false;
};
const normalizeFilePayload = (
payload: FileMessagePayload | null | undefined,
msg: ChatRecord,
): FileMessagePayload => {
const fallbackTitle =
payload?.title ||
((msg as any).fileDownloadMeta &&
typeof (msg as any).fileDownloadMeta === "object"
? ((msg as any).fileDownloadMeta as FileMessagePayload).title
: undefined) ||
extractFileTitleFromContent(msg.content) ||
(msg as any).fileName ||
(msg as any).title ||
"";
return {
type: FILE_MESSAGE_TYPE,
...payload,
title: payload?.title ?? fallbackTitle ?? "",
isDownloading: payload?.isDownloading ?? false,
};
};
const updateFileMessageState = (
msg: ChatRecord,
updater: (payload: FileMessagePayload) => FileMessagePayload,
): ChatRecord => {
const parsedPayload = parseFileJsonContent(msg.content);
if (!parsedPayload && !isFileLikeMessage(msg)) {
return msg;
}
const basePayload = parsedPayload
? normalizeFilePayload(parsedPayload, msg)
: normalizeFilePayload(
(msg as any).fileDownloadMeta as FileMessagePayload | undefined,
msg,
);
const updatedPayload = updater(basePayload);
const sanitizedPayload: FileMessagePayload = {
...basePayload,
...updatedPayload,
type: FILE_MESSAGE_TYPE,
title:
updatedPayload.title ??
basePayload.title ??
extractFileTitleFromContent(msg.content) ??
"",
isDownloading:
updatedPayload.isDownloading ?? basePayload.isDownloading ?? false,
};
if (parsedPayload) {
return {
...msg,
content: JSON.stringify({
...parsedPayload,
...sanitizedPayload,
}),
fileDownloadMeta: sanitizedPayload,
};
}
return {
...msg,
fileDownloadMeta: sanitizedPayload,
};
};
/**
* 清除AI请求定时器和队列
@@ -618,6 +781,50 @@ export const useWeChatStore = create<WeChatState>()(
}));
},
// ==================== 文件消息处理方法 ====================
/** 更新文件消息下载状态 */
setFileDownloading: (messageId: number, isDownloading: boolean) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
if (msg.id !== messageId) {
return msg;
}
try {
return updateFileMessageState(msg, payload => ({
...payload,
isDownloading,
}));
} catch (error) {
console.error("更新文件下载状态失败:", error);
return msg;
}
}),
}));
},
/** 更新文件消息URL */
setFileDownloadUrl: (messageId: number, fileUrl: string) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
if (msg.id !== messageId) {
return msg;
}
try {
return updateFileMessageState(msg, payload => ({
...payload,
url: fileUrl,
isDownloading: false,
}));
} catch (error) {
console.error("更新文件URL失败:", error);
return msg;
}
}),
}));
},
// ==================== 数据清理方法 ====================
/** 清空所有数据 */
clearAllData: () => {

View File

@@ -17,6 +17,8 @@ const updateMessage = useWeChatStore.getState().updateMessage;
const updateMomentCommonLoading =
useWeChatStore.getState().updateMomentCommonLoading;
const addMomentCommon = useWeChatStore.getState().addMomentCommon;
const setFileDownloadUrl = useWeChatStore.getState().setFileDownloadUrl;
const setFileDownloading = useWeChatStore.getState().setFileDownloading;
// 消息处理器映射
const messageHandlers: Record<string, MessageHandler> = {
// 微信账号存活状态响应
@@ -104,6 +106,22 @@ const messageHandlers: Record<string, MessageHandler> = {
console.log("视频下载结果:", message);
// setVideoUrl(message.friendMessageId, message.url);
},
CmdDownloadFileResult: message => {
const messageId = message.friendMessageId || message.chatroomMessageId;
if (!messageId) {
console.warn("文件下载结果缺少消息ID:", message);
return;
}
if (!message.url) {
console.warn("文件下载结果缺少URL:", message);
setFileDownloading(messageId, false);
return;
}
setFileDownloadUrl(messageId, message.url);
},
CmdFetchMomentResult: message => {
addMomentCommon(message.result);