refactor: repartition server-side and client-side code

This commit is contained in:
LIlGG
2025-10-11 18:26:07 +08:00
parent 7acc4949fb
commit e9b573a276
309 changed files with 631 additions and 962 deletions

View File

@@ -0,0 +1,317 @@
import type { Message } from '@prisma/client';
import type { JsonArray } from '@prisma/client/runtime/library';
import type { TextUIPart, UIMessagePart } from 'ai';
import { prisma } from '~/.server/service/prisma';
import type { SummaryAnnotation, UPageDataParts, UPageUIMessage } from '~/types/message';
import { createScopedLogger } from '~/utils/logger';
const logger = createScopedLogger('message.server');
/**
* 消息创建参数接口
*/
export interface MessageCreateParams {
chatId: string;
userId: string;
role: string;
content: string;
revisionId?: string;
annotations?: any[];
version?: number;
}
/**
* 消息更新参数接口
*/
export interface MessageUpdateParams {
content?: string;
revisionId?: string;
annotations?: any[];
version?: number;
}
/**
* 消息创建或更新参数接口
*/
export interface MessageUpsertParams {
id: string;
chatId: string;
userId: string;
role: string;
content: string;
revisionId?: string;
annotations?: any[];
version?: number;
}
/**
* 根据ID创建或更新消息upsert操作
* @param params 消息创建或更新参数
* @returns 创建或更新后的消息记录
*/
export async function upsertMessage(params: MessageUpsertParams) {
const { id, chatId, userId, role, content, revisionId, annotations } = params;
try {
const message = await prisma.message.upsert({
where: { id },
update: {
content,
revisionId,
annotations,
},
create: {
id,
chatId,
userId,
role,
content,
revisionId,
annotations,
},
});
logger.info(`[Message] 创建或更新了消息 ${id}`);
return message;
} catch (error) {
logger.error(`[Message] 创建或更新消息 ${id} 失败:`, error);
throw error;
}
}
/**
* 更新消息为遗弃消息。
*
* 此方法将会更新同一 {@param chatId} 下 startMessageId不含与 endMessageId 之间(不含)的所有消息为遗弃消息。
*
* @param chatId 聊天ID
* @param startMessageId 开始消息ID
* @param endMessageId 结束消息ID
*/
export async function updateDiscardedMessage(chatId: string, startMessageId: string) {
try {
const startMessage = await prisma.message.findUnique({
where: { id: startMessageId },
select: { createdAt: true },
});
if (!startMessage) {
logger.error(`[Message] 找不到开始消息 ${startMessageId}`);
return false;
}
// 更新 startMessageId 之后的所有消息为遗弃消息
const result = await prisma.message.updateMany({
where: {
chatId,
createdAt: {
gt: startMessage?.createdAt,
},
},
data: {
isDiscarded: true,
},
});
logger.info(`[Message] 已将聊天 ${chatId}${startMessageId} 之后的 ${result.count} 条消息标记为遗弃`);
return true;
} catch (error) {
logger.error(`[Message] 更新遗弃消息失败:`, error);
throw error;
}
}
/**
* 获取历史聊天消息接口参数
*/
export interface GetHistoryChatMessagesParams {
chatId: string;
rewindTo?: string;
}
/**
* 获取从第一条消息到指定消息之间的所有历史消息
* @param params 包含 chatId 和可选的 rewindTo 参数
* @returns 消息记录列表
*/
export async function getHistoryChatMessages(params: GetHistoryChatMessagesParams): Promise<UPageUIMessage[]> {
const { chatId, rewindTo } = params;
try {
// 如果指定了 rewindTo则获取该消息的创建时间
if (rewindTo) {
const rewindToMessage = await prisma.message.findUnique({
where: { id: rewindTo },
select: { createdAt: true },
});
if (!rewindToMessage) {
logger.warn(`[Message] 获取历史消息: 找不到指定的 rewindTo 消息 ${rewindTo}`);
// 如果找不到指定消息,则返回所有消息
return await getAllChatMessages(chatId);
}
// 获取所有在 rewindTo 消息创建时间之前(包括该消息)的消息
const messages = await prisma.message.findMany({
where: {
chatId,
isDiscarded: false,
createdAt: {
lte: rewindToMessage.createdAt,
},
},
orderBy: {
createdAt: 'asc',
},
});
logger.info(`[Message] 获取了聊天 ${chatId} 中直到消息 ${rewindTo}${messages.length} 条历史消息`);
return messages.map(convertToUIMessage);
} else {
// 如果没有指定 rewindTo则获取所有消息
return await getAllChatMessages(chatId);
}
} catch (error) {
logger.error(`[Message] 获取聊天 ${chatId} 的历史消息失败:`, error);
throw error;
}
}
function convertToUIMessage(message: Message): UPageUIMessage {
if (message.version === 2) {
return {
id: message.id,
role: message.role as 'user' | 'assistant',
parts: message.parts as any[],
metadata: message.metadata as any,
};
}
const parts: UIMessagePart<UPageDataParts, never>[] = [];
if (message.role === 'user') {
const content = JSON.parse(message.content) as TextUIPart;
parts.push({
type: 'text',
text: content.text,
});
} else {
parts.push({
type: 'text',
text: message.content,
});
}
if (message.annotations) {
const messageAnnotations = message.annotations as JsonArray;
messageAnnotations.forEach((annotation) => {
const { type } = annotation as { type: string };
if (type === 'chatSummary') {
parts.push({
type: 'data-summary',
data: annotation as unknown as SummaryAnnotation,
});
}
});
}
return {
id: message.id,
role: message.role as 'user' | 'assistant',
parts,
metadata: message.metadata as any,
};
}
/**
* 获取聊天的所有消息(内部辅助方法)
* @param chatId 聊天ID
* @returns 消息记录列表
*/
async function getAllChatMessages(chatId: string): Promise<UPageUIMessage[]> {
const messages = await prisma.message.findMany({
where: {
chatId,
isDiscarded: false,
},
orderBy: {
createdAt: 'asc',
},
});
logger.info(`[Message] 获取了聊天 ${chatId} 的所有 ${messages.length} 条历史消息`);
return messages.map(convertToUIMessage);
}
/**
* 保存聊天消息列表到数据库
* @param chatId 聊天ID
* @param messages 消息列表UPageUIMessage[]
* @returns 保存结果
*/
export async function saveChatMessages(chatId: string, messages: UPageUIMessage[]): Promise<number> {
if (!messages || messages.length === 0) {
logger.warn('[Message] 保存聊天消息: 没有提供消息数据');
return 0;
}
try {
// 获取聊天的用户ID
const chat = await prisma.chat.findUnique({
where: { id: chatId },
select: { userId: true },
});
if (!chat) {
logger.error(`[Message] 保存聊天消息: 找不到聊天 ${chatId}`);
throw new Error(`找不到聊天 ${chatId}`);
}
const userId = chat.userId;
let savedCount = 0;
// 逐条保存消息
for (const message of messages) {
// 跳过没有ID的消息
if (!message.id) {
logger.warn('[Message] 保存聊天消息: 跳过没有ID的消息');
continue;
}
// 提取消息的文本内容
const textPart = message.parts.find((part) => part.type === 'text');
const content = textPart?.text || '';
// 创建或更新消息
const updateData: any = {
content,
parts: message.parts,
metadata: message.metadata,
version: 2,
};
const createData: any = {
id: message.id,
chatId,
userId,
role: message.role,
content,
parts: message.parts,
metadata: message.metadata,
version: 2,
};
await prisma.message.upsert({
where: { id: message.id },
update: updateData,
create: createData,
});
savedCount++;
}
logger.info(`[Message] 成功保存了聊天 ${chatId}${savedCount} 条消息`);
return savedCount;
} catch (error) {
logger.error(`[Message] 保存聊天消息失败:`, error);
throw error;
}
}