聊天记录列表优化补齐
This commit is contained in:
@@ -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;
|
||||
|
||||
// ==================== 消息接收处理 ====================
|
||||
/** 接收新消息处理 */
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user