Files
map-client-vue/web/src/services/chat/MessageService.ts
2025-10-16 12:45:05 +08:00

252 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* MessageService
* 负责消息的 CRUD 操作
*
* 职责:
* - 消息的创建、读取、更新、删除
* - 消息状态管理
* - 消息查询和过滤
*/
import type { Message, Conversation } from '../../types/chat'
import type { CreateMessageOptions, UpdateMessageOptions, MessageQueryResult } from './types'
export class MessageService {
private conversations: Map<string, Conversation>
constructor(conversations: Map<string, Conversation>) {
this.conversations = conversations
}
/**
* 在指定对话中创建新消息
*/
createMessage(conversationId: string, options: CreateMessageOptions): Message {
const conversation = this.conversations.get(conversationId)
if (!conversation) {
throw new Error(`Conversation not found: ${conversationId}`)
}
const message: Message = {
id: this.generateId(),
role: options.role,
content: options.content,
status: options.status || 'success',
timestamp: new Date(),
model: options.model,
tokens: options.tokens
}
conversation.messages.push(message)
conversation.updatedAt = new Date()
return message
}
/**
* 获取指定对话的所有消息
*/
getMessages(conversationId: string): Message[] {
const conversation = this.conversations.get(conversationId)
return conversation?.messages || []
}
/**
* 根据 topicId 获取消息
*/
getMessagesByTopicId(topicId: string): Message[] {
for (const conv of this.conversations.values()) {
if (conv.topicId === topicId) {
return conv.messages
}
}
return []
}
/**
* 获取指定消息
*/
getMessage(conversationId: string, messageId: string): Message | undefined {
const conversation = this.conversations.get(conversationId)
if (!conversation) return undefined
return conversation.messages.find(m => m.id === messageId)
}
/**
* 查找消息及其位置信息
*/
findMessage(messageId: string, topicId?: string): MessageQueryResult {
for (const conv of this.conversations.values()) {
if (topicId && conv.topicId !== topicId) continue
const index = conv.messages.findIndex(m => m.id === messageId)
if (index !== -1) {
return {
message: conv.messages[index],
conversation: conv,
index
}
}
}
return {
message: undefined,
conversation: undefined,
index: -1
}
}
/**
* 更新消息
*/
updateMessage(
conversationId: string,
messageId: string,
options: UpdateMessageOptions
): boolean {
const conversation = this.conversations.get(conversationId)
if (!conversation) return false
const message = conversation.messages.find(m => m.id === messageId)
if (!message) return false
// 更新消息字段
if (options.content !== undefined) {
message.content = options.content
}
if (options.status !== undefined) {
message.status = options.status
}
if (options.tokens !== undefined) {
message.tokens = options.tokens
}
conversation.updatedAt = new Date()
return true
}
/**
* 更新消息状态
*/
updateMessageStatus(
conversationId: string,
messageId: string,
status: 'sending' | 'success' | 'error' | 'paused'
): boolean {
return this.updateMessage(conversationId, messageId, { status })
}
/**
* 追加消息内容(用于流式响应)
*/
appendMessageContent(
conversationId: string,
messageId: string,
content: string
): boolean {
const conversation = this.conversations.get(conversationId)
if (!conversation) return false
const message = conversation.messages.find(m => m.id === messageId)
if (!message) return false
message.content += content
conversation.updatedAt = new Date()
return true
}
/**
* 删除消息
*/
deleteMessage(conversationId: string, messageId: string): boolean {
const conversation = this.conversations.get(conversationId)
if (!conversation) return false
const index = conversation.messages.findIndex(m => m.id === messageId)
if (index === -1) return false
conversation.messages.splice(index, 1)
conversation.updatedAt = new Date()
return true
}
/**
* 删除指定消息之后的所有消息(包括该消息)
*/
deleteMessagesAfter(conversationId: string, messageId: string): boolean {
const conversation = this.conversations.get(conversationId)
if (!conversation) return false
const index = conversation.messages.findIndex(m => m.id === messageId)
if (index === -1) return false
conversation.messages.splice(index)
conversation.updatedAt = new Date()
return true
}
/**
* 获取对话中最后一条用户消息
*/
getLastUserMessage(conversationId: string): Message | undefined {
const conversation = this.conversations.get(conversationId)
if (!conversation) return undefined
for (let i = conversation.messages.length - 1; i >= 0; i--) {
if (conversation.messages[i].role === 'user') {
return conversation.messages[i]
}
}
return undefined
}
/**
* 获取对话中最后一条消息
*/
getLastMessage(conversationId: string): Message | undefined {
const conversation = this.conversations.get(conversationId)
if (!conversation) return undefined
const messages = conversation.messages
return messages.length > 0 ? messages[messages.length - 1] : undefined
}
/**
* 获取消息预览文本
*/
getMessagePreview(content: string, maxLength = 50): string {
if (!content) return ''
const text = content.replace(/\n/g, ' ').trim()
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text
}
/**
* 获取对话中成功的消息(用于发送给 LLM
*/
getSuccessMessages(conversationId: string): Message[] {
const conversation = this.conversations.get(conversationId)
if (!conversation) return []
return conversation.messages.filter(m =>
m.status === 'success' || m.status === 'paused'
)
}
/**
* 获取最近的 N 条成功消息
*/
getRecentSuccessMessages(conversationId: string, limit: number): Message[] {
const successMessages = this.getSuccessMessages(conversationId)
return successMessages.slice(-limit)
}
/**
* 生成唯一 ID
*/
private generateId(): string {
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
}